From 26ad0ba80ab61fbea78c9adfe584138ad4a24983 Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 17 Nov 2023 10:30:30 +0100 Subject: [PATCH 01/13] notifications --- src/model/notification.rs | 32 +++++++++++++++++++++----------- staging-diff.sql | 9 +++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/model/notification.rs b/src/model/notification.rs index d345f74..8ba4741 100644 --- a/src/model/notification.rs +++ b/src/model/notification.rs @@ -8,25 +8,35 @@ pub struct Notification { pub user_id: i64, pub message: String, pub read_at: NaiveDateTime, + pub created_at: NaiveDateTime, pub category: String, + pub link: Option, } impl Notification { - //pub async fn create(db: &SqlitePool, msg: String) -> bool { - // sqlx::query!("INSERT INTO log(msg) VALUES (?)", msg,) - // .execute(db) - // .await - // .is_ok() - //} + pub async fn create( + db: &SqlitePool, + user: &User, + message: &str, + category: &str, + link: Option<&str>, + ) { + sqlx::query!( + "INSERT INTO notification(user_id, message, category, link) VALUES (?, ?, ?, ?)", + user.id, + message, + category, + link + ) + .execute(db) + .await + .unwrap() + } async fn for_user(db: &SqlitePool, user: &User) -> Vec { sqlx::query_as!( Log, - " -SELECT id, user_id, message, read_at, category -FROM notification -WHERE user_id = {} - ", + "SELECT * FROM notification WHERE user_id = {}", user.id ) .fetch_all(db) diff --git a/staging-diff.sql b/staging-diff.sql index e69de29..a87f58f 100644 --- a/staging-diff.sql +++ b/staging-diff.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS "notification" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "user_id" INTEGER NOT NULL REFERENCES user(id), + "message" TEXT NOT NULL, + "read_at" DATETIME, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "category" TEXT NOT NULL, + "link" TEXT +); -- 2.45.2 From ab64583efc43cfc98e0b85c502f2a59bd986c5b8 Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 17 Nov 2023 10:30:30 +0100 Subject: [PATCH 02/13] notifications --- src/model/notification.rs | 32 +++++++++++++++++++++----------- staging-diff.sql | 9 +++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/model/notification.rs b/src/model/notification.rs index d345f74..8ba4741 100644 --- a/src/model/notification.rs +++ b/src/model/notification.rs @@ -8,25 +8,35 @@ pub struct Notification { pub user_id: i64, pub message: String, pub read_at: NaiveDateTime, + pub created_at: NaiveDateTime, pub category: String, + pub link: Option, } impl Notification { - //pub async fn create(db: &SqlitePool, msg: String) -> bool { - // sqlx::query!("INSERT INTO log(msg) VALUES (?)", msg,) - // .execute(db) - // .await - // .is_ok() - //} + pub async fn create( + db: &SqlitePool, + user: &User, + message: &str, + category: &str, + link: Option<&str>, + ) { + sqlx::query!( + "INSERT INTO notification(user_id, message, category, link) VALUES (?, ?, ?, ?)", + user.id, + message, + category, + link + ) + .execute(db) + .await + .unwrap() + } async fn for_user(db: &SqlitePool, user: &User) -> Vec { sqlx::query_as!( Log, - " -SELECT id, user_id, message, read_at, category -FROM notification -WHERE user_id = {} - ", + "SELECT * FROM notification WHERE user_id = {}", user.id ) .fetch_all(db) diff --git a/staging-diff.sql b/staging-diff.sql index e69de29..a87f58f 100644 --- a/staging-diff.sql +++ b/staging-diff.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS "notification" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "user_id" INTEGER NOT NULL REFERENCES user(id), + "message" TEXT NOT NULL, + "read_at" DATETIME, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "category" TEXT NOT NULL, + "link" TEXT +); -- 2.45.2 From f4cdd0ae283d8afe5e577304c62a10bd45a89ac1 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 10 Jan 2024 14:20:30 +0100 Subject: [PATCH 03/13] add table --- migration.sql | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/migration.sql b/migration.sql index 2cc86d4..a727b31 100644 --- a/migration.sql +++ b/migration.sql @@ -135,3 +135,13 @@ CREATE TABLE IF NOT EXISTS "boat_damage" ( "verified_at" datetime, "lock_boat" boolean not null default false -- if true: noone can use the boat ); + +CREATE TABLE IF NOT EXISTS "notification" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "user_id" INTEGER NOT NULL REFERENCES user(id), + "message" TEXT NOT NULL, + "read_at" DATETIME, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "category" TEXT NOT NULL, + "link" TEXT +); -- 2.45.2 From 28acee3085a14cc79ae743cb08481af5a95938bb Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 17 Nov 2023 10:30:30 +0100 Subject: [PATCH 04/13] notifications --- src/model/notification.rs | 32 +++++++++++++++++++++----------- staging-diff.sql | 9 +++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/model/notification.rs b/src/model/notification.rs index d345f74..8ba4741 100644 --- a/src/model/notification.rs +++ b/src/model/notification.rs @@ -8,25 +8,35 @@ pub struct Notification { pub user_id: i64, pub message: String, pub read_at: NaiveDateTime, + pub created_at: NaiveDateTime, pub category: String, + pub link: Option, } impl Notification { - //pub async fn create(db: &SqlitePool, msg: String) -> bool { - // sqlx::query!("INSERT INTO log(msg) VALUES (?)", msg,) - // .execute(db) - // .await - // .is_ok() - //} + pub async fn create( + db: &SqlitePool, + user: &User, + message: &str, + category: &str, + link: Option<&str>, + ) { + sqlx::query!( + "INSERT INTO notification(user_id, message, category, link) VALUES (?, ?, ?, ?)", + user.id, + message, + category, + link + ) + .execute(db) + .await + .unwrap() + } async fn for_user(db: &SqlitePool, user: &User) -> Vec { sqlx::query_as!( Log, - " -SELECT id, user_id, message, read_at, category -FROM notification -WHERE user_id = {} - ", + "SELECT * FROM notification WHERE user_id = {}", user.id ) .fetch_all(db) diff --git a/staging-diff.sql b/staging-diff.sql index e69de29..a87f58f 100644 --- a/staging-diff.sql +++ b/staging-diff.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS "notification" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "user_id" INTEGER NOT NULL REFERENCES user(id), + "message" TEXT NOT NULL, + "read_at" DATETIME, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "category" TEXT NOT NULL, + "link" TEXT +); -- 2.45.2 From 09fffa1830edf43d91f69c977c4f973ae660fbde Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 10 Jan 2024 14:20:30 +0100 Subject: [PATCH 05/13] add table --- migration.sql | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/migration.sql b/migration.sql index 2bbf1c9..d271b5a 100644 --- a/migration.sql +++ b/migration.sql @@ -140,3 +140,13 @@ CREATE TABLE IF NOT EXISTS "boat_damage" ( "verified_at" datetime, "lock_boat" boolean not null default false -- if true: noone can use the boat ); + +CREATE TABLE IF NOT EXISTS "notification" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "user_id" INTEGER NOT NULL REFERENCES user(id), + "message" TEXT NOT NULL, + "read_at" DATETIME, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "category" TEXT NOT NULL, + "link" TEXT +); -- 2.45.2 From 5af1860607df46f6de0e033619d306c9b0c1ed02 Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 17 Nov 2023 10:30:30 +0100 Subject: [PATCH 06/13] notifications --- src/model/notification.rs | 32 +++++++++++++++++++++----------- staging-diff.sql | 9 +++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/model/notification.rs b/src/model/notification.rs index 5c6c26d..ac480f6 100644 --- a/src/model/notification.rs +++ b/src/model/notification.rs @@ -8,25 +8,35 @@ pub struct Notification { pub user_id: i64, pub message: String, pub read_at: NaiveDateTime, + pub created_at: NaiveDateTime, pub category: String, + pub link: Option, } impl Notification { - //pub async fn create(db: &SqlitePool, msg: String) -> bool { - // sqlx::query!("INSERT INTO log(msg) VALUES (?)", msg,) - // .execute(db) - // .await - // .is_ok() - //} + pub async fn create( + db: &SqlitePool, + user: &User, + message: &str, + category: &str, + link: Option<&str>, + ) { + sqlx::query!( + "INSERT INTO notification(user_id, message, category, link) VALUES (?, ?, ?, ?)", + user.id, + message, + category, + link + ) + .execute(db) + .await + .unwrap() + } async fn for_user(db: &SqlitePool, user: &User) -> Vec { sqlx::query_as!( Log, - " -SELECT id, user_id, message, read_at, category -FROM notification -WHERE user_id = {} - ", + "SELECT * FROM notification WHERE user_id = {}", user.id ) .fetch_all(db) diff --git a/staging-diff.sql b/staging-diff.sql index e69de29..a87f58f 100644 --- a/staging-diff.sql +++ b/staging-diff.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS "notification" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "user_id" INTEGER NOT NULL REFERENCES user(id), + "message" TEXT NOT NULL, + "read_at" DATETIME, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "category" TEXT NOT NULL, + "link" TEXT +); -- 2.45.2 From aef5748f5fa8cdbfa127de7f7ed9e155aab4ce27 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 10 Jan 2024 14:20:30 +0100 Subject: [PATCH 07/13] add table --- migration.sql | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/migration.sql b/migration.sql index 2df76ef..74332d1 100644 --- a/migration.sql +++ b/migration.sql @@ -150,3 +150,12 @@ CREATE TABLE IF NOT EXISTS "boathouse" ( CONSTRAINT unq UNIQUE (aisle, side, level) -- only 1 boat allowed to rest at each space ); +CREATE TABLE IF NOT EXISTS "notification" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "user_id" INTEGER NOT NULL REFERENCES user(id), + "message" TEXT NOT NULL, + "read_at" DATETIME, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "category" TEXT NOT NULL, + "link" TEXT +); -- 2.45.2 From be50e6584694d223170606ae180ad43ab7adb5b8 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 20 Mar 2024 15:56:34 +0100 Subject: [PATCH 08/13] add notifications; fixes #127 --- migration.sql | 2 +- seeds.sql | 1 + src/model/logbook.rs | 19 +++++++++++++- src/model/mod.rs | 1 + src/model/notification.rs | 52 +++++++++++++++++++++++++++++++-------- src/model/trip.rs | 21 ++++++++++++++-- src/model/usertrip.rs | 22 ++++++++++++++++- src/tera/mod.rs | 8 +++++- src/tera/notification.rs | 32 ++++++++++++++++++++++++ staging-diff.sql | 2 +- templates/index.html.tera | 51 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 194 insertions(+), 17 deletions(-) create mode 100644 src/tera/notification.rs diff --git a/migration.sql b/migration.sql index b265045..cdc2a37 100644 --- a/migration.sql +++ b/migration.sql @@ -155,7 +155,7 @@ CREATE TABLE IF NOT EXISTS "notification" ( "user_id" INTEGER NOT NULL REFERENCES user(id), "message" TEXT NOT NULL, "read_at" DATETIME, - "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, "category" TEXT NOT NULL, "link" TEXT ); diff --git a/seeds.sql b/seeds.sql index 783f219..188ac10 100644 --- a/seeds.sql +++ b/seeds.sql @@ -61,3 +61,4 @@ INSERT INTO "logbook" (boat_id, shipmaster, steering_person, shipmaster_only_ste INSERT INTO "rower" (logbook_id, rower_id) VALUES(3,3); INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at) VALUES(4,'Dolle bei Position 2 fehlt', 5, '2142-12-24 15:02'); INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at, lock_boat) VALUES(5, 'TOHT', 5, '2142-12-24 15:02', 1); +INSERT INTO "notification" (user_id, message, category) VALUES (1, 'This is a test notification', 'test-cat'); diff --git a/src/model/logbook.rs b/src/model/logbook.rs index d3c6e92..b2b6939 100644 --- a/src/model/logbook.rs +++ b/src/model/logbook.rs @@ -5,7 +5,7 @@ use rocket::FromForm; use serde::Serialize; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; -use super::{boat::Boat, log::Log, rower::Rower, user::User}; +use super::{boat::Boat, log::Log, notification::Notification, rower::Rower, user::User}; #[derive(FromRow, Serialize, Clone, Debug)] pub struct Logbook { @@ -525,6 +525,23 @@ ORDER BY departure DESC Rower::create(db, self.id, *rower) .await .map_err(|e| LogbookUpdateError::RowerCreateError(*rower, e.to_string()))?; + + let user = User::find_by_id_tx(db, *rower as i32).await.unwrap(); + Notification::create_with_tx( + db, + &user, + &format!( + "Neuer Logbucheintrag: Ausfahrt am {}.{}.{} nach {} ({} km)", + dep.day(), + dep.month(), + dep.year(), + log.destination, + log.distance_in_km + ), + "Neuer Logbucheintrag", + None, + ) + .await; } sqlx::query!( diff --git a/src/model/mod.rs b/src/model/mod.rs index 6423379..b1e0416 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -16,6 +16,7 @@ pub mod log; pub mod logbook; pub mod logtype; pub mod mail; +pub mod notification; pub mod planned_event; pub mod role; pub mod rower; diff --git a/src/model/notification.rs b/src/model/notification.rs index ac480f6..8cfc6a1 100644 --- a/src/model/notification.rs +++ b/src/model/notification.rs @@ -1,21 +1,31 @@ -use chrono::{DateTime, Local, NaiveDateTime, TimeZone}; +use std::ops::DerefMut; + +use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; -use sqlx::{FromRow, SqlitePool}; +use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; + +use super::user::User; #[derive(FromRow, Debug, Serialize, Deserialize)] pub struct Notification { pub id: i64, pub user_id: i64, pub message: String, - pub read_at: NaiveDateTime, + pub read_at: Option, pub created_at: NaiveDateTime, pub category: String, pub link: Option, } impl Notification { - pub async fn create( - db: &SqlitePool, + pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option { + sqlx::query_as!(Self, "SELECT * FROM notification WHERE id like ?", id) + .fetch_one(db) + .await + .ok() + } + pub async fn create_with_tx( + db: &mut Transaction<'_, Sqlite>, user: &User, message: &str, category: &str, @@ -28,19 +38,41 @@ impl Notification { category, link ) - .execute(db) + .execute(db.deref_mut()) .await - .unwrap() + .unwrap(); } - async fn for_user(db: &SqlitePool, user: &User) -> Vec { + pub async fn create( + db: &SqlitePool, + user: &User, + message: &str, + category: &str, + link: Option<&str>, + ) { + let mut tx = db.begin().await.unwrap(); + Self::create_with_tx(&mut tx, user, message, category, link).await; + tx.commit().await.unwrap(); + } + + pub async fn for_user(db: &SqlitePool, user: &User) -> Vec { sqlx::query_as!( - Log, - "SELECT * FROM notification WHERE user_id = {}", + Self, + "SELECT * FROM notification WHERE user_id = ?", user.id ) .fetch_all(db) .await .unwrap() } + + pub async fn mark_read(self, db: &SqlitePool) { + sqlx::query!( + "UPDATE notification SET read_at=CURRENT_TIMESTAMP WHERE id=?", + self.id + ) + .execute(db) + .await + .unwrap(); + } } diff --git a/src/model/trip.rs b/src/model/trip.rs index 9141a35..b7d3d5e 100644 --- a/src/model/trip.rs +++ b/src/model/trip.rs @@ -12,12 +12,12 @@ use super::{ #[derive(Serialize, Clone, Debug)] pub struct Trip { id: i64, - cox_id: i64, + pub cox_id: i64, cox_name: String, trip_details_id: Option, planned_starting_time: String, pub max_people: i64, - day: String, + pub day: String, pub notes: Option, pub allow_guests: bool, trip_type_id: Option, @@ -45,6 +45,23 @@ impl Trip { .await; } + pub async fn find_by_trip_details(db: &SqlitePool, tripdetails_id: i64) -> Option { + sqlx::query_as!( + Self, + " +SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, trip_details.notes, allow_guests, trip_type_id, always_show, is_locked +FROM trip +INNER JOIN trip_details ON trip.trip_details_id = trip_details.id +INNER JOIN user ON trip.cox_id = user.id +WHERE trip_details.id=? + ", + tripdetails_id + ) + .fetch_one(db) + .await + .ok() + } + pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option { sqlx::query_as!( Self, diff --git a/src/model/usertrip.rs b/src/model/usertrip.rs index f727bf8..01cfac4 100644 --- a/src/model/usertrip.rs +++ b/src/model/usertrip.rs @@ -1,6 +1,6 @@ use sqlx::SqlitePool; -use super::{tripdetails::TripDetails, user::User}; +use super::{notification::Notification, trip::Trip, tripdetails::TripDetails, user::User}; use crate::model::tripdetails::{Action, CoxAtTrip::Yes}; pub struct UserTrip {} @@ -27,6 +27,7 @@ impl UserTrip { //TODO: Check if user sees the event (otherwise she could forge trip_details_id) let is_cox = trip_details.user_is_cox(db, user).await; + let mut name_newly_registered_person = String::new(); if user_note.is_none() { if let Yes(action) = is_cox { match action { @@ -47,6 +48,8 @@ impl UserTrip { .execute(db) .await .unwrap(); + + name_newly_registered_person = user.name.clone(); } else { if !trip_details.user_allowed_to_change(db, user).await { return Err(UserTripError::NotAllowedToAddGuest); @@ -59,6 +62,23 @@ impl UserTrip { .execute(db) .await .unwrap(); + + name_newly_registered_person = user_note.unwrap(); + } + + if let Some(trip) = Trip::find_by_trip_details(db, trip_details.id).await { + let cox = User::find_by_id(db, trip.cox_id as i32).await.unwrap(); + Notification::create( + db, + &cox, + &format!( + "{} hat sich für deine Ausfahrt am {} registriert", + name_newly_registered_person, trip.day + ), + "Registrierung bei Ausfahrt", + None, + ) + .await; } Ok(()) diff --git a/src/tera/mod.rs b/src/tera/mod.rs index ae52b07..0f1faf0 100644 --- a/src/tera/mod.rs +++ b/src/tera/mod.rs @@ -17,7 +17,10 @@ use serde::Deserialize; use sqlx::SqlitePool; use tera::Context; -use crate::model::user::{User, UserWithRoles}; +use crate::model::{ + notification::Notification, + user::{User, UserWithRoles}, +}; pub(crate) mod admin; mod auth; @@ -27,6 +30,7 @@ mod cox; mod ergo; mod log; mod misc; +mod notification; mod planned; mod stat; @@ -43,6 +47,7 @@ async fn index(db: &State, user: User, flash: Option) -> Rocket { .mount("/log", log::routes()) .mount("/planned", planned::routes()) .mount("/ergo", ergo::routes()) + .mount("/notification", notification::routes()) .mount("/stat", stat::routes()) .mount("/boatdamage", boatdamage::routes()) .mount("/cox", cox::routes()) diff --git a/src/tera/notification.rs b/src/tera/notification.rs new file mode 100644 index 0000000..da59ccc --- /dev/null +++ b/src/tera/notification.rs @@ -0,0 +1,32 @@ +use rocket::{ + get, + response::{Flash, Redirect}, + routes, Route, State, +}; +use sqlx::SqlitePool; + +use crate::model::{notification::Notification, user::User}; + +#[get("//read")] +async fn mark_read(db: &State, user: User, notification_id: i64) -> Flash { + let Some(notification) = Notification::find_by_id(db, notification_id).await else { + return Flash::error( + Redirect::to("/"), + format!("Nachricht mit ID {notification_id} nicht gefunden."), + ); + }; + + if notification.user_id == user.id { + notification.mark_read(db).await; + Flash::success(Redirect::to("/"), "Nachricht als gelesen markiert") + } else { + Flash::success( + Redirect::to("/"), + "Du kannst fremde Nachrichten nicht als gelesen markieren.", + ) + } +} + +pub fn routes() -> Vec { + routes![mark_read] +} diff --git a/staging-diff.sql b/staging-diff.sql index a87f58f..dd07ecf 100644 --- a/staging-diff.sql +++ b/staging-diff.sql @@ -3,7 +3,7 @@ CREATE TABLE IF NOT EXISTS "notification" ( "user_id" INTEGER NOT NULL REFERENCES user(id), "message" TEXT NOT NULL, "read_at" DATETIME, - "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP, + "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, "category" TEXT NOT NULL, "link" TEXT ); diff --git a/templates/index.html.tera b/templates/index.html.tera index e464f26..4e86fe4 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -3,6 +3,57 @@ {% block content %}

Ruderassistent

+
+ +