diff --git a/db/db-sqlx-postgres/migrations/20220526114806_alter_notifications_table.sql b/db/db-sqlx-postgres/migrations/20220526114806_alter_notifications_table.sql new file mode 100644 index 00000000..78eca31b --- /dev/null +++ b/db/db-sqlx-postgres/migrations/20220526114806_alter_notifications_table.sql @@ -0,0 +1,3 @@ +-- Add migration script here +ALTER TABLE mcaptcha_notifications ALTER COLUMN heading TYPE varchar(100), +ALTER COLUMN heading SET NOT NULL; diff --git a/db/db-sqlx-postgres/src/get_all_unread_notifications.sql b/db/db-sqlx-postgres/src/get_all_unread_notifications.sql new file mode 100644 index 00000000..49fd614e --- /dev/null +++ b/db/db-sqlx-postgres/src/get_all_unread_notifications.sql @@ -0,0 +1,24 @@ +-- gets all unread notifications a user has +SELECT + mcaptcha_notifications.id, + mcaptcha_notifications.heading, + mcaptcha_notifications.message, + mcaptcha_notifications.received, + mcaptcha_users.name +FROM + mcaptcha_notifications +INNER JOIN + mcaptcha_users +ON + mcaptcha_notifications.tx = mcaptcha_users.id +WHERE + mcaptcha_notifications.rx = ( + SELECT + id + FROM + mcaptcha_users + WHERE + name = $1 + ) +AND + mcaptcha_notifications.read IS NULL; diff --git a/db/db-sqlx-postgres/src/lib.rs b/db/db-sqlx-postgres/src/lib.rs index d232f2c5..2db8b2bd 100644 --- a/db/db-sqlx-postgres/src/lib.rs +++ b/db/db-sqlx-postgres/src/lib.rs @@ -637,30 +637,98 @@ impl MCDatabase for Database { .map_err(|e| map_row_not_found_err(e, DBError::TrafficPatternNotFound))?; Ok(()) } + + /// create new notification + async fn create_notification(&self, p: &AddNotification) -> DBResult<()> { + let now = now_unix_time_stamp(); + sqlx::query!( + "INSERT INTO mcaptcha_notifications ( + heading, message, tx, rx, received) + VALUES ( + $1, $2, + (SELECT ID FROM mcaptcha_users WHERE name = $3), + (SELECT ID FROM mcaptcha_users WHERE name = $4), + $5 + );", + p.heading, + p.message, + p.from, + p.to, + now + ) + .execute(&self.pool) + .await + .map_err(map_register_err)?; + + Ok(()) + } + + /// get all unread notifications + async fn get_all_unread_notifications( + &self, + username: &str, + ) -> DBResult> { + let mut inner_notifications = sqlx::query_file_as!( + InnerNotification, + "./src/get_all_unread_notifications.sql", + &username + ) + .fetch_all(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::AccountNotFound))?; + + let mut notifications = Vec::with_capacity(inner_notifications.len()); + + inner_notifications + .drain(0..) + .for_each(|n| notifications.push(n.into())); + + Ok(notifications) + } + + /// mark a notification read + async fn mark_notification_read(&self, username: &str, id: i32) -> DBResult<()> { + sqlx::query_file_as!( + Notification, + "./src/mark_notification_read.sql", + id, + &username + ) + .execute(&self.pool) + .await + .map_err(|e| map_row_not_found_err(e, DBError::NotificationNotFound))?; + + Ok(()) + } } -fn now_unix_time_stamp() -> i64 { - OffsetDateTime::now_utc().unix_timestamp() +fn now_unix_time_stamp() -> OffsetDateTime { + OffsetDateTime::now_utc() } -// -//#[allow(non_snake_case)] -//struct InnerGistComment { -// ID: i64, -// owner: String, -// comment: Option, -// gist_public_id: String, -// created: i64, -//} -// -//impl From for GistComment { -// fn from(g: InnerGistComment) -> Self { -// Self { -// id: g.ID, -// owner: g.owner, -// comment: g.comment.unwrap(), -// gist_public_id: g.gist_public_id, -// created: g.created, -// } -// } -//} +#[derive(Debug, Clone, Default, PartialEq)] +/// Represents notification +pub struct InnerNotification { + /// receiver name of the notification + pub name: Option, + /// heading of the notification + pub heading: Option, + /// message of the notification + pub message: Option, + /// when notification was received + pub received: Option, + /// db assigned ID of the notification + pub id: Option, +} + +impl From for Notification { + fn from(n: InnerNotification) -> Self { + Notification { + name: n.name, + heading: n.heading, + message: n.message, + received: n.received.map(|t| t.unix_timestamp()), + id: n.id, + } + } +} diff --git a/db/db-sqlx-postgres/src/mark_notification_read.sql b/db/db-sqlx-postgres/src/mark_notification_read.sql new file mode 100644 index 00000000..97ff3267 --- /dev/null +++ b/db/db-sqlx-postgres/src/mark_notification_read.sql @@ -0,0 +1,14 @@ +-- mark a notification as read +UPDATE mcaptcha_notifications + SET read = TRUE +WHERE + mcaptcha_notifications.id = $1 +AND + mcaptcha_notifications.rx = ( + SELECT + id + FROM + mcaptcha_users + WHERE + name = $2 + ); diff --git a/db/db-sqlx-postgres/src/tests.rs b/db/db-sqlx-postgres/src/tests.rs index adc111bf..b1cd9cdb 100644 --- a/db/db-sqlx-postgres/src/tests.rs +++ b/db/db-sqlx-postgres/src/tests.rs @@ -33,6 +33,9 @@ async fn everyting_works() { const CAPTCHA_SECRET: &str = "postgrescaptchasecret"; const CAPTCHA_DESCRIPTION: &str = "postgrescaptchadescription"; const CAPTCHA_DURATION: i32 = 30; + // notification config + const HEADING: &str = "testing notifications get db postgres"; + const MESSAGE: &str = "testing notifications get message db postgres"; // easy traffic pattern const TRAFFIC_PATTERN: TrafficPattern = TrafficPattern { @@ -56,6 +59,13 @@ async fn everyting_works() { }, ]; + const add_notification: AddNotification = AddNotification { + from: NAME, + to: NAME, + message: MESSAGE, + heading: HEADING, + }; + let url = env::var("POSTGRES_DATABASE_URL").unwrap(); let pool_options = PgPoolOptions::new().max_connections(2); let connection_options = ConnectionOptions::Fresh(Fresh { pool_options, url }); @@ -74,5 +84,5 @@ async fn everyting_works() { key: CAPTCHA_SECRET, description: CAPTCHA_DESCRIPTION, }; - database_works(&db, &p, &c, &LEVELS, &TRAFFIC_PATTERN).await; + database_works(&db, &p, &c, &LEVELS, &TRAFFIC_PATTERN, &add_notification).await; }