diff --git a/migration.sql b/migration.sql index d774e7f..1d18740 100644 --- a/migration.sql +++ b/migration.sql @@ -225,6 +225,15 @@ CREATE TABLE IF NOT EXISTS "distance" ( ); +CREATE TABLE IF NOT EXISTS "activity" ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + text TEXT NOT NULL, + relevant_for TEXT NOT NULL, -- e.g. user_id=123;trip_id=456 + keep_until DATETIME +); + + CREATE TRIGGER IF NOT EXISTS prevent_multiple_roles_same_cluster BEFORE INSERT ON user_role BEGIN diff --git a/src/model/activity.rs b/src/model/activity.rs new file mode 100644 index 0000000..c19d744 --- /dev/null +++ b/src/model/activity.rs @@ -0,0 +1,54 @@ +use std::ops::DerefMut; + +use chrono::NaiveDateTime; +use serde::{Deserialize, Serialize}; +use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; + +#[derive(FromRow, Debug, Serialize, Deserialize, Clone)] +pub struct Activity { + pub id: i64, + pub created_at: NaiveDateTime, + pub text: String, + pub relevant_for: String, + pub keep_until: Option, +} + +impl Activity { + pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option { + sqlx::query_as!( + Self, + "SELECT id, created_at, text, relevant_for, keep_until FROM activity WHERE id like ?", + id + ) + .fetch_one(db) + .await + .ok() + } + pub async fn create_with_tx( + db: &mut Transaction<'_, Sqlite>, + text: &str, + relevant_for: &str, + keep_until: Option, + ) { + sqlx::query!( + "INSERT INTO activity(text, relevant_for, keep_until) VALUES (?, ?, ?)", + text, + relevant_for, + keep_until + ) + .execute(db.deref_mut()) + .await + .unwrap(); + } + + pub async fn create( + db: &SqlitePool, + text: &str, + relevant_for: &str, + keep_until: Option, + ) { + let mut tx = db.begin().await.unwrap(); + Self::create_with_tx(&mut tx, text, relevant_for, keep_until).await; + tx.commit().await.unwrap(); + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 8cc6613..6e531d1 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -14,6 +14,7 @@ use self::{ use boatreservation::{BoatReservation, BoatReservationWithDetails}; use std::collections::HashMap; +pub mod activity; pub mod boat; pub mod boatdamage; pub mod boathouse; diff --git a/src/model/user/basic.rs b/src/model/user/basic.rs index 82916b4..d61ac96 100644 --- a/src/model/user/basic.rs +++ b/src/model/user/basic.rs @@ -1,12 +1,25 @@ // TODO: put back in `src/model/user/mod.rs` once that is cleaned up use super::{AllowedToEditPaymentStatusUser, ManageUserUser, User}; -use crate::model::{family::Family, log::Log, mail::valid_mails, role::Role}; +use crate::model::{activity::Activity, family::Family, log::Log, mail::valid_mails, role::Role}; use chrono::NaiveDate; use rocket::{fs::TempFile, tokio::io::AsyncReadExt}; use sqlx::SqlitePool; impl User { + pub(crate) async fn add_note( + &self, + db: &SqlitePool, + updated_by: &ManageUserUser, + note: &str, + ) -> Result<(), String> { + let note = note.trim(); + + Activity::create(db, note, "relevant_for", None).await; + + Ok(()) + } + pub(crate) async fn update_mail( &self, db: &SqlitePool, diff --git a/src/model/user/mod.rs b/src/model/user/mod.rs index 0c2e06d..2730039 100644 --- a/src/model/user/mod.rs +++ b/src/model/user/mod.rs @@ -951,9 +951,7 @@ impl UserWithMembershipPdf { #[cfg(test)] mod test { - use std::collections::HashMap; - - use crate::{tera::admin::user::UserEditForm, testdb}; + use crate::testdb; use super::User; use sqlx::SqlitePool; @@ -1014,38 +1012,6 @@ mod test { assert_eq!(User::create(&pool, "admin".into()).await, false); } - #[sqlx::test] - fn test_update() { - let pool = testdb!(); - - let user = User::find_by_id(&pool, 1).await.unwrap(); - user.update( - &pool, - UserEditForm { - id: 1, - dob: None, - weight: None, - sex: Some("m".into()), - roles: HashMap::new(), - member_since_date: None, - birthdate: None, - mail: None, - nickname: None, - notes: None, - phone: None, - address: None, - family_id: None, - membership_pdf: None, - }, - ) - .await - .unwrap(); - - let user = User::find_by_id(&pool, 1).await.unwrap(); - - assert_eq!(user.sex, Some("m".into())); - } - #[sqlx::test] fn succ_login_with_test_db() { let pool = testdb!(); diff --git a/src/tera/admin/user.rs b/src/tera/admin/user.rs index 534971e..42056cd 100644 --- a/src/tera/admin/user.rs +++ b/src/tera/admin/user.rs @@ -327,6 +327,34 @@ async fn update_mail( } } +#[derive(FromForm, Debug)] +pub struct AddNoteForm { + note: String, +} + +#[post("/user//add-note", data = "")] +async fn add_note( + db: &State, + data: Form, + admin: ManageUserUser, + id: i32, +) -> Flash { + let Some(user) = User::find_by_id(db, id).await else { + return Flash::error( + Redirect::to("/admin/user"), + format!("User with ID {} does not exist!", id), + ); + }; + + match user.add_note(db, &admin, &data.note).await { + Ok(_) => Flash::success( + Redirect::to(format!("/admin/user/{}", user.id)), + "Notiz hinzugefügt", + ), + Err(e) => Flash::error(Redirect::to(format!("/admin/user/{}", user.id)), e), + } +} + #[derive(FromForm, Debug)] pub struct PhoneUpdateForm { phone: String, @@ -1147,6 +1175,7 @@ pub fn routes() -> Vec { update_family, add_membership_pdf, add_role, + add_note, remove_role, // scheckbook_to_regular, diff --git a/staging-diff.sql b/staging-diff.sql index 199172c..85e39e1 100644 --- a/staging-diff.sql +++ b/staging-diff.sql @@ -25,3 +25,11 @@ UPDATE role SET desc='Es können Logbucheinträge im Nachhinein hinzugefügt wer UPDATE role SET desc='Erlaubt den Login auf der Wordpress-Website um zB Artikel zu schreiben.' WHERE name='allow_website_login'; UPDATE role SET desc='Muss nur den halben Rennruderbeitrag bezahlen (da zB erst in der 2. Jahreshälfte dazugestoßen wurde)' WHERE name='half-rennrudern'; UPDATE role SET desc='Muss keinen Rennruderbeitrag bezahlen, obwohl man in Rennruder-Gruppe ist.' WHERE name='renntrainer'; + +CREATE TABLE activity ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + text TEXT NOT NULL, + relevant_for TEXT NOT NULL, -- e.g. user_id=123;trip_id=456 + keep_until DATETIME -- OPTIONAL field +); diff --git a/templates/admin/user/view.html.tera b/templates/admin/user/view.html.tera index 9415256..bd08efb 100644 --- a/templates/admin/user/view.html.tera +++ b/templates/admin/user/view.html.tera @@ -31,6 +31,11 @@
{{ macros::inputgroup(label='Spitzname', name='nickname', type="text", value=user.nickname, readonly=not allowed_to_edit) }}
+ {% if allowed_to_edit %} +
+ {{ macros::inputgroup(label='Neue Notiz', name='note', type="text") }} +
+ {% endif %} Notizen: to be replaced with activity :-) {% if user.pw and allowed_to_edit %}