diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d71f35f..b197ee1 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -23,7 +23,7 @@ oauth2 = "4.4" http = "1.1" tower-http = { version = "0.6.1", features = ["full"] } chrono = { version = "0.4.38", features = ["serde"] } -sqlx = { version = "0.8", features = ["postgres", "runtime-tokio", "macros", "chrono", "uuid"] } +sqlx = { version = "0.8", features = ["postgres", "sqlite","runtime-tokio", "macros", "chrono", "uuid"] } uuid = { version = "1.10", features = ["v4"] } dotenvy = "0.15" constant_time_eq = "0.3" diff --git a/backend/migrations/20241210153133_inital_tables.down.sql b/backend/migrations/20241210153133_inital_tables.down.sql index 2d7ac35..4418e95 100644 --- a/backend/migrations/20241210153133_inital_tables.down.sql +++ b/backend/migrations/20241210153133_inital_tables.down.sql @@ -1,13 +1,13 @@ -- Drop Postgres tables -drop table oauth2_state_storage; -drop table user_sessions; -drop table users; -drop table roles; -drop table user_roles; -drop table role_permissions; -drop table wishlist_items; -drop table gift_exchange; -drop table gift_exchange_participants; -drop table calendar; -drop table calendar_event_types; -drop table calendar_events; +drop table if exists oauth2_state_storage; +drop table if exists user_sessions; +drop table if exists users; +drop table if exists roles; +drop table if exists user_roles; +drop table if exists role_permissions; +drop table if exists wishlist_items; +drop table if exists gift_exchange; +drop table if exists gift_exchange_participants; +drop table if exists calendar; +drop table if exists calendar_event_types; +drop table if exists calendar_events; diff --git a/backend/migrations/20241210153133_inital_tables.up.sql b/backend/migrations/20241210153133_inital_tables.up.sql index a3ee354..726e5ac 100644 --- a/backend/migrations/20241210153133_inital_tables.up.sql +++ b/backend/migrations/20241210153133_inital_tables.up.sql @@ -47,6 +47,8 @@ create table IF NOT EXISTS user_roles ( role_id uuid NOT NULL ); +create unique index if not exists unique_user_role on user_roles(user_id, role_id); + create table IF NOT EXISTS role_permissions ( id uuid PRIMARY KEY default gen_random_uuid(), created_at timestamp NOT NULL default now(), @@ -64,8 +66,8 @@ create table if not exists wishlist_items ( updated_at timestamp null default now(), updated_by uuid null, user_id uuid null, - item varchar(255) null, - item_url varchar(255) null, + item varchar(512) null, + item_url varchar(1024) null, purchased_by uuid null, received_at timestamp null ); diff --git a/backend/src/main.rs b/backend/src/main.rs index c710dad..41e59d9 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -3,13 +3,12 @@ use axum::{ routing::{get, get_service}, Extension, Router, }; +use dotenvy::var; use secret_gift_exchange::{giftexchange, giftexchange_save, giftexchanges}; -use sqlx::migrate::Migrator; -use sqlx::{PgPool, postgres::PgPoolOptions}; +use sqlx::{migrate::Migrator, sqlite::SqlitePoolOptions, sqlite::SqliteRow, Row, SqlitePool}; +use sqlx::{postgres::PgPoolOptions, PgPool}; use std::net::SocketAddr; use tower_http::services::ServeDir; -use dotenvy::var; - mod calendar; mod email; @@ -17,17 +16,21 @@ mod error_handling; mod google_oauth; mod middlewares; mod routes; +mod secret_gift_exchange; mod user; mod wishlist; -mod secret_gift_exchange; +use calendar::{calendar, get_events}; use error_handling::AppError; use google_oauth::{google_auth_return, login, logout}; use middlewares::inject_user_data; use routes::{about, contact, dashboard, index, profile, user_profile, useradmin}; use user::{add_user_role, delete_user_role, UserData}; -use calendar::{calendar, get_events}; -use wishlist::{user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item, user_wishlist_delete_item, user_wishlist_edit_item, user_wishlist_received_item, user_wishlist_returned_item, user_wishlist_save_item, wishlists}; +use wishlist::{ + user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item, + user_wishlist_delete_item, user_wishlist_edit_item, user_wishlist_received_item, + user_wishlist_returned_item, user_wishlist_save_item, wishlists, +}; //use email::send_emails; #[derive(Clone)] @@ -44,9 +47,7 @@ async fn main() { let database_url = var("DATABASE_URL").expect("DATABASE_URL not set"); let db_pool = PgPoolOptions::new().connect(&database_url).await.unwrap(); - let app_state = AppState { - db_pool: db_pool, - }; + let app_state = AppState { db_pool: db_pool }; static MIGRATOR: Migrator = sqlx::migrate!(); @@ -55,39 +56,74 @@ async fn main() { .await .expect("Failed to run migrations"); + // Copy from old sqlite database if it exists + if let Ok(source_db_url) = var("SOURCE_DB_URL") { + let sdb_pool = SqlitePoolOptions::new() + .max_connections(5) + .connect(&source_db_url) + .await + .unwrap(); + + copy_database(&sdb_pool, &app_state.db_pool).await; + } else { + println!("SOURCE_DB_URL not set"); + } + let user_data: Option = None; // build our application with some routes let app = Router::new() .route("/dashboard", get(dashboard)) - // User .route("/profile", get(profile)) .route("/useradmin", get(useradmin)) .route("/users/:user_id", get(user_profile)) .route("/roles/:user_id/:role_id/add", get(add_user_role)) - .route("/roles/:user_id/:user_role_id/delete", get(delete_user_role)) - + .route( + "/roles/:user_id/:user_role_id/delete", + get(delete_user_role), + ) // Calendar .route("/calendar", get(calendar)) .route("/getevents/:calendar", get(get_events)) - // Wishlist - .route("/wishlists", get(wishlists)) + .route("/wishlists", get(wishlists)) .route("/userwishlist/:user_id", get(user_wishlist)) - .route("/userwishlist/add/:user_id", get(user_wishlist_add).post(user_wishlist_add_item)) - .route("/userwishlist/edit/:item_id", get(user_wishlist_edit_item).post(user_wishlist_save_item)) - .route("/userwishlist/bought/:user_id", get(user_wishlist_bought_item)) - .route("/userwishlist/received/:user_id", get(user_wishlist_received_item)) - .route("/userwishlist/delete/:item_id", get(user_wishlist_delete_item)) - .route("/userwishlist/returned/:item_id", get(user_wishlist_returned_item)) - + .route( + "/userwishlist/add/:user_id", + get(user_wishlist_add).post(user_wishlist_add_item), + ) + .route( + "/userwishlist/edit/:item_id", + get(user_wishlist_edit_item).post(user_wishlist_save_item), + ) + .route( + "/userwishlist/bought/:user_id", + get(user_wishlist_bought_item), + ) + .route( + "/userwishlist/received/:user_id", + get(user_wishlist_received_item), + ) + .route( + "/userwishlist/delete/:item_id", + get(user_wishlist_delete_item), + ) + .route( + "/userwishlist/returned/:item_id", + get(user_wishlist_returned_item), + ) // Secret Gift Exchange - Not ready for public use yet .route("/giftexchanges", get(giftexchanges)) - .route("/giftexchange/:giftexchange_id", get(giftexchange).post(giftexchange_save)) - - .nest_service("/assets", ServeDir::new("templates/assets") - .fallback(get_service(ServeDir::new("templates/assets")))) + .route( + "/giftexchange/:giftexchange_id", + get(giftexchange).post(giftexchange_save), + ) + .nest_service( + "/assets", + ServeDir::new("templates/assets") + .fallback(get_service(ServeDir::new("templates/assets"))), + ) .route("/", get(index)) .route("/about", get(about)) .route("/contactus", get(contact)) @@ -113,3 +149,169 @@ async fn main() { .await .unwrap(); } + +async fn copy_database(sdb_pool: &SqlitePool, db_pool: &PgPool) { + // Copy users + let users = sqlx::query( + r#"select + datetime(u.created_at, 'unixepoch'), + coalesce(cb.email, 'admin@jean-marie.ca') as created_by_email, + datetime(u.updated_at, 'unixepoch'), + coalesce(ub.email, 'admin@jean-marie.ca') as updated_by_email, + u.email, + u.name, + u.family_name, + u.given_name + from users u + left join users cb on cb.id = u.created_by + left join users ub on ub.id = u.updated_by;"#, + ) + .fetch_all(sdb_pool) + .await + .expect("Failed to copy users from SQLite to Postgres"); + + println!("\nCopying {} users", users.len()); + + for user in users { + if let ( + Ok(created_at), + Ok(created_by), + Ok(updated_at), + Ok(updated_by), + Ok(email), + Ok(name), + Ok(family_name), + Ok(given_name), + ) = ( + user.try_get::(0), + user.try_get::(1), + user.try_get::(2), + user.try_get::(3), + user.try_get::(4), + user.try_get::(5), + user.try_get::(6), + user.try_get::(7), + ) { + let result = sqlx::query( + r#"insert into users (created_at, created_by, updated_at, updated_by, email, name, family_name, given_name) + values ($1, (select id from users where email =$2), $3, (select id from users where email =$4), $5, $6, $7, $8)"# + ) + .bind(created_at) + .bind(created_by) + .bind(updated_at) + .bind(updated_by) + .bind(email) + .bind(name) + .bind(family_name) + .bind(given_name) + .execute(db_pool) + .await; + + if let Err(e) = result { + println!("Error: {}", e); + } + } + } + + // Copy user roles + let user_roles = sqlx::query( + r#"select + datetime(ur.created_at, 'unixepoch'), + coalesce(cb.email, 'admin@jean-marie.ca') as created_by_email, + datetime(ur.updated_at, 'unixepoch'), + coalesce(ub.email, 'admin@jean-marie.ca') as updated_by_email, + u.email as user_email, + r.name as role_name + from user_roles ur + left join users cb on cb.id = ur.created_by + left join users ub on ub.id = ur.updated_by + join users u on u.id = ur.user_id + join roles r on r.id = ur.role_id;"#, + ) + .fetch_all(sdb_pool) + .await + .expect("Failed to copy user roles from SQLite to Postgres"); + + println!("\nCopying {} user roles", user_roles.len()); + + for user_role in user_roles { + if let ( + Ok(created_at), + Ok(created_by), + Ok(updated_at), + Ok(updated_by), + Ok(user_email), + Ok(role_name), + ) = ( + user_role.try_get::(0), + user_role.try_get::(1), + user_role.try_get::(2), + user_role.try_get::(3), + user_role.try_get::(4), + user_role.try_get::(5), + ) { + let result = sqlx::query( + r#"insert into user_roles (created_at, created_by, updated_at, updated_by, user_id, role_id) + values ($1, (select id from users where email=$2), $3, (select id from users where email=$4), (select id from users where email=$5), (select id from roles where name=$6))"# + ) + .bind(created_at) + .bind(created_by) + .bind(updated_at) + .bind(updated_by) + .bind(user_email) + .bind(role_name) + .execute(db_pool) + .await; + + if let Err(e) = result { + println!("Error: {}", e); + } + } + } + + // Copy wishlistitems + let wishlistitems = sqlx::query( + r#"select + datetime(wi.created_at, 'unixepoch'), + coalesce(cb.email, 'admin@jean-marie.ca') as created_by_email, + datetime(wi.updated_at, 'unixepoch'), + coalesce(ub.email, 'admin@jean-marie.ca') as updated_by_email, + u.email as user_email, + wi.item, + wi.item_url, + pb.email, + datetime(wi.received_at, 'unixepoch') + from wishlist_items wi + left join users cb on cb.id = wi.created_by + left join users ub on ub.id = wi.updated_by + left join users pb on pb.id = wi.purchased_by + join users u on u.id = wi.user_id;"#, + ) + .fetch_all(sdb_pool) + .await + .expect("Failed to copy wishlistitems from SQLite to Postgres"); + + println!("\nCopying {} wishlistitems", wishlistitems.len()); + + for wishlistitem in wishlistitems { + let result = sqlx::query( + r#"insert into wishlist_items (created_at, created_by, updated_at, updated_by, user_id, item, item_url, purchased_by, received_at) + values ($1, (select id from users where email=$2), $3, (select id from users where email=$4), (select id from users where email=$5), $6, $7, (select id from users where email=$8), $9)"# + ) + .bind(wishlistitem.try_get::(0).unwrap()) + .bind(wishlistitem.try_get::(1).unwrap()) + .bind(wishlistitem.try_get::(2).unwrap()) + .bind(wishlistitem.try_get::(3).unwrap()) + .bind(wishlistitem.try_get::(4).unwrap()) + .bind(wishlistitem.try_get::(5).unwrap()) + .bind(wishlistitem.try_get::(6).unwrap()) + .bind(wishlistitem.try_get::(7).unwrap()) + .bind(wishlistitem.try_get::(8).unwrap_or_default()) + .execute(db_pool) + .await; + + if let Err(e) = result { + println!("Error: {}", e); + } + } +}