use std::collections::HashMap; use crate::{ model::{ family::Family, log::Log, logbook::Logbook, role::Role, user::{ AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, SchnupperBetreuerUser, User, UserWithDetails, UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, }, }, tera::Config, }; use futures::future::join_all; use lettre::Address; use rocket::{ form::Form, fs::TempFile, get, http::{ContentType, Status}, post, request::{FlashMessage, FromRequest, Outcome}, response::{Flash, Redirect}, routes, FromForm, Request, Route, State, }; use rocket_dyn_templates::{tera::Context, Template}; use sqlx::SqlitePool; // Custom request guard to extract the Referer header struct Referer(String); #[rocket::async_trait] impl<'r> FromRequest<'r> for Referer { type Error = (); async fn from_request(request: &'r Request<'_>) -> Outcome { match request.headers().get_one("Referer") { Some(referer) => Outcome::Success(Referer(referer.to_string())), None => Outcome::Error((Status::BadRequest, ())), } } } #[get("/user")] async fn index( db: &State, user: VorstandUser, flash: Option>, ) -> Template { let user_futures: Vec<_> = User::all(db) .await .into_iter() .map(|u| async move { UserWithRolesAndMembershipPdf::from_user(db, u).await }) .collect(); let user: User = user.into_inner(); let allowed_to_edit = ManageUserUser::new(db, user.clone()).await.is_some(); let users: Vec = join_all(user_futures).await; let roles = Role::all(db).await; let families = Family::all_with_members(db).await; let mut context = Context::new(); if let Some(msg) = flash { context.insert("flash", &msg.into_inner()); } context.insert("allowed_to_edit", &allowed_to_edit); context.insert("users", &users); context.insert("roles", &roles); context.insert("families", &families); context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await); Template::render("admin/user/index", context.into_json()) } #[get("/user", rank = 2)] async fn index_admin( db: &State, user: AdminUser, flash: Option>, ) -> Template { let user_futures: Vec<_> = User::all(db) .await .into_iter() .map(|u| async move { UserWithRolesAndMembershipPdf::from_user(db, u).await }) .collect(); let users: Vec = join_all(user_futures).await; let user: User = user.user; let allowed_to_edit = ManageUserUser::new(db, user.clone()).await.is_some(); let roles = Role::all(db).await; let families = Family::all_with_members(db).await; let mut context = Context::new(); if let Some(msg) = flash { context.insert("flash", &msg.into_inner()); } context.insert("allowed_to_edit", &allowed_to_edit); context.insert("users", &users); context.insert("roles", &roles); context.insert("families", &families); context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await); Template::render("admin/user/index", context.into_json()) } #[get("/user/fees")] async fn fees( db: &State, user: VorstandUser, flash: Option>, ) -> Template { let mut context = Context::new(); let users = User::all_payer_groups(db).await; let mut fees = Vec::new(); for user in users { if let Some(fee) = user.fee(db).await { fees.push(fee); } } context.insert("fees", &fees); if let Some(msg) = flash { context.insert("flash", &msg.into_inner()); } context.insert( "loggedin_user", &UserWithDetails::from_user(user.into_inner(), db).await, ); Template::render("admin/user/fees", context.into_json()) } #[get("/user/scheckbuch")] async fn scheckbuch( db: &State, user: VorstandUser, flash: Option>, ) -> Template { let mut context = Context::new(); let scheckbooks = Role::find_by_name(db, "scheckbuch").await.unwrap(); let scheckbooks = User::all_with_role(db, &scheckbooks).await; let mut scheckbooks_with_roles = Vec::new(); for s in scheckbooks { scheckbooks_with_roles.push(( Logbook::completed_with_user(db, &s).await, UserWithDetails::from_user(s, db).await, )) } context.insert("scheckbooks", &scheckbooks_with_roles); if let Some(msg) = flash { context.insert("flash", &msg.into_inner()); } context.insert( "loggedin_user", &UserWithDetails::from_user(user.into_inner(), db).await, ); Template::render("admin/user/scheckbuch", context.into_json()) } #[get("/user/fees/paid?")] async fn fees_paid( db: &State, calling_user: AllowedToEditPaymentStatusUser, user_ids: Vec, referer: Referer, ) -> Flash { let mut res = String::new(); for user_id in user_ids { let user = User::find_by_id(db, user_id).await.unwrap(); res.push_str(&format!("{} + ", user.name)); if user.has_role(db, "paid").await { Log::create( db, format!( "{} set fees NOT paid for '{}'", calling_user.user.name, user.name ), ) .await; user.remove_role(db, &Role::find_by_name(db, "paid").await.unwrap()) .await; } else { Log::create( db, format!( "{} set fees paid for '{}'", calling_user.user.name, user.name ), ) .await; user.add_role(db, &Role::find_by_name(db, "paid").await.unwrap()) .await .expect("paid role has no group"); } } res.truncate(res.len() - 3); // remove ' + ' from the end Flash::success( Redirect::to(referer.0), format!("Zahlungsstatus von {} erfolgreich geändert", res), ) } #[get("/user//send-welcome-mail")] async fn send_welcome_mail( db: &State, _admin: ManageUserUser, config: &State, user: i32, ) -> Flash { let Some(user) = User::find_by_id(db, user).await else { return Flash::error(Redirect::to("/admin/user"), "User does not exist"); }; match user.send_welcome_email(db, &config.smtp_pw).await { Ok(()) => Flash::success( Redirect::to("/admin/user"), format!("Willkommens-Email wurde an {} versandt.", user.name), ), Err(e) => Flash::error(Redirect::to("/admin/user"), e), } } #[get("/user//reset-pw")] async fn resetpw(db: &State, admin: ManageUserUser, user: i32) -> Flash { let user = User::find_by_id(db, user).await; match user { Some(user) => { Log::create( db, format!("{} has resetted the pw for {}", admin.user.name, user.name), ) .await; user.reset_pw(db).await; Flash::success( Redirect::to("/admin/user"), format!("Passwort von {} zurückgesetzt", user.name), ) } None => Flash::error(Redirect::to("/admin/user"), "User does not exist"), } } #[get("/user//delete")] async fn delete(db: &State, admin: ManageUserUser, user: i32) -> Flash { let user = User::find_by_id(db, user).await; Log::create(db, format!("{} deleted user: {user:?}", admin.user.name)).await; match user { Some(user) => { user.delete(db).await; Flash::success( Redirect::to("/admin/user"), format!("Benutzer {} gelöscht", user.name), ) } None => Flash::error(Redirect::to("/admin/user"), "User does not exist"), } } #[derive(FromForm, Debug)] pub struct UserEditForm<'a> { pub(crate) id: i32, pub(crate) dob: Option, pub(crate) weight: Option, pub(crate) sex: Option, pub(crate) roles: HashMap, pub(crate) member_since_date: Option, pub(crate) birthdate: Option, pub(crate) mail: Option, pub(crate) nickname: Option, pub(crate) notes: Option, pub(crate) phone: Option, pub(crate) address: Option, pub(crate) family_id: Option, pub(crate) membership_pdf: Option>, } #[post("/user", data = "", format = "multipart/form-data")] async fn update( db: &State, data: Form>, admin: ManageUserUser, ) -> Flash { let user = User::find_by_id(db, data.id).await; Log::create( db, format!("{} updated user from {user:?} to {data:?}", admin.user.name), ) .await; let Some(user) = user else { return Flash::error( Redirect::to("/admin/user"), format!("User with ID {} does not exist!", data.id), ); }; match user.update(db, data.into_inner()).await { Ok(_) => Flash::success(Redirect::to("/admin/user"), "Successfully updated user"), Err(e) => Flash::error(Redirect::to("/admin/user"), e), } } #[get("/user//membership")] async fn download_membership_pdf( db: &State, admin: ManageUserUser, user: i32, ) -> (ContentType, Vec) { let user = User::find_by_id(db, user).await.unwrap(); let user = UserWithMembershipPdf::from(db, user).await; Log::create( db, format!( "{} downloaded membership application for user: {}", admin.user.name, user.user.name ), ) .await; (ContentType::PDF, user.membership_pdf.unwrap()) } #[derive(FromForm, Debug)] struct UserAddForm<'r> { name: &'r str, } #[post("/user/new", data = "")] async fn create( db: &State, data: Form>, admin: ManageUserUser, ) -> Flash { if User::create(db, data.name).await { Log::create( db, format!("{} created new user: {data:?}", admin.user.name), ) .await; Flash::success(Redirect::to("/admin/user"), "Successfully created user") } else { Flash::error( Redirect::to("/admin/user"), format!("User {} already exists", data.name), ) } } #[derive(FromForm, Debug)] struct UserAddScheckbuchForm<'r> { name: &'r str, mail: &'r str, } #[post("/user/new/scheckbuch", data = "")] async fn create_scheckbuch( db: &State, data: Form>, admin: VorstandUser, config: &State, ) -> Flash { // 1. Check mail adress let mail = data.mail.trim(); if mail.parse::
().is_err() { return Flash::error( Redirect::to("/admin/user/scheckbuch"), "Keine gültige Mailadresse".to_string(), ); } // 2. Check name let name = data.name.trim(); if User::find_by_name(db, name).await.is_some() { return Flash::error( Redirect::to("/admin/user/scheckbuch"), "Kann kein Scheckbuch erstellen, der Name wird bereits von einem User verwendet" .to_string(), ); } // 3. Create user User::create_with_mail(db, name, mail).await; let user = User::find_by_name(db, name).await.unwrap(); // 4. Add 'scheckbuch' role let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap(); user.add_role(db, &scheckbuch) .await .expect("new user has no roles yet"); // 4. Send welcome mail (+ notification) user.send_welcome_email(db, &config.smtp_pw).await.unwrap(); Log::create( db, format!("{} created new scheckbuch: {data:?}", admin.name), ) .await; Flash::success(Redirect::to("/admin/user/scheckbuch"), &format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {mail} verschickt.")) } #[get("/user/move/schnupperant//to/scheckbuch")] async fn schnupper_to_scheckbuch( db: &State, id: i32, admin: SchnupperBetreuerUser, config: &State, ) -> Flash { let Some(user) = User::find_by_id(db, id).await else { return Flash::error( Redirect::to("/admin/schnupper"), "user id not found".to_string(), ); }; if !user.has_role(db, "schnupperant").await { return Flash::error( Redirect::to("/admin/schnupper"), "kein schnupperant...".to_string(), ); } let schnupperant = Role::find_by_name(db, "schnupperant").await.unwrap(); let paid = Role::find_by_name(db, "paid").await.unwrap(); user.remove_role(db, &schnupperant).await; user.remove_role(db, &paid).await; let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap(); user.add_role(db, &scheckbuch) .await .expect("just removed 'schnupperant' thus can't have a role with that group"); if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await { user.add_role(db, &no_einschreibgebuehr) .await .expect("role doesn't have a group"); } user.send_welcome_email(db, &config.smtp_pw).await.unwrap(); Log::create( db, format!( "{} created new scheckbuch (from schnupperant): {}", admin.name, user.name ), ) .await; Flash::success(Redirect::to("/admin/schnupper"), &format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {} verschickt.", user.mail.unwrap())) } pub fn routes() -> Vec { routes![ index, index_admin, resetpw, update, create, create_scheckbuch, schnupper_to_scheckbuch, delete, fees, fees_paid, scheckbuch, download_membership_pdf, send_welcome_mail ] }