jean-marie/backend/src/main.rs

365 lines
13 KiB
Rust

use axum::{
middleware,
routing::{get, get_service, post},
Extension, Router,
};
use dotenvy::var;
use secret_gift_exchange::{giftexchange, giftexchange_save, giftexchanges};
use sqlx::{migrate::Migrator, sqlite::SqlitePoolOptions, Row, SqlitePool};
use sqlx::{postgres::PgPoolOptions, PgPool};
use std::net::SocketAddr;
use tower_http::services::ServeDir;
mod calendar;
mod email;
mod error_handling;
mod google_oauth;
mod middlewares;
mod routes;
mod secret_gift_exchange;
mod user;
mod wishlist;
use calendar::{calendar, get_events, create_event, new_event};
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, user_profile_account, useradmin};
use user::{add_user_role, delete_user_role, AccountData};
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 crate::calendar::new_request;
//use email::send_emails;
#[derive(Clone)]
pub struct AppState {
pub db_pool: PgPool,
}
#[tokio::main]
async fn main() {
// initialize tracing
tracing_subscriber::fmt::init();
// Get the server settings from the env file
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 };
static MIGRATOR: Migrator = sqlx::migrate!();
MIGRATOR
.run(&app_state.db_pool)
.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<AccountData> = 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("/user/{user_id}", get(user_profile))
.route("/user/{user_id}/{account_id}", get(user_profile_account))
.route("/roles/{user_id}/{role_id}/add", get(add_user_role))
.route(
"/roles/{user_id}/{user_role_id}/delete",
get(delete_user_role),
)
// Calendar
.route("/calendar", get(calendar))
.route("/calendar/getevents", get(get_events))
.route("/calendar/createevent", post(create_event))
.route("/calendar/newevent", get(new_event))
.route("/calendar/newrequest", post(new_request))
// Wishlist
.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),
)
// 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("/", get(index))
.route("/about", get(about))
.route("/contactus", get(contact))
.route("/login", get(login))
.route("/logout", get(logout))
.route("/google_auth_return", get(google_auth_return))
.route_layer(middleware::from_fn_with_state(
app_state.db_pool.clone(),
inject_user_data,
))
.with_state(app_state.db_pool.clone())
.layer(Extension(user_data));
// Send email indicating server has started
//let recipients = get_useremails_by_role("admin".to_string(), &app_state.db_pool).await;
//send_emails("Server started".to_string(), recipients, "Server has been started".to_string());
// run it
let addr = SocketAddr::from(([0, 0, 0, 0], 40192));
tracing::debug!("listening on {}", addr);
axum_server::bind(addr)
.serve(app.into_make_service())
.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::<chrono::NaiveDateTime, _>(0),
user.try_get::<String, _>(1),
user.try_get::<chrono::NaiveDateTime, _>(2),
user.try_get::<String, _>(3),
user.try_get::<String, _>(4),
user.try_get::<String, _>(5),
user.try_get::<String, _>(6),
user.try_get::<String, _>(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::<chrono::NaiveDateTime, _>(0),
user_role.try_get::<String, _>(1),
user_role.try_get::<chrono::NaiveDateTime, _>(2),
user_role.try_get::<String, _>(3),
user_role.try_get::<String, _>(4),
user_role.try_get::<String, _>(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::<chrono::NaiveDateTime,_>(0).unwrap())
.bind(wishlistitem.try_get::<String,_>(1).unwrap())
.bind(wishlistitem.try_get::<chrono::NaiveDateTime,_>(2).unwrap())
.bind(wishlistitem.try_get::<String,_>(3).unwrap())
.bind(wishlistitem.try_get::<String,_>(4).unwrap())
.bind(wishlistitem.try_get::<String,_>(5).unwrap())
.bind(wishlistitem.try_get::<String,_>(6).unwrap())
.bind(wishlistitem.try_get::<String,_>(7).unwrap())
.bind(wishlistitem.try_get::<chrono::NaiveDateTime,_>(8).unwrap_or_default())
.execute(db_pool)
.await;
if let Err(e) = result {
println!("Error: {}", e);
}
}
// Run migration scripts again
// Copy accounts(users) to profiles(people)
let result =sqlx::query(r#"insert into people (created_by, updated_by, email, name, family_name, given_name)
select created_by, updated_by, email, name, family_name, given_name from users
on conflict do nothing;"#)
.execute(db_pool)
.await;
if let Err(e) = result {
println!("Error: {}", e);
}
// Link accounts to profiles
let result = sqlx::query(r#"update users u set person_id = p.id from people p where p.email = u.email;"#)
.execute(db_pool)
.await;
if let Err(e) = result {
println!("Error: {}", e);
}
// Move wishlist items from accounts to profiles
let result = sqlx::query(r#"update wishlist_items wi set user_id = p.person_id from users p where p.id = wi.user_id;"#)
.execute(db_pool)
.await;
if let Err(e) = result {
println!("Error: {}", e);
}
// Copy normal role from accounts to profiles
let result = sqlx::query(r#"insert into user_roles (created_at, created_by, updated_at, updated_by, user_id, role_id)
select ur.created_at, ur.created_by, ur.updated_at, ur.updated_by, u.person_id, ur.role_id from user_roles ur join roles r on r.id = ur.role_id join users u on u.id = ur.user_id where r.name = 'normal'
on conflict do nothing;"#)
.execute(db_pool)
.await;
if let Err(e) = result {
println!("Error: {}", e);
}
}