From a14a35f9bda1604fe239fd58a904a61aa270f4a9 Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Tue, 26 Nov 2024 14:52:36 +0800 Subject: [PATCH 01/16] feat(db): add notification inbox tables - Added `notifications` table to store notification messages. - Added `readed_notifications` table to track which notifications have been read by users. --- .../0039_add-notification-inbox.down.sql | 1 + .../0039_add-notification-inbox.up.sql | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 ee/tabby-db/migrations/0039_add-notification-inbox.down.sql create mode 100644 ee/tabby-db/migrations/0039_add-notification-inbox.up.sql diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.down.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.down.sql new file mode 100644 index 000000000000..d2f607c5b8bd --- /dev/null +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql new file mode 100644 index 000000000000..ec70200a08e1 --- /dev/null +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql @@ -0,0 +1,20 @@ +CREATE TABLE notifications ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + + created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), + updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), + + -- enum of ADMIN, ALL_USERS + kind: TEXT NOT NULL, + + message: TEXT NOT NULL, +) + +CREATE TABLE readed_notifications ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + notification_id INTEGER NOT NULL, + + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE, +) \ No newline at end of file From 19631d4247963ff4b743af42745538adb38270b6 Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Tue, 26 Nov 2024 14:58:02 +0800 Subject: [PATCH 02/16] update --- ee/tabby-db/migrations/0039_add-notification-inbox.down.sql | 3 ++- ee/tabby-db/migrations/0039_add-notification-inbox.up.sql | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.down.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.down.sql index d2f607c5b8bd..53c4478d75ae 100644 --- a/ee/tabby-db/migrations/0039_add-notification-inbox.down.sql +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.down.sql @@ -1 +1,2 @@ --- Add down migration script here +DROP TABLE notifications; +DROP TABLE readed_notifications; \ No newline at end of file diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql index ec70200a08e1..4555055cd905 100644 --- a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql @@ -7,7 +7,7 @@ CREATE TABLE notifications ( -- enum of ADMIN, ALL_USERS kind: TEXT NOT NULL, - message: TEXT NOT NULL, + content: TEXT NOT NULL, ) CREATE TABLE readed_notifications ( From 039c43bc7f6d92d1abed88554a6ed72ebc26e929 Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Thu, 5 Dec 2024 17:45:30 +0800 Subject: [PATCH 03/16] update --- ee/tabby-db/migrations/0039_add-notification-inbox.up.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql index 4555055cd905..b6c20cc81482 100644 --- a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql @@ -5,9 +5,10 @@ CREATE TABLE notifications ( updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), -- enum of ADMIN, ALL_USERS - kind: TEXT NOT NULL, + kind TEXT NOT NULL, - content: TEXT NOT NULL, + -- content of notification, in markdown format. + content TEXT NOT NULL, ) CREATE TABLE readed_notifications ( From e203bec1f4b94c54d2e92d29d68fbfc81c123191 Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Thu, 5 Dec 2024 17:49:24 +0800 Subject: [PATCH 04/16] update --- .../0039_add-notification-inbox.up.sql | 6 +- ee/tabby-db/schema.sqlite | Bin 204800 -> 217088 bytes ee/tabby-db/schema/schema.sql | 16 + ee/tabby-db/schema/schema.svg | 892 ++++++++++-------- 4 files changed, 493 insertions(+), 421 deletions(-) diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql index b6c20cc81482..78fa9d314e41 100644 --- a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql @@ -8,8 +8,8 @@ CREATE TABLE notifications ( kind TEXT NOT NULL, -- content of notification, in markdown format. - content TEXT NOT NULL, -) + content TEXT NOT NULL +); CREATE TABLE readed_notifications ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, @@ -17,5 +17,5 @@ CREATE TABLE readed_notifications ( notification_id INTEGER NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE, + FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE ) \ No newline at end of file diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite index 868e87017b2ccdf4c788857351f7ecac50ae693e..6ca1d475f572958ed885ca690c7d966b56d29472 100644 GIT binary patch delta 946 zcmZoTz|*jRcY?Ga9|Hq}DGy!?{Pw9MqhlFa-( z-ORkC{0bujBNJUiBV7Yi1p`Yf6LTvQlid-~d;E32F&ODfS|$5B@BVmXZh6=~>3^yn zdM<(@ed?XOLRAr#Pdk&A7W<1Zm}Q9GpR;+jU?d;28o%6Rb_H)He!0zp2B-MgSeY3a z)zz9+^tY?%Gcp;nF*z|lV2qu(Fm8KS0%I8C^x%A^wcC4>7)$;t10BxEcASA-lkGU~ zZLZ&(!JHBtciC67moS+!-eAaLNaE0#xX{j<&4mr9l7XQq+nHTlU7fL2v?MVpCl%RS z#V8zO=O9<|^c;DLA_Ldiv=oIQsa+ zhX%U_1#>AVWM}53Oc%^%5_SiwOU}~B_8FTK6kX>y0!z3or>8nzi btl6Pb)BRHzMd6&R6h;|WF3n*VSz$~7ii{~d delta 297 zcmZozz}s+uXM(gKF9QRE5fH;b)I=R)ab5^pur`+W)=PID*B8} zhHOBk42-c87shSxN?;6QoPHsfaqafrB*v1ziX0jYyo|~W{9E~hdG)ybIF@l_a~QDg zXaC3|!&bzo%)B3HI3wfsgI>&YnAoD7*u?WaCo^(LO#hb1WWQZ5m1!m8^!NEp{L`0s zFbhua_hELJzS*DIcY3TZv+wrR=}bn9Y^HW>;-1Q2O#xAiEYs_Knc1e#NoV5c0J;i< c8MfceU{YqE{veTY?IHt(MGgYX7BDpc06zarfB*mh diff --git a/ee/tabby-db/schema/schema.sql b/ee/tabby-db/schema/schema.sql index c972bc2a081c..e7d91cd096e9 100644 --- a/ee/tabby-db/schema/schema.sql +++ b/ee/tabby-db/schema/schema.sql @@ -223,3 +223,19 @@ FOREIGN KEY(user_group_id) REFERENCES user_groups(id) ON DELETE CASCADE, -- access_policy is unique per source_id and user_group_id CONSTRAINT idx_unique_source_id_user_group_id UNIQUE(source_id, user_group_id) ); +CREATE TABLE notifications( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), + updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), + -- enum of ADMIN, ALL_USERS + kind TEXT NOT NULL, + -- content of notification, in markdown format. + content TEXT NOT NULL +); +CREATE TABLE readed_notifications( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + notification_id INTEGER NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(notification_id) REFERENCES notifications(id) ON DELETE CASCADE +); diff --git a/ee/tabby-db/schema/schema.svg b/ee/tabby-db/schema/schema.svg index 67189b5de0f0..ff24cf05e164 100644 --- a/ee/tabby-db/schema/schema.svg +++ b/ee/tabby-db/schema/schema.svg @@ -1,14 +1,14 @@ - - - + + structs - + _sqlx_migrations @@ -122,181 +122,207 @@ invitations - -invitations - -🔑 - -id - -  - -email - -  - -code - -  - -created_at + +invitations + +🔑 + +id + +  + +email + +  + +code + +  + +created_at job_runs - -job_runs - -🔑 - -id - -  - -job - -  - -start_ts - -  - -end_ts - -  - -exit_code - -  - -stdout - -  - -stderr - -  - -created_at - -  - -updated_at - -  - -command - -  - -started_at + +job_runs + +🔑 + +id + +  + +job + +  + +start_ts + +  + +end_ts + +  + +exit_code + +  + +stdout + +  + +stderr + +  + +created_at + +  + +updated_at + +  + +command + +  + +started_at + + + +notifications + +notifications + +🔑 + +id + +  + +created_at + +  + +updated_at + +  + +kind + +  + +content - + oauth_credential - -oauth_credential - -🔑 - -id - -  - -provider - -  - -client_id - -  - -client_secret - -  - -created_at - -  - -updated_at + +oauth_credential + +🔑 + +id + +  + +provider + +  + +client_id + +  + +client_secret + +  + +created_at + +  + +updated_at - + password_reset - -password_reset - -🔑 - -id - -  - -user_id - -  - -code - -  - -created_at + +password_reset + +🔑 + +id + +  + +user_id + +  + +code + +  + +created_at - + users - -users - -🔑 - -id - -  - -email - -  - -is_admin - -  - -created_at - -  - -updated_at - -  - -auth_token - -  - -active - -  - -password_encrypted - -  - -avatar - -  - -name + +users + +🔑 + +id + +  + +email + +  + +is_admin + +  + +created_at + +  + +updated_at + +  + +auth_token + +  + +active + +  + +password_encrypted + +  + +avatar + +  + +name password_reset:e->users:w - - + + - + provided_repositories provided_repositories @@ -334,111 +360,141 @@ updated_at - + provided_repositories:e->integrations:w - - + + + + + +readed_notifications + +readed_notifications + +🔑 + +id + +  + +user_id + +  + +notification_id + + + +readed_notifications:e->notifications:w + + + + + +readed_notifications:e->users:w + + - + refresh_tokens - -refresh_tokens - -🔑 - -id - -  - -user_id - -  - -token - -  - -expires_at - -  - -created_at + +refresh_tokens + +🔑 + +id + +  + +user_id + +  + +token + +  + +expires_at + +  + +created_at refresh_tokens:e->users:w - - + + - + registration_token - -registration_token - -🔑 - -id - -  - -token - -  - -created_at - -  - -updated_at + +registration_token + +🔑 + +id + +  + +token + +  + +created_at + +  + +updated_at - + repositories - -repositories - -🔑 - -id - -  - -name - -  - -git_url + +repositories + +🔑 + +id + +  + +name + +  + +git_url - + server_setting - -server_setting - -🔑 - -id - -  - -security_allowed_register_domain_list - -  - -security_disable_client_side_telemetry - -  - -network_external_url - -  - -billing_enterprise_license + +server_setting + +🔑 + +id + +  + +security_allowed_register_domain_list + +  + +security_disable_client_side_telemetry + +  + +network_external_url + +  + +billing_enterprise_license - + source_id_read_access_policies source_id_read_access_policies @@ -464,7 +520,7 @@ updated_at - + user_groups user_groups @@ -488,175 +544,175 @@ source_id_read_access_policies:e->user_groups:w - - + + - + thread_messages - -thread_messages - -🔑 - -id - -  - -thread_id - -  - -role - -  - -content - -  - -code_attachments - -  - -client_code_attachments - -  - -doc_attachments - -  - -created_at - -  - -updated_at + +thread_messages + +🔑 + +id + +  + +thread_id + +  + +role + +  + +content + +  + +code_attachments + +  + +client_code_attachments + +  + +doc_attachments + +  + +created_at + +  + +updated_at - + threads - -threads - -🔑 - -id - -  - -is_ephemeral - -  - -user_id - -  - -created_at - -  - -updated_at - -  - -relevant_questions + +threads + +🔑 + +id + +  + +is_ephemeral + +  + +user_id + +  + +created_at + +  + +updated_at + +  + +relevant_questions thread_messages:e->threads:w - - + + threads:e->users:w - - + + - + user_completions - -user_completions - -🔑 - -id - -  - -user_id - -  - -completion_id - -  - -language - -  - -views - -  - -selects - -  - -dismisses - -  - -created_at - -  - -updated_at + +user_completions + +🔑 + +id + +  + +user_id + +  + +completion_id + +  + +language + +  + +views + +  + +selects + +  + +dismisses + +  + +created_at + +  + +updated_at user_completions:e->users:w - - + + - + user_events - -user_events - -🔑 - -id - -  - -user_id - -  - -kind - -  - -created_at - -  - -payload + +user_events + +🔑 + +id + +  + +user_id + +  + +kind + +  + +created_at + +  + +payload user_events:e->users:w - - + + - + user_group_memberships user_group_memberships @@ -688,44 +744,44 @@ user_group_memberships:e->user_groups:w - - + + user_group_memberships:e->users:w - - + + - + web_documents - -web_documents - -🔑 - -id - -  - -name - -  - -url - -  - -is_preset - -  - -created_at - -  - -updated_at + +web_documents + +🔑 + +id + +  + +name + +  + +url + +  + +is_preset + +  + +created_at + +  + +updated_at From 81a3ea43829539455943b88bd840fff9a79b78d9 Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Thu, 5 Dec 2024 17:50:14 +0800 Subject: [PATCH 05/16] update --- .../0039_add-notification-inbox.up.sql | 3 + ee/tabby-db/schema.sqlite | Bin 217088 -> 217088 bytes ee/tabby-db/schema/schema.sql | 2 + ee/tabby-db/schema/schema.svg | 64 ++++++++++-------- 4 files changed, 41 insertions(+), 28 deletions(-) diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql index 78fa9d314e41..f1bc96515a7a 100644 --- a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql @@ -16,6 +16,9 @@ CREATE TABLE readed_notifications ( user_id INTEGER NOT NULL, notification_id INTEGER NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), + updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE ) \ No newline at end of file diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite index 6ca1d475f572958ed885ca690c7d966b56d29472..5ec9bfaa787fe91aff60899e92e5007de70d95e0 100644 GIT binary patch delta 207 zcmZozz}v8ZcY?H_00RSqDGWKm|7oSdmXMO$2WH6&4~*9>|D(OF6`o_ri^XQ z(-*lj>oOWozu?Y1W4c`iQ|a^t>5SaddwrPMr>{w4lAiuPpNW6^5)bBTMw9Idp3D!} E0Az1P_5c6? delta 182 zcmZozz}v8ZcY?Ga9|Hq}DG)H`{Fsv<0(b|x(?_7`C=%MiUkr+Kwt`)WZ(AZFUWT97$` zZ!>qpSNUlU%xru!7 notifications - -notifications - -🔑 - -id + +notifications + +🔑 + +id + +  + +created_at + +  + +updated_at   -created_at +kind   -updated_at - -  - -kind - -  - -content +content @@ -368,32 +368,40 @@ readed_notifications - -readed_notifications - -🔑 - -id + +readed_notifications + +🔑 + +id + +  + +user_id + +  + +notification_id   -user_id +created_at   -notification_id +updated_at readed_notifications:e->notifications:w - - + + readed_notifications:e->users:w - - + + From 754dcec9a9ed048b2a7948d1c1bf783ecd53239a Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Thu, 5 Dec 2024 17:54:18 +0800 Subject: [PATCH 06/16] update --- .../0039_add-notification-inbox.up.sql | 2 +- ee/tabby-db/schema.sqlite | Bin 217088 -> 217088 bytes ee/tabby-db/schema/schema.sql | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql index f1bc96515a7a..fca4efefce08 100644 --- a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql @@ -5,7 +5,7 @@ CREATE TABLE notifications ( updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), -- enum of ADMIN, ALL_USERS - kind TEXT NOT NULL, + kind VARCHAR(255) NOT NULL DEFAULT 'admin', -- content of notification, in markdown format. content TEXT NOT NULL diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite index 5ec9bfaa787fe91aff60899e92e5007de70d95e0..48a17a01063199e778c8ca35636c6f216d5f2b0e 100644 GIT binary patch delta 226 zcmZozz}v8ZcY?H_5Ca2)DGXJJzs?Ap^h(lqekWu1ot>}$TC?70oz*vIg){e+AI+-;+gA%R0x{F})q>0k ze2cjo1Q?As3mUxVpBTV8&4HPXkDGya6>lupL9P^zd+gks6BWL*O;>hjHfJ>19_`LN zn~|%T*_mBjU7fMjb($x${PZYyX1?jVp3HY9Ut$!U9v95W#g>?oo0+G+y(^K)mUa63 Xd?xE=aHj6lq^eYGHS z0w1HvW Date: Thu, 5 Dec 2024 18:23:27 +0800 Subject: [PATCH 07/16] add notifications dao --- .../0039_add-notification-inbox.up.sql | 2 +- ee/tabby-db/src/lib.rs | 1 + ee/tabby-db/src/notifications.rs | 82 +++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 ee/tabby-db/src/notifications.rs diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql index fca4efefce08..811d916d8a2a 100644 --- a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql @@ -4,7 +4,7 @@ CREATE TABLE notifications ( created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), - -- enum of ADMIN, ALL_USERS + -- enum of admin, all_user kind VARCHAR(255) NOT NULL DEFAULT 'admin', -- content of notification, in markdown format. diff --git a/ee/tabby-db/src/lib.rs b/ee/tabby-db/src/lib.rs index 31aaaf69d88b..1be2179bf347 100644 --- a/ee/tabby-db/src/lib.rs +++ b/ee/tabby-db/src/lib.rs @@ -45,6 +45,7 @@ mod user_events; mod user_groups; mod users; mod web_documents; +mod notifications; use anyhow::Result; use sql_query_builder as sql; diff --git a/ee/tabby-db/src/notifications.rs b/ee/tabby-db/src/notifications.rs new file mode 100644 index 000000000000..9312f77db16a --- /dev/null +++ b/ee/tabby-db/src/notifications.rs @@ -0,0 +1,82 @@ +use anyhow::{Context, Result}; +use chrono::{DateTime, Duration, Utc}; +use sqlx::{prelude::*, query, query_as}; + +use crate::DbConn; + +#[derive(FromRow)] +pub struct NotificationDAO { + pub id: i64, + + pub kind: String, + pub content: String, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +impl DbConn { + pub async fn create_notification(&self, kind: &str, content: &str) -> Result { + let res = query!( + "INSERT INTO notifications (kind, content) VALUES (?, ?)", + kind, + content + ) + .execute(&self.pool) + .await?; + + Ok(res.last_insert_rowid()) + } + + pub async fn mark_notification_readed(&self, id: i64, user_id: i64) -> Result<()> { + query!( + "INSERT INTO readed_notifications (notification_id, user_id) VALUES (?, ?)", + id, + user_id + ) + .execute(&self.pool) + .await?; + + Ok(()) + } + + pub async fn list_notifications_within_7days( + &self, + user_id: i64, + ) -> Result> { + let user = self + .get_user(user_id) + .await? + .context("User doesn't exist")?; + let kind_clause = if user.is_admin { + "kind = 'all_user' OR kind = 'admin'" + } else { + "kind = 'all_user'" + }; + let date_7days_ago = Utc::now() - Duration::days(7); + let sql = format!( + r#" + SELECT notifications.id, notifications.created_at, notifications.updated_at, kind, content + FROM notifications LEFT JOIN readed_notifications ON notifications.id = readed_notifications.notification_id + WHERE ({kind_clause}) AND notifications.created_at > '{date_7days_ago}' AND readed_notifications.user_id IS NULL -- notification is not marked as readed + "# + ); + let notifications = query_as(&sql).fetch_all(&self.pool).await?; + Ok(notifications) + } +} + +#[cfg(test)] +mod tests { + use crate::testutils; + + use super::*; + + /// Smoke test to ensure sql query is valid, actual functionality test shall happens at service level. + #[tokio::test] + async fn smoketest_list_notifications() { + let db = DbConn::new_in_memory().await.unwrap(); + let user1 = testutils::create_user(&db).await; + let notifications = db.list_notifications_within_7days(user1).await.unwrap(); + assert!(notifications.is_empty()) + } +} \ No newline at end of file From d3bc2dceefaeae412d138f44669376251d95ea32 Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Thu, 5 Dec 2024 18:27:29 +0800 Subject: [PATCH 08/16] update --- .../0039_add-notification-inbox.up.sql | 2 ++ ee/tabby-db/schema.sqlite | Bin 217088 -> 221184 bytes ee/tabby-db/schema/schema.sql | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql index 811d916d8a2a..765bb9a83738 100644 --- a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql @@ -19,6 +19,8 @@ CREATE TABLE readed_notifications ( created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), + CONSTRAINT idx_unique_user_id_notification_id UNIQUE (user_id, notification_id), + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (notification_id) REFERENCES notifications(id) ON DELETE CASCADE ) \ No newline at end of file diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite index 48a17a01063199e778c8ca35636c6f216d5f2b0e..2a57ba44e0d9e0087e59909bc35d5bd75cbd6710 100644 GIT binary patch delta 354 zcmZozz}s+ucY?H_2m=Fy84$xj#zY-sMv;vP0)p0t23AJqRz^nr-RkF_R6O@t|2E#n zpmbNqJGuCk6K7PYi7L73zT0`zXjk(@lX9NE$#*ZNPGH`jJcr$APV;KP_SJ%nK+Lp# zwIFi>AEU`;L4)`F69ZTmO%M>{<+#qk&X&%=yNWlK>mXMO$9=YRj=s%_3W03=Eh;YT z;-;pI?b#)XNja(01KgQKrt5(Sc28!$p8mpvS)S2kI)^9oMX|(`+{`>3g~Xhkc#thz3JTeoc`1`GGKx)) V3uffnF5t!dft8VKGo!(OegHEKYfk_G delta 248 zcmZoTz}v8ZcY?H_5Ca2)DGXJJzs?Ap^h(lqekWu1ot>}$TC?70oz*vIg){e+AI+-;+gA%R0x{F} z)q>0ke2cjo1Q?As3mUxVpBTV8DS$-3i%%<|LcxiRxi7xQF3GCehsNn-LPM$zeU!Hitn1-zI)umS*rk4~`w diff --git a/ee/tabby-db/schema/schema.sql b/ee/tabby-db/schema/schema.sql index 3f09de29811f..9807a6b81468 100644 --- a/ee/tabby-db/schema/schema.sql +++ b/ee/tabby-db/schema/schema.sql @@ -227,7 +227,7 @@ CREATE TABLE notifications( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), - -- enum of ADMIN, ALL_USERS + -- enum of admin, all_user kind VARCHAR(255) NOT NULL DEFAULT 'admin', -- content of notification, in markdown format. content TEXT NOT NULL @@ -238,6 +238,7 @@ CREATE TABLE readed_notifications( notification_id INTEGER NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), + CONSTRAINT idx_unique_user_id_notification_id UNIQUE(user_id, notification_id), FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY(notification_id) REFERENCES notifications(id) ON DELETE CASCADE ); From 7cbbc5a50da82311920656d291b599b7b7b3e9eb Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Thu, 5 Dec 2024 18:35:24 +0800 Subject: [PATCH 09/16] add NotificationKind --- ee/tabby-schema/src/dao.rs | 20 +++++++++++++++++++- ee/tabby-schema/src/schema/mod.rs | 1 + ee/tabby-schema/src/schema/notification.rs | 7 +++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 ee/tabby-schema/src/schema/notification.rs diff --git a/ee/tabby-schema/src/dao.rs b/ee/tabby-schema/src/dao.rs index 7961ca48dc24..3a0ecc9f6773 100644 --- a/ee/tabby-schema/src/dao.rs +++ b/ee/tabby-schema/src/dao.rs @@ -11,6 +11,7 @@ use tabby_db::{ use crate::{ integration::{Integration, IntegrationKind, IntegrationStatus}, interface::UserValue, + notification::NotificationKind, repository::RepositoryKind, schema::{ auth::{self, OAuthCredential, OAuthProvider}, @@ -23,7 +24,7 @@ use crate::{ user_event::{EventKind, UserEvent}, CoreError, }, - thread::{self}, + thread, }; impl From for auth::Invitation { @@ -467,3 +468,20 @@ impl DbEnum for thread::Role { } } } + +impl DbEnum for NotificationKind { + fn as_enum_str(&self) -> &'static str { + match self { + NotificationKind::Admin => "admin", + NotificationKind::AllUser => "all_user", + } + } + + fn from_enum_str(s: &str) -> anyhow::Result { + match s { + "admin" => Ok(NotificationKind::Admin), + "all_user" => Ok(NotificationKind::AllUser), + _ => bail!("{s} is not a valid value for NotificationKind"), + } + } +} diff --git a/ee/tabby-schema/src/schema/mod.rs b/ee/tabby-schema/src/schema/mod.rs index 504b9ef0a02a..b50235322b7b 100644 --- a/ee/tabby-schema/src/schema/mod.rs +++ b/ee/tabby-schema/src/schema/mod.rs @@ -15,6 +15,7 @@ pub mod user_event; pub mod user_group; pub mod web_documents; pub mod worker; +pub mod notification; use std::{sync::Arc, time::Instant}; diff --git a/ee/tabby-schema/src/schema/notification.rs b/ee/tabby-schema/src/schema/notification.rs new file mode 100644 index 000000000000..50ccaec2cecc --- /dev/null +++ b/ee/tabby-schema/src/schema/notification.rs @@ -0,0 +1,7 @@ +use juniper::GraphQLEnum; + +#[derive(GraphQLEnum, Clone, Debug)] +pub enum NotificationKind { + Admin, + AllUser, +} \ No newline at end of file From 7d0d5beceffd0e5c6b5703531fcf09835a8718a5 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:42:18 +0000 Subject: [PATCH 10/16] [autofix.ci] apply automated fixes --- ee/tabby-db/src/lib.rs | 2 +- ee/tabby-db/src/notifications.rs | 5 ++--- ee/tabby-schema/src/schema/mod.rs | 2 +- ee/tabby-schema/src/schema/notification.rs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ee/tabby-db/src/lib.rs b/ee/tabby-db/src/lib.rs index 1be2179bf347..48017908a8e8 100644 --- a/ee/tabby-db/src/lib.rs +++ b/ee/tabby-db/src/lib.rs @@ -33,6 +33,7 @@ mod invitations; mod job_runs; #[cfg(test)] mod migration_tests; +mod notifications; mod oauth_credential; mod password_reset; mod provided_repositories; @@ -45,7 +46,6 @@ mod user_events; mod user_groups; mod users; mod web_documents; -mod notifications; use anyhow::Result; use sql_query_builder as sql; diff --git a/ee/tabby-db/src/notifications.rs b/ee/tabby-db/src/notifications.rs index 9312f77db16a..9aa23aaf7a2b 100644 --- a/ee/tabby-db/src/notifications.rs +++ b/ee/tabby-db/src/notifications.rs @@ -67,9 +67,8 @@ impl DbConn { #[cfg(test)] mod tests { - use crate::testutils; - use super::*; + use crate::testutils; /// Smoke test to ensure sql query is valid, actual functionality test shall happens at service level. #[tokio::test] @@ -79,4 +78,4 @@ mod tests { let notifications = db.list_notifications_within_7days(user1).await.unwrap(); assert!(notifications.is_empty()) } -} \ No newline at end of file +} diff --git a/ee/tabby-schema/src/schema/mod.rs b/ee/tabby-schema/src/schema/mod.rs index b50235322b7b..e85b2e0a4cd8 100644 --- a/ee/tabby-schema/src/schema/mod.rs +++ b/ee/tabby-schema/src/schema/mod.rs @@ -8,6 +8,7 @@ pub mod integration; pub mod interface; pub mod job; pub mod license; +pub mod notification; pub mod repository; pub mod setting; pub mod thread; @@ -15,7 +16,6 @@ pub mod user_event; pub mod user_group; pub mod web_documents; pub mod worker; -pub mod notification; use std::{sync::Arc, time::Instant}; diff --git a/ee/tabby-schema/src/schema/notification.rs b/ee/tabby-schema/src/schema/notification.rs index 50ccaec2cecc..aa9712c96a58 100644 --- a/ee/tabby-schema/src/schema/notification.rs +++ b/ee/tabby-schema/src/schema/notification.rs @@ -4,4 +4,4 @@ use juniper::GraphQLEnum; pub enum NotificationKind { Admin, AllUser, -} \ No newline at end of file +} From c749141da9552563c87dbc90744f00fe4e143b47 Mon Sep 17 00:00:00 2001 From: Meng Zhang Date: Thu, 5 Dec 2024 18:50:55 +0800 Subject: [PATCH 11/16] update --- .../0039_add-notification-inbox.up.sql | 2 +- ee/tabby-db/schema.sqlite | Bin 221184 -> 221184 bytes ee/tabby-db/schema/schema.sql | 2 +- ee/tabby-db/schema/schema.svg | 2 +- ee/tabby-db/src/notifications.rs | 18 +++++++++--------- ee/tabby-schema/src/dao.rs | 12 ++++++------ ee/tabby-schema/src/schema/notification.rs | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql index 765bb9a83738..04ca59cef9b7 100644 --- a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql @@ -5,7 +5,7 @@ CREATE TABLE notifications ( updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), -- enum of admin, all_user - kind VARCHAR(255) NOT NULL DEFAULT 'admin', + recipient VARCHAR(255) NOT NULL DEFAULT 'admin', -- content of notification, in markdown format. content TEXT NOT NULL diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite index 2a57ba44e0d9e0087e59909bc35d5bd75cbd6710..349a5d2b1b1148b46232f67bb0a2348fb7af88f9 100644 GIT binary patch delta 229 zcmZoTz}s+ucY?H_7y|=?84$xj&O{w!MzM_v!h#m223E!fEg^v+N8=_*A9?InwMz1K zgNg9w1o^}!ZNBA;zh8U1mFGv1!p9Zz&wfNM5Uad-av2w|*Xrigg6*pX8G)E-`)Wbv z1ir=G1_G=gON=)Q8ocKhXW-?y#K6v$#K60XH~1wj_?u&4~&=Y}3=-n9Ui@ zw$BGrOu{BWO&lPnHM2Xji>s?Mw)#$g>A@^NeV!XL-*hog=4+fqsmYlInW=dtlP@xg TO^*v^V;dS^qZP#-Map z$2+b~1~(`Z-oM3Zu!zR7nlrcPkqpFD@%XioEL!S>aHj6lq^eYGHS z0w1HvW2bk~T-yb_m_M*Ga&2Zb_|Fdjy7f+Z diff --git a/ee/tabby-db/schema/schema.sql b/ee/tabby-db/schema/schema.sql index 9807a6b81468..ef1e248389cc 100644 --- a/ee/tabby-db/schema/schema.sql +++ b/ee/tabby-db/schema/schema.sql @@ -228,7 +228,7 @@ CREATE TABLE notifications( created_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), updated_at TIMESTAMP NOT NULL DEFAULT(DATETIME('now')), -- enum of admin, all_user - kind VARCHAR(255) NOT NULL DEFAULT 'admin', + recipient VARCHAR(255) NOT NULL DEFAULT 'admin', -- content of notification, in markdown format. content TEXT NOT NULL ); diff --git a/ee/tabby-db/schema/schema.svg b/ee/tabby-db/schema/schema.svg index 89358e7b451c..5596f9955dd9 100644 --- a/ee/tabby-db/schema/schema.svg +++ b/ee/tabby-db/schema/schema.svg @@ -211,7 +211,7 @@   -kind +recipient   diff --git a/ee/tabby-db/src/notifications.rs b/ee/tabby-db/src/notifications.rs index 9aa23aaf7a2b..520778c161a7 100644 --- a/ee/tabby-db/src/notifications.rs +++ b/ee/tabby-db/src/notifications.rs @@ -8,17 +8,17 @@ use crate::DbConn; pub struct NotificationDAO { pub id: i64, - pub kind: String, + pub recipient: String, pub content: String, pub created_at: DateTime, pub updated_at: DateTime, } impl DbConn { - pub async fn create_notification(&self, kind: &str, content: &str) -> Result { + pub async fn create_notification(&self, recipient: &str, content: &str) -> Result { let res = query!( - "INSERT INTO notifications (kind, content) VALUES (?, ?)", - kind, + "INSERT INTO notifications (recipient, content) VALUES (?, ?)", + recipient, content ) .execute(&self.pool) @@ -47,17 +47,17 @@ impl DbConn { .get_user(user_id) .await? .context("User doesn't exist")?; - let kind_clause = if user.is_admin { - "kind = 'all_user' OR kind = 'admin'" + let recipient_clause = if user.is_admin { + "recipient = 'all_user' OR recipient = 'admin'" } else { - "kind = 'all_user'" + "recipient = 'all_user'" }; let date_7days_ago = Utc::now() - Duration::days(7); let sql = format!( r#" - SELECT notifications.id, notifications.created_at, notifications.updated_at, kind, content + SELECT notifications.id, notifications.created_at, notifications.updated_at, recipient, content FROM notifications LEFT JOIN readed_notifications ON notifications.id = readed_notifications.notification_id - WHERE ({kind_clause}) AND notifications.created_at > '{date_7days_ago}' AND readed_notifications.user_id IS NULL -- notification is not marked as readed + WHERE ({recipient_clause}) AND notifications.created_at > '{date_7days_ago}' AND readed_notifications.user_id IS NULL -- notification is not marked as readed "# ); let notifications = query_as(&sql).fetch_all(&self.pool).await?; diff --git a/ee/tabby-schema/src/dao.rs b/ee/tabby-schema/src/dao.rs index 3a0ecc9f6773..a89f1df9f943 100644 --- a/ee/tabby-schema/src/dao.rs +++ b/ee/tabby-schema/src/dao.rs @@ -11,7 +11,7 @@ use tabby_db::{ use crate::{ integration::{Integration, IntegrationKind, IntegrationStatus}, interface::UserValue, - notification::NotificationKind, + notification::NotificationRecipient, repository::RepositoryKind, schema::{ auth::{self, OAuthCredential, OAuthProvider}, @@ -469,18 +469,18 @@ impl DbEnum for thread::Role { } } -impl DbEnum for NotificationKind { +impl DbEnum for NotificationRecipient { fn as_enum_str(&self) -> &'static str { match self { - NotificationKind::Admin => "admin", - NotificationKind::AllUser => "all_user", + NotificationRecipient::Admin => "admin", + NotificationRecipient::AllUser => "all_user", } } fn from_enum_str(s: &str) -> anyhow::Result { match s { - "admin" => Ok(NotificationKind::Admin), - "all_user" => Ok(NotificationKind::AllUser), + "admin" => Ok(NotificationRecipient::Admin), + "all_user" => Ok(NotificationRecipient::AllUser), _ => bail!("{s} is not a valid value for NotificationKind"), } } diff --git a/ee/tabby-schema/src/schema/notification.rs b/ee/tabby-schema/src/schema/notification.rs index aa9712c96a58..a72e3e243173 100644 --- a/ee/tabby-schema/src/schema/notification.rs +++ b/ee/tabby-schema/src/schema/notification.rs @@ -1,7 +1,7 @@ use juniper::GraphQLEnum; #[derive(GraphQLEnum, Clone, Debug)] -pub enum NotificationKind { +pub enum NotificationRecipient { Admin, AllUser, } From cda8e51d4b404e55490c82fe149f44e543ad6fe1 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Wed, 11 Dec 2024 11:56:35 +0800 Subject: [PATCH 12/16] feat(graphQL): add notifications api Signed-off-by: Wei Zhang --- ee/tabby-schema/graphql/schema.graphql | 10 ++++ ee/tabby-schema/src/schema/mod.rs | 61 ++++++++++++++++++++++ ee/tabby-schema/src/schema/notification.rs | 12 ++++- 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/ee/tabby-schema/graphql/schema.graphql b/ee/tabby-schema/graphql/schema.graphql index 67c1b82377d1..9c09e84cc936 100644 --- a/ee/tabby-schema/graphql/schema.graphql +++ b/ee/tabby-schema/graphql/schema.graphql @@ -576,6 +576,7 @@ type Mutation { refreshToken(refreshToken: String!): RefreshTokenResponse! createInvitation(email: String!): ID! sendTestEmail(to: String!): Boolean! + markNotificationsReaded(notificationIds: [ID!]!): Boolean! createGitRepository(name: String!, gitUrl: String!): ID! deleteGitRepository(id: ID!): Boolean! updateGitRepository(id: ID!, name: String!, gitUrl: String!): Boolean! @@ -615,6 +616,14 @@ type NetworkSetting { externalUrl: String! } +type Notification { + id: ID! + content: String! + read: Boolean! + createdAt: DateTime! + updatedAt: DateTime! +} + type OAuthCredential { provider: OAuthProvider! clientId: String! @@ -713,6 +722,7 @@ type Query { dailyStatsInPastYear(users: [ID!]): [CompletionStats!]! dailyStats(start: DateTime!, end: DateTime!, users: [ID!], languages: [Language!]): [CompletionStats!]! userEvents(after: String, before: String, first: Int, last: Int, users: [ID!], start: DateTime!, end: DateTime!): UserEventConnection! + notifications(readed: Boolean): [Notification!]! diskUsageStats: DiskUsageStats! repositoryList: [Repository!]! contextInfo: ContextInfo! diff --git a/ee/tabby-schema/src/schema/mod.rs b/ee/tabby-schema/src/schema/mod.rs index e85b2e0a4cd8..4c59508e1980 100644 --- a/ee/tabby-schema/src/schema/mod.rs +++ b/ee/tabby-schema/src/schema/mod.rs @@ -528,6 +528,63 @@ impl Query { .await } + async fn notifications( + ctx: &Context, + readed: Option, + ) -> Result> { + let user = check_user(ctx).await?; + match readed { + Some(true) => Ok(vec![ + notification::Notification { + id: "1".to_string().into(), + content: "Hello".into(), + read: true, + created_at: Utc::now(), + updated_at: Utc::now(), + }, + notification::Notification { + id: "3".to_string().into(), + content: "Tabby".into(), + read: true, + created_at: Utc::now(), + updated_at: Utc::now(), + }, + ]), + Some(false) => Ok(vec![ + notification::Notification { + id: "2".to_string().into(), + content: "World".into(), + read: false, + created_at: Utc::now(), + updated_at: Utc::now(), + }, + notification::Notification { + id: "4".to_string().into(), + content: "Assistant".into(), + read: false, + created_at: Utc::now(), + updated_at: Utc::now(), + }, + ]), + None => Ok(vec![ + notification::Notification { + id: "5".to_string().into(), + content: "World".into(), + read: false, + created_at: Utc::now(), + updated_at: Utc::now(), + }, + notification::Notification { + id: "6".to_string().into(), + content: "Assistant".into(), + read: true, + created_at: Utc::now(), + updated_at: Utc::now(), + }, + ]), + } + } + async fn disk_usage_stats(ctx: &Context) -> Result { check_admin(ctx).await?; ctx.locator.analytic().disk_usage_stats().await @@ -989,6 +1046,10 @@ impl Mutation { Ok(true) } + async fn mark_notifications_readed(ctx: &Context, notification_ids: Vec) -> Result { + Ok(true) + } + async fn create_git_repository(ctx: &Context, name: String, git_url: String) -> Result { check_admin(ctx).await?; let input = repository::CreateGitRepositoryInput { name, git_url }; diff --git a/ee/tabby-schema/src/schema/notification.rs b/ee/tabby-schema/src/schema/notification.rs index a72e3e243173..78cddc443ffc 100644 --- a/ee/tabby-schema/src/schema/notification.rs +++ b/ee/tabby-schema/src/schema/notification.rs @@ -1,7 +1,17 @@ -use juniper::GraphQLEnum; +use chrono::{DateTime, Utc}; +use juniper::{GraphQLEnum, GraphQLObject, ID}; #[derive(GraphQLEnum, Clone, Debug)] pub enum NotificationRecipient { Admin, AllUser, } + +#[derive(GraphQLObject)] +pub struct Notification { + pub id: ID, + pub content: String, + pub read: bool, + pub created_at: DateTime, + pub updated_at: DateTime, +} From 83696ed45513d388d414bb959d376a463c3b9f70 Mon Sep 17 00:00:00 2001 From: Wei Zhang Date: Thu, 12 Dec 2024 19:30:37 +0800 Subject: [PATCH 13/16] feat(graphQL): add list notifications and mark read --- .../0039_add-notification-inbox.down.sql | 2 +- .../0039_add-notification-inbox.up.sql | 2 +- ee/tabby-db/schema.sqlite | Bin 221184 -> 221184 bytes ee/tabby-db/schema/schema.svg | 1294 ++++++++--------- ee/tabby-db/src/lib.rs | 1 + ee/tabby-db/src/notifications.rs | 80 +- ee/tabby-schema/src/dao.rs | 16 +- ee/tabby-schema/src/schema/mod.rs | 66 +- ee/tabby-schema/src/schema/notification.rs | 10 + ee/tabby-webserver/src/service/mod.rs | 9 + .../src/service/notification.rs | 239 +++ 11 files changed, 1006 insertions(+), 713 deletions(-) create mode 100644 ee/tabby-webserver/src/service/notification.rs diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.down.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.down.sql index 53c4478d75ae..34e0300a477f 100644 --- a/ee/tabby-db/migrations/0039_add-notification-inbox.down.sql +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.down.sql @@ -1,2 +1,2 @@ DROP TABLE notifications; -DROP TABLE readed_notifications; \ No newline at end of file +DROP TABLE read_notifications; \ No newline at end of file diff --git a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql index 04ca59cef9b7..3b18f122df16 100644 --- a/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql +++ b/ee/tabby-db/migrations/0039_add-notification-inbox.up.sql @@ -11,7 +11,7 @@ CREATE TABLE notifications ( content TEXT NOT NULL ); -CREATE TABLE readed_notifications ( +CREATE TABLE read_notifications ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, notification_id INTEGER NOT NULL, diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite index 349a5d2b1b1148b46232f67bb0a2348fb7af88f9..e37f65dce475e0c4b6911249ff2a985ca9cb6d19 100644 GIT binary patch delta 1624 zcmai!eQXnD9LMi@p4+u+sn>N~uf4u*g)+wqcip;7&_Q8qrZT~SlBmnpZAb0c+PQX4 zFk}NIOAN!pLvqp0Oy&!5i;6U%zWgD`A1Xl;midxkeNC2V;*>;)$q;=?LZbJOczK?? z%k#P4_xJmK@3|X0YZ^Oi$~wy+!!QkG-a0lPt*s-AYxfP7kR`-lDT0P~1`qMzp4>Hl z1T@JZpA=dj4y~`3vWH%Zsd>y*2ru(7598pIzc$s`kfgA8%_% zuU9V5pI>;s{R;Z_Jv>)deTN(h{!0b_P$nTZ_93=f0$S8+kzwsV{ItcXC9hf}1Pedm zuPm#zXhpe%Pz2(Q<&qZdCy2Elz^BToDi(`q!hjuCuQn=bm8<(s?(*n>x307wd4|)< zP%s%AP(o6up0x6xZlMU}&Q6v6vz^q|Q@}0<<=!@j zgv@8!@j-`Q+fj}nbfg+DIx4lOLX_d#b+}ce$cRW%eK3G$MYlHS3lh{gRd<6P{4Q5wy9 zWH{M@3m%`=1*4Y)RbkxZrQYrCB6?#Mzu={c_>3U#iQ@--XMDgom@AR4@-tcAOjerN zWG3VECf1x|$F6%%XN_jZ8uClJHT~qiicGP1&6P>t@W-~Z0X=tv(RFb*OqUJIr8`RP z`hsqby#&MHYvuta3Yy8o4DPW#IVS&QEBXb1kb3SubU%>u2fE=D1d{4e;0CZx zy%dK+{;xJP0o3nf;LZG89QFegxNC#@-Q%!^mq(QD&fZkd@PLxcq}SwkB;dOZIpf$> z@68TK6J(gl*>2KLGI}n|@SmE}rhwr&Ls0(;7uG+Vi}~DK^jS}{PLO3V9?!(O`;{Rj z*4vp(WfHp+J+Vw8l}yts^;_CoB0F1rJ0qL7w)k#x^+XqpS!C{)Tub+{{eqnu0sF- delta 1703 zcma)+eM}o=9LM|Im1`-KmeTi@_Hg5EtmC;WN1!tZj?RsB?1j!T*sLu*paa^a6mcq% zk-bF!aAl9?Vq79Hqo!m=*JzD@ZDz~nqA|{w;U-QZA@Oa7KhPPb_>{y%@A$*Z{gTV? z^L&5L@A=;EZuFvh^rAXxD>TYvvRWMfT>ax4-8i`^K9Pe{q-HS-xOa}6(13>YZ<=Ay z06Ch09u9ik@VI}E|H@-$Y?r^^`c%qtd$gt~{kStYR#ouEx!=F~<=y;yaeDDh=fb@M z6UL)ofAqRa-7<})3o8m>y$jaUtlI-Q59>yAg^)CMokeZ1vxu#sA?xAlJWMUhFM=d-Z5};T1jT3sBeLowdb&57P7Oga* zYLmpD0TV{-el%f{?DesUA)3yi6_cbMV}>s9=jBel0+eU{(I1Y5T=Ai(D-sR!Pp;+B z3ucBe{)H}?C2jv;#_pLbkliA2ZXRm}Q;#L}`qO`O|2@QX)nMD?@bMMBA(q zGHk`7nHiL_7K=q+V$q%fq_o+^gtcMEyC)HE+ayMl7%90T^o32b6=`Q&KYw#jr2v&# zgH(8ap1>nC$R`q!XwO;>1?{-G9X;qp`|3CG@A1Bd5xJa)mhF;#HWV|&k1wG^#ge{H zVRCB;eNZeZcNddx3o3C)T<|&YxPID-o|U4MgCQCV(5!6QT$`4U-gizV=`*|KbS`yOrl_Q@ zs&A{7bK7#w%2~yhQdW#lHt48kYW0sc*0)7rDlZ2o48r+#a6rl3MHbxM6mbx9Rg-l9Uv=h8viN3|SZ->fik95~gN2!{Ik zI3Eo2;XpK&h;&D~f{92hI=EJ@cFSIGliy4Gn;!Og>D3jPqmPoafO^DBnL0UqPHUyK gRDfBlSWV`YcJeOxe|{bVLhK+}DLfM(cTPd=-=b8{7XSbN diff --git a/ee/tabby-db/schema/schema.svg b/ee/tabby-db/schema/schema.svg index 5596f9955dd9..a5c49d083675 100644 --- a/ee/tabby-db/schema/schema.svg +++ b/ee/tabby-db/schema/schema.svg @@ -1,795 +1,795 @@ - - - + + structs - + _sqlx_migrations - -_sqlx_migrations - -🔑 - -version - -  - -description - -  - -installed_on - -  - -success - -  - -checksum - -  - -execution_time + +_sqlx_migrations + +🔑 + +version + +  + +description + +  + +installed_on + +  + +success + +  + +checksum + +  + +execution_time email_setting - -email_setting - -🔑 - -id - -  - -smtp_username - -  - -smtp_password - -  - -smtp_server - -  - -from_address - -  - -encryption - -  - -auth_method - -  - -smtp_port + +email_setting + +🔑 + +id + +  + +smtp_username + +  + +smtp_password + +  + +smtp_server + +  + +from_address + +  + +encryption + +  + +auth_method + +  + +smtp_port integrations - -integrations - -🔑 - -id - -  - -kind - -  - -display_name - -  - -access_token - -  - -api_base - -  - -error - -  - -created_at - -  - -updated_at - -  - -synced + +integrations + +🔑 + +id + +  + +kind + +  + +display_name + +  + +access_token + +  + +api_base + +  + +error + +  + +created_at + +  + +updated_at + +  + +synced invitations - -invitations - -🔑 - -id - -  - -email - -  - -code - -  - -created_at + +invitations + +🔑 + +id + +  + +email + +  + +code + +  + +created_at job_runs - -job_runs - -🔑 - -id - -  - -job - -  - -start_ts - -  - -end_ts - -  - -exit_code - -  - -stdout - -  - -stderr - -  - -created_at - -  - -updated_at - -  - -command - -  - -started_at + +job_runs + +🔑 + +id + +  + +job + +  + +start_ts + +  + +end_ts + +  + +exit_code + +  + +stdout + +  + +stderr + +  + +created_at + +  + +updated_at + +  + +command + +  + +started_at notifications - -notifications - -🔑 - -id - -  - -created_at - -  - -updated_at - -  - -recipient - -  - -content + +notifications + +🔑 + +id + +  + +created_at + +  + +updated_at + +  + +recipient + +  + +content oauth_credential - -oauth_credential - -🔑 - -id - -  - -provider - -  - -client_id - -  - -client_secret - -  - -created_at - -  - -updated_at + +oauth_credential + +🔑 + +id + +  + +provider + +  + +client_id + +  + +client_secret + +  + +created_at + +  + +updated_at password_reset - -password_reset - -🔑 - -id - -  - -user_id - -  - -code - -  - -created_at + +password_reset + +🔑 + +id + +  + +user_id + +  + +code + +  + +created_at users - -users - -🔑 - -id - -  - -email - -  - -is_admin - -  - -created_at - -  - -updated_at - -  - -auth_token - -  - -active - -  - -password_encrypted - -  - -avatar - -  - -name + +users + +🔑 + +id + +  + +email + +  + +is_admin + +  + +created_at + +  + +updated_at + +  + +auth_token + +  + +active + +  + +password_encrypted + +  + +avatar + +  + +name password_reset:e->users:w - - + + provided_repositories - -provided_repositories - -🔑 - -id - -  - -integration_id - -  - -vendor_id - -  - -name - -  - -git_url - -  - -active - -  - -created_at - -  - -updated_at + +provided_repositories + +🔑 + +id + +  + +integration_id + +  + +vendor_id + +  + +name + +  + +git_url + +  + +active + +  + +created_at + +  + +updated_at provided_repositories:e->integrations:w - - + + readed_notifications - -readed_notifications - -🔑 - -id - -  - -user_id - -  - -notification_id - -  - -created_at - -  - -updated_at + +readed_notifications + +🔑 + +id + +  + +user_id + +  + +notification_id + +  + +created_at + +  + +updated_at readed_notifications:e->notifications:w - - + + readed_notifications:e->users:w - - + + refresh_tokens - -refresh_tokens - -🔑 - -id - -  - -user_id - -  - -token - -  - -expires_at - -  - -created_at + +refresh_tokens + +🔑 + +id + +  + +user_id + +  + +token + +  + +expires_at + +  + +created_at refresh_tokens:e->users:w - - + + registration_token - -registration_token - -🔑 - -id - -  - -token - -  - -created_at - -  - -updated_at + +registration_token + +🔑 + +id + +  + +token + +  + +created_at + +  + +updated_at repositories - -repositories - -🔑 - -id - -  - -name - -  - -git_url + +repositories + +🔑 + +id + +  + +name + +  + +git_url server_setting - -server_setting - -🔑 - -id - -  - -security_allowed_register_domain_list - -  - -security_disable_client_side_telemetry - -  - -network_external_url - -  - -billing_enterprise_license + +server_setting + +🔑 + +id + +  + +security_allowed_register_domain_list + +  + +security_disable_client_side_telemetry + +  + +network_external_url + +  + +billing_enterprise_license source_id_read_access_policies - -source_id_read_access_policies - -🔑 - -id - -  - -source_id - -  - -user_group_id - -  - -created_at - -  - -updated_at + +source_id_read_access_policies + +🔑 + +id + +  + +source_id + +  + +user_group_id + +  + +created_at + +  + +updated_at user_groups - -user_groups - -🔑 - -id - -  - -name - -  - -created_at - -  - -updated_at + +user_groups + +🔑 + +id + +  + +name + +  + +created_at + +  + +updated_at source_id_read_access_policies:e->user_groups:w - - + + thread_messages - -thread_messages - -🔑 - -id - -  - -thread_id - -  - -role - -  - -content - -  - -code_attachments - -  - -client_code_attachments - -  - -doc_attachments - -  - -created_at - -  - -updated_at + +thread_messages + +🔑 + +id + +  + +thread_id + +  + +role + +  + +content + +  + +code_attachments + +  + +client_code_attachments + +  + +doc_attachments + +  + +created_at + +  + +updated_at threads - -threads - -🔑 - -id - -  - -is_ephemeral - -  - -user_id - -  - -created_at - -  - -updated_at - -  - -relevant_questions + +threads + +🔑 + +id + +  + +is_ephemeral + +  + +user_id + +  + +created_at + +  + +updated_at + +  + +relevant_questions thread_messages:e->threads:w - - + + threads:e->users:w - - + + user_completions - -user_completions - -🔑 - -id - -  - -user_id - -  - -completion_id - -  - -language - -  - -views - -  - -selects - -  - -dismisses - -  - -created_at - -  - -updated_at + +user_completions + +🔑 + +id + +  + +user_id + +  + +completion_id + +  + +language + +  + +views + +  + +selects + +  + +dismisses + +  + +created_at + +  + +updated_at user_completions:e->users:w - - + + user_events - -user_events - -🔑 - -id - -  - -user_id - -  - -kind - -  - -created_at - -  - -payload + +user_events + +🔑 + +id + +  + +user_id + +  + +kind + +  + +created_at + +  + +payload user_events:e->users:w - - + + user_group_memberships - -user_group_memberships - -🔑 - -id - -  - -user_id - -  - -user_group_id - -  - -is_group_admin - -  - -created_at - -  - -updated_at + +user_group_memberships + +🔑 + +id + +  + +user_id + +  + +user_group_id + +  + +is_group_admin + +  + +created_at + +  + +updated_at user_group_memberships:e->user_groups:w - - + + user_group_memberships:e->users:w - - + + web_documents - -web_documents - -🔑 - -id - -  - -name - -  - -url - -  - -is_preset - -  - -created_at - -  - -updated_at + +web_documents + +🔑 + +id + +  + +name + +  + +url + +  + +is_preset + +  + +created_at + +  + +updated_at diff --git a/ee/tabby-db/src/lib.rs b/ee/tabby-db/src/lib.rs index 48017908a8e8..a2eaccfbc594 100644 --- a/ee/tabby-db/src/lib.rs +++ b/ee/tabby-db/src/lib.rs @@ -8,6 +8,7 @@ pub use email_setting::EmailSettingDAO; pub use integrations::IntegrationDAO; pub use invitations::InvitationDAO; pub use job_runs::JobRunDAO; +pub use notifications::NotificationDAO; pub use oauth_credential::OAuthCredentialDAO; pub use provided_repositories::ProvidedRepositoryDAO; pub use repositories::RepositoryDAO; diff --git a/ee/tabby-db/src/notifications.rs b/ee/tabby-db/src/notifications.rs index 520778c161a7..0abda892caac 100644 --- a/ee/tabby-db/src/notifications.rs +++ b/ee/tabby-db/src/notifications.rs @@ -4,12 +4,16 @@ use sqlx::{prelude::*, query, query_as}; use crate::DbConn; +pub const NOTIFICATION_RECIPIENT_ALL_USER: &str = "all_user"; +pub const NOTIFICATION_RECIPIENT_ADMIN: &str = "admin"; + #[derive(FromRow)] pub struct NotificationDAO { pub id: i64, pub recipient: String, pub content: String, + pub read: bool, pub created_at: DateTime, pub updated_at: DateTime, } @@ -27,9 +31,9 @@ impl DbConn { Ok(res.last_insert_rowid()) } - pub async fn mark_notification_readed(&self, id: i64, user_id: i64) -> Result<()> { + pub async fn mark_notification_read(&self, id: i64, user_id: i64) -> Result<()> { query!( - "INSERT INTO readed_notifications (notification_id, user_id) VALUES (?, ?)", + "INSERT INTO read_notifications (notification_id, user_id) VALUES (?, ?)", id, user_id ) @@ -39,6 +43,49 @@ impl DbConn { Ok(()) } + pub async fn mark_all_notifications_read_by_user(&self, user_id: i64) -> Result<()> { + let user = self + .get_user(user_id) + .await? + .context("User doesn't exist")?; + let recipient_clause = if user.is_admin { + format!( + "recipient = '{}' OR recipient = '{}'", + NOTIFICATION_RECIPIENT_ALL_USER, NOTIFICATION_RECIPIENT_ADMIN + ) + } else { + format!("recipient = '{}'", NOTIFICATION_RECIPIENT_ALL_USER) + }; + + let query = format!( + r#" +INSERT INTO read_notifications (notification_id, user_id) +SELECT + notifications.id, + ? +FROM + notifications +LEFT JOIN + read_notifications +ON + notifications.id = read_notifications.notification_id + AND read_notifications.user_id = ? +WHERE + {} + AND read_notifications.notification_id IS NULL; + "#, + recipient_clause + ); + + sqlx::query(&query) + .bind(user_id) + .bind(user_id) + .execute(&self.pool) + .await?; + + Ok(()) + } + pub async fn list_notifications_within_7days( &self, user_id: i64, @@ -48,16 +95,35 @@ impl DbConn { .await? .context("User doesn't exist")?; let recipient_clause = if user.is_admin { - "recipient = 'all_user' OR recipient = 'admin'" + format!( + "recipient = '{}' OR recipient = '{}'", + NOTIFICATION_RECIPIENT_ALL_USER, NOTIFICATION_RECIPIENT_ADMIN + ) } else { - "recipient = 'all_user'" + format!("recipient = '{}'", NOTIFICATION_RECIPIENT_ALL_USER) }; let date_7days_ago = Utc::now() - Duration::days(7); let sql = format!( r#" - SELECT notifications.id, notifications.created_at, notifications.updated_at, recipient, content - FROM notifications LEFT JOIN readed_notifications ON notifications.id = readed_notifications.notification_id - WHERE ({recipient_clause}) AND notifications.created_at > '{date_7days_ago}' AND readed_notifications.user_id IS NULL -- notification is not marked as readed +SELECT + notifications.id, + notifications.created_at, + notifications.updated_at, + recipient, + content, + CASE + WHEN read_notifications.user_id IS NOT NULL THEN 1 + ELSE 0 + END AS read +FROM + notifications +LEFT JOIN + read_notifications +ON + notifications.id = read_notifications.notification_id +WHERE + ({recipient_clause}) + AND notifications.created_at > '{date_7days_ago}' "# ); let notifications = query_as(&sql).fetch_all(&self.pool).await?; diff --git a/ee/tabby-schema/src/dao.rs b/ee/tabby-schema/src/dao.rs index a89f1df9f943..9c1e8d70708e 100644 --- a/ee/tabby-schema/src/dao.rs +++ b/ee/tabby-schema/src/dao.rs @@ -2,7 +2,7 @@ use anyhow::bail; use hash_ids::HashIds; use lazy_static::lazy_static; use tabby_db::{ - EmailSettingDAO, IntegrationDAO, InvitationDAO, JobRunDAO, OAuthCredentialDAO, + EmailSettingDAO, IntegrationDAO, InvitationDAO, JobRunDAO, NotificationDAO, OAuthCredentialDAO, ServerSettingDAO, ThreadDAO, ThreadMessageAttachmentClientCode, ThreadMessageAttachmentCode, ThreadMessageAttachmentDoc, ThreadMessageAttachmentIssueDoc, ThreadMessageAttachmentPullDoc, ThreadMessageAttachmentWebDoc, UserEventDAO, @@ -11,7 +11,7 @@ use tabby_db::{ use crate::{ integration::{Integration, IntegrationKind, IntegrationStatus}, interface::UserValue, - notification::NotificationRecipient, + notification::{Notification, NotificationRecipient}, repository::RepositoryKind, schema::{ auth::{self, OAuthCredential, OAuthProvider}, @@ -186,6 +186,18 @@ impl TryFrom for UserEvent { } } +impl From for Notification { + fn from(value: NotificationDAO) -> Self { + Self { + id: value.id.as_id(), + content: value.content, + read: value.read, + created_at: value.created_at, + updated_at: value.updated_at, + } + } +} + impl From for thread::MessageAttachmentCode { fn from(value: ThreadMessageAttachmentCode) -> Self { Self { diff --git a/ee/tabby-schema/src/schema/mod.rs b/ee/tabby-schema/src/schema/mod.rs index 4c59508e1980..476dbeef6650 100644 --- a/ee/tabby-schema/src/schema/mod.rs +++ b/ee/tabby-schema/src/schema/mod.rs @@ -41,6 +41,7 @@ use juniper::{ graphql_object, graphql_subscription, graphql_value, FieldError, GraphQLEnum, GraphQLObject, IntoFieldError, Object, RootNode, ScalarValue, Value, ID, }; +use notification::NotificationService; use repository::RepositoryGrepOutput; use tabby_common::{ api::{code::CodeSearch, event::EventLogger}, @@ -104,6 +105,7 @@ pub trait ServiceLocator: Send + Sync { fn context(&self) -> Arc; fn user_group(&self) -> Arc; fn access_policy(&self) -> Arc; + fn notification(&self) -> Arc; } pub struct Context { @@ -528,61 +530,9 @@ impl Query { .await } - async fn notifications( - ctx: &Context, - readed: Option, - ) -> Result> { + async fn notifications(ctx: &Context) -> Result> { let user = check_user(ctx).await?; - match readed { - Some(true) => Ok(vec![ - notification::Notification { - id: "1".to_string().into(), - content: "Hello".into(), - read: true, - created_at: Utc::now(), - updated_at: Utc::now(), - }, - notification::Notification { - id: "3".to_string().into(), - content: "Tabby".into(), - read: true, - created_at: Utc::now(), - updated_at: Utc::now(), - }, - ]), - Some(false) => Ok(vec![ - notification::Notification { - id: "2".to_string().into(), - content: "World".into(), - read: false, - created_at: Utc::now(), - updated_at: Utc::now(), - }, - notification::Notification { - id: "4".to_string().into(), - content: "Assistant".into(), - read: false, - created_at: Utc::now(), - updated_at: Utc::now(), - }, - ]), - None => Ok(vec![ - notification::Notification { - id: "5".to_string().into(), - content: "World".into(), - read: false, - created_at: Utc::now(), - updated_at: Utc::now(), - }, - notification::Notification { - id: "6".to_string().into(), - content: "Assistant".into(), - read: true, - created_at: Utc::now(), - updated_at: Utc::now(), - }, - ]), - } + ctx.locator.notification().list(&user.id).await } async fn disk_usage_stats(ctx: &Context) -> Result { @@ -1046,7 +996,13 @@ impl Mutation { Ok(true) } - async fn mark_notifications_readed(ctx: &Context, notification_ids: Vec) -> Result { + async fn mark_notifications_read(ctx: &Context, notification_id: Option) -> Result { + let user = check_user(ctx).await?; + + ctx.locator + .notification() + .mark_read(&user.id, notification_id) + .await?; Ok(true) } diff --git a/ee/tabby-schema/src/schema/notification.rs b/ee/tabby-schema/src/schema/notification.rs index 78cddc443ffc..8c34d3e24c5d 100644 --- a/ee/tabby-schema/src/schema/notification.rs +++ b/ee/tabby-schema/src/schema/notification.rs @@ -1,6 +1,9 @@ +use async_trait::async_trait; use chrono::{DateTime, Utc}; use juniper::{GraphQLEnum, GraphQLObject, ID}; +use crate::Result; + #[derive(GraphQLEnum, Clone, Debug)] pub enum NotificationRecipient { Admin, @@ -15,3 +18,10 @@ pub struct Notification { pub created_at: DateTime, pub updated_at: DateTime, } + +#[async_trait] +pub trait NotificationService: Send + Sync { + async fn list(&self, user_id: &ID) -> Result>; + + async fn mark_read(&self, user_id: &ID, id: Option) -> Result<()>; +} diff --git a/ee/tabby-webserver/src/service/mod.rs b/ee/tabby-webserver/src/service/mod.rs index 66a90352f5e4..9a8c4dd516da 100644 --- a/ee/tabby-webserver/src/service/mod.rs +++ b/ee/tabby-webserver/src/service/mod.rs @@ -9,6 +9,7 @@ pub mod event_logger; pub mod integration; pub mod job; mod license; +mod notification; mod preset_web_documents_data; pub mod repository; mod setting; @@ -51,6 +52,7 @@ use tabby_schema::{ is_demo_mode, job::JobService, license::{IsLicenseValid, LicenseService}, + notification::NotificationService, policy, repository::RepositoryService, setting::SettingService, @@ -72,6 +74,7 @@ struct ServerContext { chat: Option>, completion: Option>, auth: Arc, + notification: Arc, license: Arc, repository: Arc, integration: Arc, @@ -118,6 +121,7 @@ impl ServerContext { )); let user_group = Arc::new(user_group::create(db_conn.clone())); let access_policy = Arc::new(access_policy::create(db_conn.clone(), context.clone())); + let notification = Arc::new(notification::create(db_conn.clone())); background_job::start( db_conn.clone(), @@ -150,6 +154,7 @@ impl ServerContext { setting, user_group, access_policy, + notification, db_conn, user_rate_limiter: UserRateLimiter::default(), } @@ -290,6 +295,10 @@ impl ServiceLocator for ArcServerContext { self.0.logger.clone() } + fn notification(&self) -> Arc { + self.0.notification.clone() + } + fn job(&self) -> Arc { self.0.job.clone() } diff --git a/ee/tabby-webserver/src/service/notification.rs b/ee/tabby-webserver/src/service/notification.rs new file mode 100644 index 000000000000..d4aa7ee53dd7 --- /dev/null +++ b/ee/tabby-webserver/src/service/notification.rs @@ -0,0 +1,239 @@ +use async_trait::async_trait; +use juniper::ID; +use tabby_db::DbConn; +use tabby_schema::{ + notification::{Notification, NotificationService}, + AsRowid, Result, +}; + +struct NotificationServiceImpl { + db: DbConn, +} + +pub fn create(db: DbConn) -> impl NotificationService { + NotificationServiceImpl { db } +} + +#[async_trait] +impl NotificationService for NotificationServiceImpl { + async fn list(&self, user_id: &ID) -> Result> { + let notifications = self + .db + .list_notifications_within_7days(user_id.as_rowid().unwrap()) + .await?; + Ok(notifications.into_iter().map(|n| n.into()).collect()) + } + + async fn mark_read(&self, user_id: &ID, id: Option) -> Result<()> { + if let Some(id) = id { + self.db + .mark_notification_read(id.as_rowid().unwrap(), user_id.as_rowid().unwrap()) + .await?; + } else { + self.db + .mark_all_notifications_read_by_user(user_id.as_rowid().unwrap()) + .await?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use juniper::ID; + use tabby_db::DbConn; + use tabby_schema::{notification::NotificationService, AsID}; + + #[tokio::test] + async fn test_notification_admin_list() { + let db = DbConn::new_in_memory().await.unwrap(); + let service = create(db.clone()); + + let user_id = db + .create_user("test".into(), None, true, None) + .await + .unwrap() + .as_id(); + let notification_id = db + .create_notification("admin", "admin_list") + .await + .unwrap() + .as_id(); + + let notifications = service.list(&user_id).await.unwrap(); + assert_eq!(notifications.len(), 1); + assert_eq!(notifications[0].id, ID::from(notification_id)); + assert_eq!(notifications[0].content, "admin_list"); + assert_eq!(notifications[0].read, false); + } + + #[tokio::test] + async fn test_notification_admin_list_read() { + let db = DbConn::new_in_memory().await.unwrap(); + let service = create(db.clone()); + + let user_id = db + .create_user("test".into(), None, true, None) + .await + .unwrap(); + let notification_id = db + .create_notification("admin", "admin_list_read") + .await + .unwrap(); + db.mark_notification_read(notification_id, user_id) + .await + .unwrap(); + + let notifications = service.list(&user_id.as_id()).await.unwrap(); + assert_eq!(notifications.len(), 1); + assert_eq!(notifications[0].id, notification_id.as_id()); + assert_eq!(notifications[0].content, "admin_list_read"); + assert_eq!(notifications[0].read, true); + } + + #[tokio::test] + async fn test_notification_admin_list_all() { + let db = DbConn::new_in_memory().await.unwrap(); + let service = create(db.clone()); + + let user_id = db + .create_user("test".into(), None, true, None) + .await + .unwrap() + .as_id(); + db.create_notification("admin", "admin_list") + .await + .unwrap() + .as_id(); + db.create_notification("all_user", "admin_list_all_user") + .await + .unwrap() + .as_id(); + + let notifications = service.list(&user_id).await.unwrap(); + assert_eq!(notifications.len(), 2); + assert_eq!(notifications[0].content, "admin_list"); + assert_eq!(notifications[0].read, false); + assert_eq!(notifications[1].content, "admin_list_all_user"); + assert_eq!(notifications[1].read, false); + } + + #[tokio::test] + async fn test_notification_admin_mark_all_read_admin() { + let db = DbConn::new_in_memory().await.unwrap(); + let service = create(db.clone()); + + let user_id = db + .create_user("test".into(), None, true, None) + .await + .unwrap() + .as_id(); + db.create_notification("admin", "admin_list").await.unwrap(); + + service.mark_read(&user_id, None).await.unwrap(); + let notifications = service.list(&user_id).await.unwrap(); + assert_eq!(notifications.len(), 1); + assert_eq!(notifications[0].read, true); + } + + #[tokio::test] + async fn test_notification_admin_mark_read_twice() { + let db = DbConn::new_in_memory().await.unwrap(); + let service = create(db.clone()); + + let user_id = db + .create_user("test".into(), None, true, None) + .await + .unwrap() + .as_id(); + let notification_id = db + .create_notification("admin", "admin_list") + .await + .unwrap() + .as_id(); + + service + .mark_read(&user_id, Some(notification_id.clone())) + .await + .unwrap(); + let notifications = service.list(&user_id).await.unwrap(); + assert_eq!(notifications.len(), 1); + assert_eq!(notifications[0].read, true); + + assert!(service + .mark_read(&user_id, Some(notification_id)) + .await + .is_err()) + } + + #[tokio::test] + async fn test_notification_admin_mark_all_read_twice() { + let db = DbConn::new_in_memory().await.unwrap(); + let service = create(db.clone()); + + let user_id = db + .create_user("test".into(), None, true, None) + .await + .unwrap() + .as_id(); + db.create_notification("admin", "admin_list") + .await + .unwrap() + .as_id(); + + service.mark_read(&user_id, None).await.unwrap(); + let notifications = service.list(&user_id).await.unwrap(); + assert_eq!(notifications.len(), 1); + assert_eq!(notifications[0].read, true); + + // mark all read will not return error even when call twice + // but it should not create duplicated notifications + service.mark_read(&user_id, None).await.unwrap(); + assert_eq!(notifications.len(), 1); + assert_eq!(notifications[0].read, true); + } + + #[tokio::test] + async fn test_notification_admin_mark_all_read_admin_and_all_user() { + let db = DbConn::new_in_memory().await.unwrap(); + let service = create(db.clone()); + + let user_id = db + .create_user("test".into(), None, true, None) + .await + .unwrap() + .as_id(); + db.create_notification("admin", "admin_list").await.unwrap(); + db.create_notification("all_user", "all_user") + .await + .unwrap(); + + service.mark_read(&user_id, None).await.unwrap(); + let notifications = service.list(&user_id).await.unwrap(); + assert_eq!(notifications.len(), 2); + assert_eq!(notifications[0].read, true); + assert_eq!(notifications[1].read, true); + } + + #[tokio::test] + async fn test_notification_user_mark_all_read_admin_and_all_user() { + let db = DbConn::new_in_memory().await.unwrap(); + let service = create(db.clone()); + + let user_id = db + .create_user("test".into(), None, false, None) + .await + .unwrap() + .as_id(); + db.create_notification("admin", "admin_list").await.unwrap(); + db.create_notification("all_user", "all_user") + .await + .unwrap(); + + service.mark_read(&user_id, None).await.unwrap(); + let notifications = service.list(&user_id).await.unwrap(); + assert_eq!(notifications.len(), 1); + assert_eq!(notifications[0].read, true); + } +} From b3118120dec0365e324ba2388d2ee422b85b069e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:43:47 +0000 Subject: [PATCH 14/16] [autofix.ci] apply automated fixes --- ee/tabby-schema/graphql/schema.graphql | 4 +-- .../src/service/notification.rs | 27 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ee/tabby-schema/graphql/schema.graphql b/ee/tabby-schema/graphql/schema.graphql index 9c09e84cc936..a2296dd89afa 100644 --- a/ee/tabby-schema/graphql/schema.graphql +++ b/ee/tabby-schema/graphql/schema.graphql @@ -576,7 +576,7 @@ type Mutation { refreshToken(refreshToken: String!): RefreshTokenResponse! createInvitation(email: String!): ID! sendTestEmail(to: String!): Boolean! - markNotificationsReaded(notificationIds: [ID!]!): Boolean! + markNotificationsRead(notificationId: ID): Boolean! createGitRepository(name: String!, gitUrl: String!): ID! deleteGitRepository(id: ID!): Boolean! updateGitRepository(id: ID!, name: String!, gitUrl: String!): Boolean! @@ -722,7 +722,7 @@ type Query { dailyStatsInPastYear(users: [ID!]): [CompletionStats!]! dailyStats(start: DateTime!, end: DateTime!, users: [ID!], languages: [Language!]): [CompletionStats!]! userEvents(after: String, before: String, first: Int, last: Int, users: [ID!], start: DateTime!, end: DateTime!): UserEventConnection! - notifications(readed: Boolean): [Notification!]! + notifications: [Notification!]! diskUsageStats: DiskUsageStats! repositoryList: [Repository!]! contextInfo: ContextInfo! diff --git a/ee/tabby-webserver/src/service/notification.rs b/ee/tabby-webserver/src/service/notification.rs index d4aa7ee53dd7..1cccc5b54494 100644 --- a/ee/tabby-webserver/src/service/notification.rs +++ b/ee/tabby-webserver/src/service/notification.rs @@ -40,11 +40,12 @@ impl NotificationService for NotificationServiceImpl { #[cfg(test)] mod tests { - use super::*; use juniper::ID; use tabby_db::DbConn; use tabby_schema::{notification::NotificationService, AsID}; + use super::*; + #[tokio::test] async fn test_notification_admin_list() { let db = DbConn::new_in_memory().await.unwrap(); @@ -63,9 +64,9 @@ mod tests { let notifications = service.list(&user_id).await.unwrap(); assert_eq!(notifications.len(), 1); - assert_eq!(notifications[0].id, ID::from(notification_id)); + assert_eq!(notifications[0].id, notification_id); assert_eq!(notifications[0].content, "admin_list"); - assert_eq!(notifications[0].read, false); + assert!(!notifications[0].read); } #[tokio::test] @@ -89,7 +90,7 @@ mod tests { assert_eq!(notifications.len(), 1); assert_eq!(notifications[0].id, notification_id.as_id()); assert_eq!(notifications[0].content, "admin_list_read"); - assert_eq!(notifications[0].read, true); + assert!(notifications[0].read); } #[tokio::test] @@ -114,9 +115,9 @@ mod tests { let notifications = service.list(&user_id).await.unwrap(); assert_eq!(notifications.len(), 2); assert_eq!(notifications[0].content, "admin_list"); - assert_eq!(notifications[0].read, false); + assert!(!notifications[0].read); assert_eq!(notifications[1].content, "admin_list_all_user"); - assert_eq!(notifications[1].read, false); + assert!(!notifications[1].read); } #[tokio::test] @@ -134,7 +135,7 @@ mod tests { service.mark_read(&user_id, None).await.unwrap(); let notifications = service.list(&user_id).await.unwrap(); assert_eq!(notifications.len(), 1); - assert_eq!(notifications[0].read, true); + assert!(notifications[0].read); } #[tokio::test] @@ -159,7 +160,7 @@ mod tests { .unwrap(); let notifications = service.list(&user_id).await.unwrap(); assert_eq!(notifications.len(), 1); - assert_eq!(notifications[0].read, true); + assert!(notifications[0].read); assert!(service .mark_read(&user_id, Some(notification_id)) @@ -185,13 +186,13 @@ mod tests { service.mark_read(&user_id, None).await.unwrap(); let notifications = service.list(&user_id).await.unwrap(); assert_eq!(notifications.len(), 1); - assert_eq!(notifications[0].read, true); + assert!(notifications[0].read); // mark all read will not return error even when call twice // but it should not create duplicated notifications service.mark_read(&user_id, None).await.unwrap(); assert_eq!(notifications.len(), 1); - assert_eq!(notifications[0].read, true); + assert!(notifications[0].read); } #[tokio::test] @@ -212,8 +213,8 @@ mod tests { service.mark_read(&user_id, None).await.unwrap(); let notifications = service.list(&user_id).await.unwrap(); assert_eq!(notifications.len(), 2); - assert_eq!(notifications[0].read, true); - assert_eq!(notifications[1].read, true); + assert!(notifications[0].read); + assert!(notifications[1].read); } #[tokio::test] @@ -234,6 +235,6 @@ mod tests { service.mark_read(&user_id, None).await.unwrap(); let notifications = service.list(&user_id).await.unwrap(); assert_eq!(notifications.len(), 1); - assert_eq!(notifications[0].read, true); + assert!(notifications[0].read); } } From b5bba7cc3abff4f512bb8944094cbe9023bf651e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:51:31 +0000 Subject: [PATCH 15/16] [autofix.ci] apply automated fixes (attempt 2/3) --- ee/tabby-webserver/src/service/notification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/tabby-webserver/src/service/notification.rs b/ee/tabby-webserver/src/service/notification.rs index 1cccc5b54494..f6277893fe22 100644 --- a/ee/tabby-webserver/src/service/notification.rs +++ b/ee/tabby-webserver/src/service/notification.rs @@ -40,7 +40,7 @@ impl NotificationService for NotificationServiceImpl { #[cfg(test)] mod tests { - use juniper::ID; + use tabby_db::DbConn; use tabby_schema::{notification::NotificationService, AsID}; From 486fb274b0411dcc5ff374fc93d47c4cbae6347f Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:58:22 +0000 Subject: [PATCH 16/16] [autofix.ci] apply automated fixes (attempt 3/3) --- ee/tabby-webserver/src/service/notification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/tabby-webserver/src/service/notification.rs b/ee/tabby-webserver/src/service/notification.rs index f6277893fe22..0eba4fa2dad5 100644 --- a/ee/tabby-webserver/src/service/notification.rs +++ b/ee/tabby-webserver/src/service/notification.rs @@ -40,7 +40,7 @@ impl NotificationService for NotificationServiceImpl { #[cfg(test)] mod tests { - + use tabby_db::DbConn; use tabby_schema::{notification::NotificationService, AsID};