diff --git a/migration.sql b/migration.sql index 11c7494..421ca30 100644 --- a/migration.sql +++ b/migration.sql @@ -16,7 +16,8 @@ CREATE TABLE IF NOT EXISTS "user" ( "notes" text, "phone" text, "address" text, - "family_id" INTEGER REFERENCES family(id) + "family_id" INTEGER REFERENCES family(id), + "membership_pdf" BLOB ); CREATE TABLE IF NOT EXISTS "family" ( diff --git a/src/model/family.rs b/src/model/family.rs index f648cfd..6bff530 100644 --- a/src/model/family.rs +++ b/src/model/family.rs @@ -75,7 +75,7 @@ GROUP BY family.id;" } pub async fn members(&self, db: &SqlitePool) -> Vec { - sqlx::query_as!(User, "SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user WHERE family_id = ?", self.id) + sqlx::query_as!(User, "SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, membership_pdf FROM user WHERE family_id = ?", self.id) .fetch_all(db) .await .unwrap() diff --git a/src/model/rower.rs b/src/model/rower.rs index 796a2ad..a953738 100644 --- a/src/model/rower.rs +++ b/src/model/rower.rs @@ -16,7 +16,7 @@ impl Rower { sqlx::query_as!( User, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id +SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, membership_pdf FROM user WHERE id in (SELECT rower_id FROM rower WHERE logbook_id=?) ", diff --git a/src/model/user.rs b/src/model/user.rs index ca6b710..e4f7eb3 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1,13 +1,19 @@ -use std::ops::{Deref, DerefMut}; +use std::{ + fs::File, + io::Read, + ops::{Deref, DerefMut}, +}; use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; use chrono::{Datelike, Local, NaiveDate}; use log::info; use rocket::{ async_trait, - http::{Cookie, Status}, + data::ToByteUnit, + http::{ext::IntoCollection, Cookie, Status}, request::{self, FromRequest, Outcome}, time::{Duration, OffsetDateTime}, + tokio::io::AsyncReadExt, Request, }; use serde::{Deserialize, Serialize}; @@ -43,6 +49,7 @@ pub struct User { pub phone: Option, pub address: Option, pub family_id: Option, + pub membership_pdf: Option>, } #[derive(Debug, Serialize, Deserialize)] @@ -285,7 +292,7 @@ impl User { sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id +SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, membership_pdf FROM user WHERE id like ? ", @@ -300,7 +307,7 @@ WHERE id like ? sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id +SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, membership_pdf FROM user WHERE id like ? ", @@ -315,7 +322,7 @@ WHERE id like ? sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id +SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, membership_pdf FROM user WHERE name like ? ", @@ -357,7 +364,7 @@ WHERE name like ? sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id +SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, membership_pdf FROM user WHERE deleted = 0 ORDER BY last_access DESC @@ -372,7 +379,7 @@ ORDER BY last_access DESC sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id +SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, membership_pdf FROM user u JOIN user_role ur ON u.id = ur.user_id WHERE ur.role_id = ? AND deleted = 0 @@ -388,14 +395,14 @@ ORDER BY name; sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user +SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, membership_pdf FROM user WHERE family_id IS NOT NULL GROUP BY family_id UNION -- Select users with a null family_id, without grouping -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id FROM user +SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, membership_pdf FROM user WHERE family_id IS NULL; " ) @@ -408,7 +415,7 @@ WHERE family_id IS NULL; sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id +SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, membership_pdf FROM user WHERE deleted = 0 AND dob != '' and weight != '' and sex != '' ORDER BY name @@ -423,7 +430,7 @@ ORDER BY name sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id +SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, membership_pdf FROM user WHERE deleted = 0 AND (SELECT COUNT(*) FROM user_role WHERE user_id=user.id AND role_id = (SELECT id FROM role WHERE name = 'cox')) > 0 ORDER BY last_access DESC @@ -441,13 +448,29 @@ ORDER BY last_access DESC .is_ok() } - pub async fn update(&self, db: &SqlitePool, data: UserEditForm) { + pub async fn update(&self, db: &SqlitePool, data: UserEditForm<'_>) { let mut family_id = data.family_id; if family_id.is_some_and(|x| x == -1) { family_id = Some(Family::insert(db).await) } + if self.membership_pdf.is_none() { + if let Some(membership_pdf) = data.membership_pdf { + let mut stream = membership_pdf.open().await.unwrap(); + let mut buffer = Vec::new(); + stream.read_to_end(&mut buffer).await.unwrap(); + sqlx::query!( + "UPDATE user SET membership_pdf = ? where id = ?", + buffer, + self.id + ) + .execute(db) + .await + .unwrap(); //Okay, because we can only create a User of a valid id + } + } + sqlx::query!( "UPDATE user SET dob = ?, weight = ?, sex = ?, member_since_date=?, birthdate=?, mail=?, nickname=?, notes=?, phone=?, address=?, family_id = ? where id = ?", data.dob, diff --git a/src/tera/admin/user.rs b/src/tera/admin/user.rs index 7a55d85..ea0eb56 100644 --- a/src/tera/admin/user.rs +++ b/src/tera/admin/user.rs @@ -10,11 +10,12 @@ use crate::model::{ use futures::future::join_all; use rocket::{ form::Form, + fs::TempFile, get, - http::Status, + http::{ContentType, Status}, post, request::{FlashMessage, FromRequest, Outcome}, - response::{Flash, Redirect}, + response::{content, Flash, Redirect}, routes, FromForm, Request, Route, State, }; use rocket_dyn_templates::{tera::Context, Template}; @@ -231,7 +232,7 @@ async fn delete(db: &State, admin: AdminUser, user: i32) -> Flash { pub(crate) id: i32, pub(crate) dob: Option, pub(crate) weight: Option, @@ -245,12 +246,13 @@ pub struct UserEditForm { pub(crate) phone: Option, pub(crate) address: Option, pub(crate) family_id: Option, + pub(crate) membership_pdf: Option>, } -#[post("/user", data = "")] +#[post("/user", data = "", format = "multipart/form-data")] async fn update( db: &State, - data: Form, + data: Form>, admin: AdminUser, ) -> Flash { let user = User::find_by_id(db, data.id).await; @@ -271,6 +273,25 @@ async fn update( Flash::success(Redirect::to("/admin/user"), "Successfully updated user") } +#[get("/user//membership")] +async fn download_membership_pdf( + db: &State, + admin: AdminUser, + user: i32, +) -> (ContentType, Vec) { + let user = User::find_by_id(db, user).await.unwrap(); + Log::create( + db, + format!( + "{} downloaded membership application for user: {user:?}", + admin.user.name + ), + ) + .await; + + (ContentType::PDF, user.membership_pdf.unwrap()) +} + #[derive(FromForm, Debug)] struct UserAddForm<'r> { name: &'r str, @@ -307,6 +328,7 @@ pub fn routes() -> Vec { delete, fees, fees_paid, - scheckbuch + scheckbuch, + download_membership_pdf ] } diff --git a/templates/admin/user/index.html.tera b/templates/admin/user/index.html.tera index 068ce64..5da8e7b 100644 --- a/templates/admin/user/index.html.tera +++ b/templates/admin/user/index.html.tera @@ -33,7 +33,7 @@ name="name" id="filter-js" class="search-bar" - placeholder="Suchen nach (Name, [yes|no]-role:)" /> + placeholder="Suchen nach (Name, [yes|no]-role:, has-[no-]membership-pdf)" />
@@ -41,9 +41,10 @@ class="text-primary-950 dark:text-white text-right">
{% for user in users %}
+ data-filter="{{ user.name }} {% for role in roles %} {% if role.name in user.roles %} yes-role:{{ role.name }} {% else %} no-role:{{ role.name }} {% endif %} role-{{ role }} {% endfor %} {% if user.membership_pdf %}has-membership-pdf{% else %}has-no-membership-pdf{% endif %} ">
@@ -62,6 +63,11 @@ {% for role in roles %} {{ macros::checkbox(label=role.name, name="roles[" ~ role.id ~ "]", id=loop.index , checked=role.name in user.roles, disabled=allowed_to_edit == false) }} {% endfor %} + {% if user.membership_pdf %} + Beitrittserklärung herunterladen + {% endif %} + {{ macros::input(label='Beitrittserklärung', name='membership_pdf', id=loop.index, type="file", readonly=allowed_to_edit == false) }} {{ macros::input(label='DOB', name='dob', id=loop.index, type="text", value=user.dob, readonly=allowed_to_edit == false) }} {{ macros::input(label='Weight (kg)', name='weight', id=loop.index, type="text", value=user.weight, readonly=allowed_to_edit == false) }} {{ macros::input(label='Sex', name='sex', id=loop.index, type="text", value=user.sex, readonly=allowed_to_edit == false) }}