From e0a9997d7901ec233c5f9b6442d7f97db99393a9 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 9 Jul 2024 03:06:43 +0300 Subject: [PATCH 01/55] chore: Add `chrono` to `oxidetalis_entitys` dependencies Signed-off-by: Awiteb --- Cargo.lock | 1 + crates/oxidetalis_entities/Cargo.toml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8c8c1fc..4b1fc6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1965,6 +1965,7 @@ dependencies = [ name = "oxidetalis_entities" version = "0.1.0" dependencies = [ + "chrono", "sea-orm", ] diff --git a/crates/oxidetalis_entities/Cargo.toml b/crates/oxidetalis_entities/Cargo.toml index b8f6e33..4a444f6 100644 --- a/crates/oxidetalis_entities/Cargo.toml +++ b/crates/oxidetalis_entities/Cargo.toml @@ -11,7 +11,8 @@ rust-version.workspace = true [dependencies] -sea-orm = {workspace = true } +sea-orm = { workspace = true } +chrono = { workspace = true } [lints.rust] unsafe_code = "deny" -- 2.45.2 From 71ae2f91450a0a73ee6e0aeb7099ebd05ceaac9d Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 9 Jul 2024 03:15:16 +0300 Subject: [PATCH 02/55] feat: Outgoing chat request table Signed-off-by: Awiteb --- crates/oxidetalis_entities/src/lib.rs | 1 + .../src/outgoing_chat_requests.rs | 57 +++++++++++++++ crates/oxidetalis_entities/src/prelude.rs | 6 ++ crates/oxidetalis_entities/src/users.rs | 13 +++- .../create_outgoing_chat_requests_table.rs | 71 +++++++++++++++++++ crates/oxidetalis_migrations/src/lib.rs | 6 +- 6 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 crates/oxidetalis_entities/src/outgoing_chat_requests.rs create mode 100644 crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs diff --git a/crates/oxidetalis_entities/src/lib.rs b/crates/oxidetalis_entities/src/lib.rs index 21ae991..649ad31 100644 --- a/crates/oxidetalis_entities/src/lib.rs +++ b/crates/oxidetalis_entities/src/lib.rs @@ -19,5 +19,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +pub mod outgoing_chat_requests; pub mod prelude; pub mod users; diff --git a/crates/oxidetalis_entities/src/outgoing_chat_requests.rs b/crates/oxidetalis_entities/src/outgoing_chat_requests.rs new file mode 100644 index 0000000..35a92b7 --- /dev/null +++ b/crates/oxidetalis_entities/src/outgoing_chat_requests.rs @@ -0,0 +1,57 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use chrono::Utc; +use sea_orm::entity::prelude::*; + +use super::users::Entity as UserEntity; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "out_chat_requests")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub sender_id: i32, + /// Public key of the recipient + pub recipient: String, + /// The timestamp of the request, when it was sent + pub out_on: chrono::DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "UserEntity", + from = "Column::SenderId", + to = "super::users::Column::Id" + on_update = "NoAction", + on_delete = "Cascade" + )] + SenderId, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::SenderId.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/oxidetalis_entities/src/prelude.rs b/crates/oxidetalis_entities/src/prelude.rs index ca81639..adcfb97 100644 --- a/crates/oxidetalis_entities/src/prelude.rs +++ b/crates/oxidetalis_entities/src/prelude.rs @@ -33,6 +33,12 @@ pub use sea_orm::{ SqlErr, }; +pub use super::outgoing_chat_requests::{ + ActiveModel as OutChatRequestsActiveModel, + Column as OutChatRequestsColumn, + Entity as OutChatRequestsEntity, + Model as OutChatRequestsModel, +}; pub use super::users::{ ActiveModel as UserActiveModel, Column as UserColumn, diff --git a/crates/oxidetalis_entities/src/users.rs b/crates/oxidetalis_entities/src/users.rs index 1bae627..0edad50 100644 --- a/crates/oxidetalis_entities/src/users.rs +++ b/crates/oxidetalis_entities/src/users.rs @@ -21,6 +21,8 @@ use sea_orm::entity::prelude::*; +use super::outgoing_chat_requests::Entity as OutChatRequestsEntity; + #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "users")] pub struct Model { @@ -31,6 +33,15 @@ pub struct Model { } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} +pub enum Relation { + #[sea_orm(has_many = "OutChatRequestsEntity")] + OutChatRequests, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::OutChatRequests.def() + } +} impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs new file mode 100644 index 0000000..3db083c --- /dev/null +++ b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs @@ -0,0 +1,71 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(OutChatRequests::Table) + .if_not_exists() + .col( + ColumnDef::new(OutChatRequests::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col( + ColumnDef::new(OutChatRequests::SenderId) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(OutChatRequests::Recipient) + .string() + .not_null(), + ) + .col( + ColumnDef::new(OutChatRequests::OutOn) + .timestamp_with_time_zone() + .not_null(), + ) + .to_owned(), + ) + .await + } +} + +#[derive(DeriveIden)] +enum OutChatRequests { + Table, + Id, + SenderId, + /// Public key of the recipient + Recipient, + OutOn, +} diff --git a/crates/oxidetalis_migrations/src/lib.rs b/crates/oxidetalis_migrations/src/lib.rs index 83f94c0..b2be56c 100644 --- a/crates/oxidetalis_migrations/src/lib.rs +++ b/crates/oxidetalis_migrations/src/lib.rs @@ -21,6 +21,7 @@ pub use sea_orm_migration::prelude::*; +mod create_outgoing_chat_requests_table; mod create_users_table; pub struct Migrator; @@ -28,6 +29,9 @@ pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { - vec![Box::new(create_users_table::Migration)] + vec![ + Box::new(create_users_table::Migration), + Box::new(create_outgoing_chat_requests_table::Migration), + ] } } -- 2.45.2 From 844adbf699dbc9ba87e19e863a1ee803a458a921 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 9 Jul 2024 03:18:41 +0300 Subject: [PATCH 03/55] feat: Incoming chat request table Signed-off-by: Awiteb --- .../src/incoming_chat_requests.rs | 57 ++++++++++++++++ crates/oxidetalis_entities/src/lib.rs | 1 + crates/oxidetalis_entities/src/prelude.rs | 6 ++ crates/oxidetalis_entities/src/users.rs | 9 +++ .../create_incoming_chat_requests_table.rs | 67 +++++++++++++++++++ crates/oxidetalis_migrations/src/lib.rs | 2 + 6 files changed, 142 insertions(+) create mode 100644 crates/oxidetalis_entities/src/incoming_chat_requests.rs create mode 100644 crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs diff --git a/crates/oxidetalis_entities/src/incoming_chat_requests.rs b/crates/oxidetalis_entities/src/incoming_chat_requests.rs new file mode 100644 index 0000000..5e34c7e --- /dev/null +++ b/crates/oxidetalis_entities/src/incoming_chat_requests.rs @@ -0,0 +1,57 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use chrono::Utc; +use sea_orm::entity::prelude::*; + +use super::users::Entity as UserEntity; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "in_chat_requests")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub recipient_id: i32, + /// Public key of the sender + pub sender: String, + /// The timestamp of the request, when it was received + pub in_on: chrono::DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "UserEntity", + from = "Column::RecipientId", + to = "super::users::Column::Id" + on_update = "NoAction", + on_delete = "Cascade" + )] + RecipientId, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::RecipientId.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/oxidetalis_entities/src/lib.rs b/crates/oxidetalis_entities/src/lib.rs index 649ad31..0fe9098 100644 --- a/crates/oxidetalis_entities/src/lib.rs +++ b/crates/oxidetalis_entities/src/lib.rs @@ -19,6 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +pub mod incoming_chat_requests; pub mod outgoing_chat_requests; pub mod prelude; pub mod users; diff --git a/crates/oxidetalis_entities/src/prelude.rs b/crates/oxidetalis_entities/src/prelude.rs index adcfb97..cd34ded 100644 --- a/crates/oxidetalis_entities/src/prelude.rs +++ b/crates/oxidetalis_entities/src/prelude.rs @@ -33,6 +33,12 @@ pub use sea_orm::{ SqlErr, }; +pub use super::incoming_chat_requests::{ + ActiveModel as InChatRequestsActiveModel, + Column as InChatRequestsColumn, + Entity as InChatRequestsEntity, + Model as InChatRequestsModel, +}; pub use super::outgoing_chat_requests::{ ActiveModel as OutChatRequestsActiveModel, Column as OutChatRequestsColumn, diff --git a/crates/oxidetalis_entities/src/users.rs b/crates/oxidetalis_entities/src/users.rs index 0edad50..24a88cd 100644 --- a/crates/oxidetalis_entities/src/users.rs +++ b/crates/oxidetalis_entities/src/users.rs @@ -21,6 +21,7 @@ use sea_orm::entity::prelude::*; +use super::incoming_chat_requests::Entity as InChatRequestsEntity; use super::outgoing_chat_requests::Entity as OutChatRequestsEntity; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] @@ -34,10 +35,18 @@ pub struct Model { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { + #[sea_orm(has_many = "InChatRequestsEntity")] + InChatRequests, #[sea_orm(has_many = "OutChatRequestsEntity")] OutChatRequests, } +impl Related for Entity { + fn to() -> RelationDef { + Relation::InChatRequests.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::OutChatRequests.def() diff --git a/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs new file mode 100644 index 0000000..b9e1ca5 --- /dev/null +++ b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs @@ -0,0 +1,67 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(InChatRequests::Table) + .if_not_exists() + .col( + ColumnDef::new(InChatRequests::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col( + ColumnDef::new(InChatRequests::RecipientId) + .integer() + .not_null(), + ) + .col(ColumnDef::new(InChatRequests::Sender).string().not_null()) + .col( + ColumnDef::new(InChatRequests::InOn) + .timestamp_with_time_zone() + .not_null(), + ) + .to_owned(), + ) + .await + } +} + +#[derive(DeriveIden)] +enum InChatRequests { + Table, + Id, + RecipientId, + /// Public key of the sender + Sender, + InOn, +} diff --git a/crates/oxidetalis_migrations/src/lib.rs b/crates/oxidetalis_migrations/src/lib.rs index b2be56c..8a1009f 100644 --- a/crates/oxidetalis_migrations/src/lib.rs +++ b/crates/oxidetalis_migrations/src/lib.rs @@ -21,6 +21,7 @@ pub use sea_orm_migration::prelude::*; +mod create_incoming_chat_requests_table; mod create_outgoing_chat_requests_table; mod create_users_table; @@ -31,6 +32,7 @@ impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ Box::new(create_users_table::Migration), + Box::new(create_incoming_chat_requests_table::Migration), Box::new(create_outgoing_chat_requests_table::Migration), ] } -- 2.45.2 From 9215d836de14e904e5acc8e364756a88eb96b056 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 9 Jul 2024 11:16:57 +0300 Subject: [PATCH 04/55] feat: Blacklist table Signed-off-by: Awiteb --- crates/oxidetalis_entities/src/blacklist.rs | 56 ++++++++++++++++ crates/oxidetalis_entities/src/lib.rs | 1 + crates/oxidetalis_entities/src/prelude.rs | 6 ++ crates/oxidetalis_entities/src/users.rs | 9 +++ .../src/create_blacklist_table.rs | 65 +++++++++++++++++++ crates/oxidetalis_migrations/src/lib.rs | 2 + 6 files changed, 139 insertions(+) create mode 100644 crates/oxidetalis_entities/src/blacklist.rs create mode 100644 crates/oxidetalis_migrations/src/create_blacklist_table.rs diff --git a/crates/oxidetalis_entities/src/blacklist.rs b/crates/oxidetalis_entities/src/blacklist.rs new file mode 100644 index 0000000..6d955a6 --- /dev/null +++ b/crates/oxidetalis_entities/src/blacklist.rs @@ -0,0 +1,56 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use chrono::Utc; +use sea_orm::entity::prelude::*; + +use super::users::Entity as UserEntity; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "blacklist")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub user_id: i32, + /// Public key of the target + pub target: String, + pub blacklisted_at: chrono::DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "UserEntity", + from = "Column::UserId", + to = "super::users::Column::Id" + on_update = "NoAction", + on_delete = "Cascade" + )] + UserId, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UserId.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/oxidetalis_entities/src/lib.rs b/crates/oxidetalis_entities/src/lib.rs index 0fe9098..e758d98 100644 --- a/crates/oxidetalis_entities/src/lib.rs +++ b/crates/oxidetalis_entities/src/lib.rs @@ -19,6 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +pub mod blacklist; pub mod incoming_chat_requests; pub mod outgoing_chat_requests; pub mod prelude; diff --git a/crates/oxidetalis_entities/src/prelude.rs b/crates/oxidetalis_entities/src/prelude.rs index cd34ded..8d0b50e 100644 --- a/crates/oxidetalis_entities/src/prelude.rs +++ b/crates/oxidetalis_entities/src/prelude.rs @@ -33,6 +33,12 @@ pub use sea_orm::{ SqlErr, }; +pub use super::blacklist::{ + ActiveModel as BlacklistActiveModel, + Column as BlacklistColumn, + Entity as BlacklistEntity, + Model as BlacklistModel, +}; pub use super::incoming_chat_requests::{ ActiveModel as InChatRequestsActiveModel, Column as InChatRequestsColumn, diff --git a/crates/oxidetalis_entities/src/users.rs b/crates/oxidetalis_entities/src/users.rs index 24a88cd..9842826 100644 --- a/crates/oxidetalis_entities/src/users.rs +++ b/crates/oxidetalis_entities/src/users.rs @@ -21,6 +21,7 @@ use sea_orm::entity::prelude::*; +use super::blacklist::Entity as BlacklistEntity; use super::incoming_chat_requests::Entity as InChatRequestsEntity; use super::outgoing_chat_requests::Entity as OutChatRequestsEntity; @@ -39,6 +40,8 @@ pub enum Relation { InChatRequests, #[sea_orm(has_many = "OutChatRequestsEntity")] OutChatRequests, + #[sea_orm(has_many = "BlacklistEntity")] + Blacklist, } impl Related for Entity { @@ -53,4 +56,10 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Blacklist.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/oxidetalis_migrations/src/create_blacklist_table.rs b/crates/oxidetalis_migrations/src/create_blacklist_table.rs new file mode 100644 index 0000000..e459d2e --- /dev/null +++ b/crates/oxidetalis_migrations/src/create_blacklist_table.rs @@ -0,0 +1,65 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Blacklist::Table) + .if_not_exists() + .col( + ColumnDef::new(Blacklist::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(Blacklist::UserId).integer().not_null()) + .col(ColumnDef::new(Blacklist::Target).string().not_null()) + .col(ColumnDef::new(Blacklist::Reason).string_len(400)) + .col( + ColumnDef::new(Blacklist::BlacklistedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .to_owned(), + ) + .await + } +} + +#[derive(DeriveIden)] +enum Blacklist { + Table, + Id, + UserId, + /// Public key of the target + Target, + Reason, + BlacklistedAt, +} diff --git a/crates/oxidetalis_migrations/src/lib.rs b/crates/oxidetalis_migrations/src/lib.rs index 8a1009f..b50125c 100644 --- a/crates/oxidetalis_migrations/src/lib.rs +++ b/crates/oxidetalis_migrations/src/lib.rs @@ -21,6 +21,7 @@ pub use sea_orm_migration::prelude::*; +mod create_blacklist_table; mod create_incoming_chat_requests_table; mod create_outgoing_chat_requests_table; mod create_users_table; @@ -34,6 +35,7 @@ impl MigratorTrait for Migrator { Box::new(create_users_table::Migration), Box::new(create_incoming_chat_requests_table::Migration), Box::new(create_outgoing_chat_requests_table::Migration), + Box::new(create_blacklist_table::Migration), ] } } -- 2.45.2 From b899dd6ac5bcabd49cd8d474119b6f3d2b6d8faf Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 9 Jul 2024 13:08:32 +0300 Subject: [PATCH 05/55] feat: Whitelist table Signed-off-by: Awiteb --- crates/oxidetalis_entities/src/lib.rs | 1 + crates/oxidetalis_entities/src/prelude.rs | 6 ++ crates/oxidetalis_entities/src/users.rs | 9 +++ crates/oxidetalis_entities/src/whitelist.rs | 56 +++++++++++++++++ .../src/create_whitelist_table.rs | 63 +++++++++++++++++++ crates/oxidetalis_migrations/src/lib.rs | 2 + 6 files changed, 137 insertions(+) create mode 100644 crates/oxidetalis_entities/src/whitelist.rs create mode 100644 crates/oxidetalis_migrations/src/create_whitelist_table.rs diff --git a/crates/oxidetalis_entities/src/lib.rs b/crates/oxidetalis_entities/src/lib.rs index e758d98..3c72884 100644 --- a/crates/oxidetalis_entities/src/lib.rs +++ b/crates/oxidetalis_entities/src/lib.rs @@ -24,3 +24,4 @@ pub mod incoming_chat_requests; pub mod outgoing_chat_requests; pub mod prelude; pub mod users; +pub mod whitelist; diff --git a/crates/oxidetalis_entities/src/prelude.rs b/crates/oxidetalis_entities/src/prelude.rs index 8d0b50e..6d045cb 100644 --- a/crates/oxidetalis_entities/src/prelude.rs +++ b/crates/oxidetalis_entities/src/prelude.rs @@ -57,3 +57,9 @@ pub use super::users::{ Entity as UserEntity, Model as UserModel, }; +pub use super::whitelist::{ + ActiveModel as WhitelistActiveModel, + Column as WhitelistColumn, + Entity as WhitelistEntity, + Model as WhitelistModel, +}; diff --git a/crates/oxidetalis_entities/src/users.rs b/crates/oxidetalis_entities/src/users.rs index 9842826..c4bf38a 100644 --- a/crates/oxidetalis_entities/src/users.rs +++ b/crates/oxidetalis_entities/src/users.rs @@ -24,6 +24,7 @@ use sea_orm::entity::prelude::*; use super::blacklist::Entity as BlacklistEntity; use super::incoming_chat_requests::Entity as InChatRequestsEntity; use super::outgoing_chat_requests::Entity as OutChatRequestsEntity; +use super::whitelist::Entity as WhitelistEntity; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "users")] @@ -42,6 +43,8 @@ pub enum Relation { OutChatRequests, #[sea_orm(has_many = "BlacklistEntity")] Blacklist, + #[sea_orm(has_many = "WhitelistEntity")] + Whitelist, } impl Related for Entity { @@ -62,4 +65,10 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Whitelist.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/oxidetalis_entities/src/whitelist.rs b/crates/oxidetalis_entities/src/whitelist.rs new file mode 100644 index 0000000..2a72197 --- /dev/null +++ b/crates/oxidetalis_entities/src/whitelist.rs @@ -0,0 +1,56 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use chrono::Utc; +use sea_orm::entity::prelude::*; + +use super::users::Entity as UserEntity; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "whitelist")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub user_id: i32, + /// Public key of the target + pub target: String, + pub whitelisted_at: chrono::DateTime, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "UserEntity", + from = "Column::UserId", + to = "super::users::Column::Id" + on_update = "NoAction", + on_delete = "Cascade" + )] + UserId, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UserId.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/oxidetalis_migrations/src/create_whitelist_table.rs b/crates/oxidetalis_migrations/src/create_whitelist_table.rs new file mode 100644 index 0000000..027c363 --- /dev/null +++ b/crates/oxidetalis_migrations/src/create_whitelist_table.rs @@ -0,0 +1,63 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (c) 2024 OxideTalis Developers +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(Whitelist::Table) + .if_not_exists() + .col( + ColumnDef::new(Whitelist::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(Whitelist::UserId).integer().not_null()) + .col(ColumnDef::new(Whitelist::Target).string().not_null()) + .col( + ColumnDef::new(Whitelist::WhitelistedAt) + .timestamp_with_time_zone() + .not_null(), + ) + .to_owned(), + ) + .await + } +} + +#[derive(DeriveIden)] +enum Whitelist { + Table, + Id, + UserId, + /// Public key of the target + Target, + WhitelistedAt, +} diff --git a/crates/oxidetalis_migrations/src/lib.rs b/crates/oxidetalis_migrations/src/lib.rs index b50125c..0108570 100644 --- a/crates/oxidetalis_migrations/src/lib.rs +++ b/crates/oxidetalis_migrations/src/lib.rs @@ -25,6 +25,7 @@ mod create_blacklist_table; mod create_incoming_chat_requests_table; mod create_outgoing_chat_requests_table; mod create_users_table; +mod create_whitelist_table; pub struct Migrator; @@ -36,6 +37,7 @@ impl MigratorTrait for Migrator { Box::new(create_incoming_chat_requests_table::Migration), Box::new(create_outgoing_chat_requests_table::Migration), Box::new(create_blacklist_table::Migration), + Box::new(create_whitelist_table::Migration), ] } } -- 2.45.2 From b712f96f59a687078e7341479bb5a0129852c653 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 9 Jul 2024 14:09:03 +0300 Subject: [PATCH 06/55] change: Change the user id from `integer` to `bigint` Signed-off-by: Awiteb --- crates/oxidetalis_entities/src/blacklist.rs | 5 +++-- crates/oxidetalis_entities/src/incoming_chat_requests.rs | 5 +++-- crates/oxidetalis_entities/src/outgoing_chat_requests.rs | 5 +++-- crates/oxidetalis_entities/src/prelude.rs | 3 +++ crates/oxidetalis_entities/src/users.rs | 3 ++- crates/oxidetalis_entities/src/whitelist.rs | 5 +++-- crates/oxidetalis_migrations/src/create_blacklist_table.rs | 4 ++-- .../src/create_incoming_chat_requests_table.rs | 4 ++-- .../src/create_outgoing_chat_requests_table.rs | 4 ++-- crates/oxidetalis_migrations/src/create_users_table.rs | 2 +- crates/oxidetalis_migrations/src/create_whitelist_table.rs | 4 ++-- 11 files changed, 26 insertions(+), 18 deletions(-) diff --git a/crates/oxidetalis_entities/src/blacklist.rs b/crates/oxidetalis_entities/src/blacklist.rs index 6d955a6..30b5ad2 100644 --- a/crates/oxidetalis_entities/src/blacklist.rs +++ b/crates/oxidetalis_entities/src/blacklist.rs @@ -23,13 +23,14 @@ use chrono::Utc; use sea_orm::entity::prelude::*; use super::users::Entity as UserEntity; +use crate::prelude::UserId; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "blacklist")] pub struct Model { #[sea_orm(primary_key)] - pub id: i32, - pub user_id: i32, + pub id: UserId, + pub user_id: UserId, /// Public key of the target pub target: String, pub blacklisted_at: chrono::DateTime, diff --git a/crates/oxidetalis_entities/src/incoming_chat_requests.rs b/crates/oxidetalis_entities/src/incoming_chat_requests.rs index 5e34c7e..d3e16e4 100644 --- a/crates/oxidetalis_entities/src/incoming_chat_requests.rs +++ b/crates/oxidetalis_entities/src/incoming_chat_requests.rs @@ -23,13 +23,14 @@ use chrono::Utc; use sea_orm::entity::prelude::*; use super::users::Entity as UserEntity; +use crate::prelude::UserId; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "in_chat_requests")] pub struct Model { #[sea_orm(primary_key)] - pub id: i32, - pub recipient_id: i32, + pub id: UserId, + pub recipient_id: UserId, /// Public key of the sender pub sender: String, /// The timestamp of the request, when it was received diff --git a/crates/oxidetalis_entities/src/outgoing_chat_requests.rs b/crates/oxidetalis_entities/src/outgoing_chat_requests.rs index 35a92b7..6df5ed0 100644 --- a/crates/oxidetalis_entities/src/outgoing_chat_requests.rs +++ b/crates/oxidetalis_entities/src/outgoing_chat_requests.rs @@ -23,13 +23,14 @@ use chrono::Utc; use sea_orm::entity::prelude::*; use super::users::Entity as UserEntity; +use crate::prelude::UserId; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "out_chat_requests")] pub struct Model { #[sea_orm(primary_key)] - pub id: i32, - pub sender_id: i32, + pub id: UserId, + pub sender_id: UserId, /// Public key of the recipient pub recipient: String, /// The timestamp of the request, when it was sent diff --git a/crates/oxidetalis_entities/src/prelude.rs b/crates/oxidetalis_entities/src/prelude.rs index 6d045cb..67477f1 100644 --- a/crates/oxidetalis_entities/src/prelude.rs +++ b/crates/oxidetalis_entities/src/prelude.rs @@ -33,6 +33,9 @@ pub use sea_orm::{ SqlErr, }; +/// User ID type +pub type UserId = i64; + pub use super::blacklist::{ ActiveModel as BlacklistActiveModel, Column as BlacklistColumn, diff --git a/crates/oxidetalis_entities/src/users.rs b/crates/oxidetalis_entities/src/users.rs index c4bf38a..f33a285 100644 --- a/crates/oxidetalis_entities/src/users.rs +++ b/crates/oxidetalis_entities/src/users.rs @@ -25,12 +25,13 @@ use super::blacklist::Entity as BlacklistEntity; use super::incoming_chat_requests::Entity as InChatRequestsEntity; use super::outgoing_chat_requests::Entity as OutChatRequestsEntity; use super::whitelist::Entity as WhitelistEntity; +use crate::prelude::UserId; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "users")] pub struct Model { #[sea_orm(primary_key)] - pub id: i32, + pub id: UserId, pub public_key: String, pub is_admin: bool, } diff --git a/crates/oxidetalis_entities/src/whitelist.rs b/crates/oxidetalis_entities/src/whitelist.rs index 2a72197..1f4d944 100644 --- a/crates/oxidetalis_entities/src/whitelist.rs +++ b/crates/oxidetalis_entities/src/whitelist.rs @@ -23,13 +23,14 @@ use chrono::Utc; use sea_orm::entity::prelude::*; use super::users::Entity as UserEntity; +use crate::prelude::UserId; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "whitelist")] pub struct Model { #[sea_orm(primary_key)] - pub id: i32, - pub user_id: i32, + pub id: UserId, + pub user_id: UserId, /// Public key of the target pub target: String, pub whitelisted_at: chrono::DateTime, diff --git a/crates/oxidetalis_migrations/src/create_blacklist_table.rs b/crates/oxidetalis_migrations/src/create_blacklist_table.rs index e459d2e..2eaf805 100644 --- a/crates/oxidetalis_migrations/src/create_blacklist_table.rs +++ b/crates/oxidetalis_migrations/src/create_blacklist_table.rs @@ -34,12 +34,12 @@ impl MigrationTrait for Migration { .if_not_exists() .col( ColumnDef::new(Blacklist::Id) - .integer() + .big_integer() .not_null() .auto_increment() .primary_key(), ) - .col(ColumnDef::new(Blacklist::UserId).integer().not_null()) + .col(ColumnDef::new(Blacklist::UserId).big_integer().not_null()) .col(ColumnDef::new(Blacklist::Target).string().not_null()) .col(ColumnDef::new(Blacklist::Reason).string_len(400)) .col( diff --git a/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs index b9e1ca5..7289225 100644 --- a/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs +++ b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs @@ -34,14 +34,14 @@ impl MigrationTrait for Migration { .if_not_exists() .col( ColumnDef::new(InChatRequests::Id) - .integer() + .big_integer() .not_null() .auto_increment() .primary_key(), ) .col( ColumnDef::new(InChatRequests::RecipientId) - .integer() + .big_integer() .not_null(), ) .col(ColumnDef::new(InChatRequests::Sender).string().not_null()) diff --git a/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs index 3db083c..7db4b81 100644 --- a/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs +++ b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs @@ -34,14 +34,14 @@ impl MigrationTrait for Migration { .if_not_exists() .col( ColumnDef::new(OutChatRequests::Id) - .integer() + .big_integer() .not_null() .auto_increment() .primary_key(), ) .col( ColumnDef::new(OutChatRequests::SenderId) - .integer() + .big_integer() .not_null(), ) .col( diff --git a/crates/oxidetalis_migrations/src/create_users_table.rs b/crates/oxidetalis_migrations/src/create_users_table.rs index 71e771e..6e1b671 100644 --- a/crates/oxidetalis_migrations/src/create_users_table.rs +++ b/crates/oxidetalis_migrations/src/create_users_table.rs @@ -34,7 +34,7 @@ impl MigrationTrait for Migration { .if_not_exists() .col( ColumnDef::new(Users::Id) - .integer() + .big_integer() .not_null() .auto_increment() .primary_key(), diff --git a/crates/oxidetalis_migrations/src/create_whitelist_table.rs b/crates/oxidetalis_migrations/src/create_whitelist_table.rs index 027c363..d29f837 100644 --- a/crates/oxidetalis_migrations/src/create_whitelist_table.rs +++ b/crates/oxidetalis_migrations/src/create_whitelist_table.rs @@ -34,12 +34,12 @@ impl MigrationTrait for Migration { .if_not_exists() .col( ColumnDef::new(Whitelist::Id) - .integer() + .big_integer() .not_null() .auto_increment() .primary_key(), ) - .col(ColumnDef::new(Whitelist::UserId).integer().not_null()) + .col(ColumnDef::new(Whitelist::UserId).big_integer().not_null()) .col(ColumnDef::new(Whitelist::Target).string().not_null()) .col( ColumnDef::new(Whitelist::WhitelistedAt) -- 2.45.2 From 2bac5be8c84c8fbd8021ee84a6b04d1a6227efad Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 9 Jul 2024 14:15:12 +0300 Subject: [PATCH 07/55] chore: Use entities prelude Signed-off-by: Awiteb --- crates/oxidetalis_entities/src/blacklist.rs | 3 +-- crates/oxidetalis_entities/src/incoming_chat_requests.rs | 3 +-- crates/oxidetalis_entities/src/outgoing_chat_requests.rs | 3 +-- crates/oxidetalis_entities/src/users.rs | 6 +----- crates/oxidetalis_entities/src/whitelist.rs | 3 +-- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/crates/oxidetalis_entities/src/blacklist.rs b/crates/oxidetalis_entities/src/blacklist.rs index 30b5ad2..848432d 100644 --- a/crates/oxidetalis_entities/src/blacklist.rs +++ b/crates/oxidetalis_entities/src/blacklist.rs @@ -22,8 +22,7 @@ use chrono::Utc; use sea_orm::entity::prelude::*; -use super::users::Entity as UserEntity; -use crate::prelude::UserId; +use crate::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "blacklist")] diff --git a/crates/oxidetalis_entities/src/incoming_chat_requests.rs b/crates/oxidetalis_entities/src/incoming_chat_requests.rs index d3e16e4..2d363cd 100644 --- a/crates/oxidetalis_entities/src/incoming_chat_requests.rs +++ b/crates/oxidetalis_entities/src/incoming_chat_requests.rs @@ -22,8 +22,7 @@ use chrono::Utc; use sea_orm::entity::prelude::*; -use super::users::Entity as UserEntity; -use crate::prelude::UserId; +use crate::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "in_chat_requests")] diff --git a/crates/oxidetalis_entities/src/outgoing_chat_requests.rs b/crates/oxidetalis_entities/src/outgoing_chat_requests.rs index 6df5ed0..d083e3e 100644 --- a/crates/oxidetalis_entities/src/outgoing_chat_requests.rs +++ b/crates/oxidetalis_entities/src/outgoing_chat_requests.rs @@ -22,8 +22,7 @@ use chrono::Utc; use sea_orm::entity::prelude::*; -use super::users::Entity as UserEntity; -use crate::prelude::UserId; +use crate::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "out_chat_requests")] diff --git a/crates/oxidetalis_entities/src/users.rs b/crates/oxidetalis_entities/src/users.rs index f33a285..cd6b172 100644 --- a/crates/oxidetalis_entities/src/users.rs +++ b/crates/oxidetalis_entities/src/users.rs @@ -21,11 +21,7 @@ use sea_orm::entity::prelude::*; -use super::blacklist::Entity as BlacklistEntity; -use super::incoming_chat_requests::Entity as InChatRequestsEntity; -use super::outgoing_chat_requests::Entity as OutChatRequestsEntity; -use super::whitelist::Entity as WhitelistEntity; -use crate::prelude::UserId; +use crate::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "users")] diff --git a/crates/oxidetalis_entities/src/whitelist.rs b/crates/oxidetalis_entities/src/whitelist.rs index 1f4d944..b1bee22 100644 --- a/crates/oxidetalis_entities/src/whitelist.rs +++ b/crates/oxidetalis_entities/src/whitelist.rs @@ -22,8 +22,7 @@ use chrono::Utc; use sea_orm::entity::prelude::*; -use super::users::Entity as UserEntity; -use crate::prelude::UserId; +use crate::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "whitelist")] -- 2.45.2 From 33b44cb25602e6b78fc9ff4c3ac454c34f5c8aa4 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 9 Jul 2024 19:23:49 +0300 Subject: [PATCH 08/55] chore: Impl `AsRef` for `ServerEvent` Signed-off-by: Awiteb --- crates/oxidetalis/src/extensions.rs | 9 +++++---- crates/oxidetalis/src/websocket/events/server.rs | 6 ++++++ crates/oxidetalis/src/websocket/mod.rs | 16 ++++++++++------ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/crates/oxidetalis/src/extensions.rs b/crates/oxidetalis/src/extensions.rs index 3f4415e..eeee250 100644 --- a/crates/oxidetalis/src/extensions.rs +++ b/crates/oxidetalis/src/extensions.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use chrono::Utc; use oxidetalis_config::Config; use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; -use salvo::{websocket::Message, Depot}; +use salvo::Depot; use sea_orm::DatabaseConnection; use uuid::Uuid; @@ -87,9 +87,10 @@ impl OnlineUsersExt for OnlineUsers { let now = Utc::now(); self.write().await.par_iter_mut().for_each(|(_, u)| { u.pinged_at = now; - let _ = u.sender.unbounded_send(Ok(Message::from( - &ServerEvent::ping().sign(&u.shared_secret), - ))); + let _ = u.sender.unbounded_send(Ok(ServerEvent::ping() + .sign(&u.shared_secret) + .as_ref() + .into())); }); } diff --git a/crates/oxidetalis/src/websocket/events/server.rs b/crates/oxidetalis/src/websocket/events/server.rs index 225b129..9bd990a 100644 --- a/crates/oxidetalis/src/websocket/events/server.rs +++ b/crates/oxidetalis/src/websocket/events/server.rs @@ -101,6 +101,12 @@ impl ServerEvent { } } +impl AsRef for ServerEvent { + fn as_ref(&self) -> &Self { + self + } +} + impl From<&ServerEvent> for Message { fn from(value: &ServerEvent) -> Self { Message::text(serde_json::to_string(value).expect("This can't fail")) diff --git a/crates/oxidetalis/src/websocket/mod.rs b/crates/oxidetalis/src/websocket/mod.rs index c91479e..ed4a901 100644 --- a/crates/oxidetalis/src/websocket/mod.rs +++ b/crates/oxidetalis/src/websocket/mod.rs @@ -132,18 +132,22 @@ async fn handle_socket( match handle_ws_msg(msg, &nonce_cache, &user_shared_secret).await { Ok(event) => { if let Some(server_event) = handle_events(event, &conn_id).await { - if let Err(err) = sender.unbounded_send(Ok(Message::from( - &server_event.sign(&user_shared_secret), - ))) { + if let Err(err) = sender.unbounded_send(Ok(server_event + .sign(&user_shared_secret) + .as_ref() + .into())) + { log::error!("Websocket Error: {err}"); break; } }; } Err(err) => { - if let Err(err) = sender.unbounded_send(Ok(Message::from( - &ServerEvent::from(err).sign(&user_shared_secret), - ))) { + if let Err(err) = sender.unbounded_send(Ok(ServerEvent::from(err) + .sign(&user_shared_secret) + .as_ref() + .into())) + { log::error!("Websocket Error: {err}"); break; }; -- 2.45.2 From 033a21f733f0b868a6599573b85fb89c16176d71 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 10 Jul 2024 20:16:55 +0300 Subject: [PATCH 09/55] chore: Function to return user by its public key Signed-off-by: Awiteb --- crates/oxidetalis/src/database/user.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/oxidetalis/src/database/user.rs b/crates/oxidetalis/src/database/user.rs index 7f52764..3230aa3 100644 --- a/crates/oxidetalis/src/database/user.rs +++ b/crates/oxidetalis/src/database/user.rs @@ -28,6 +28,8 @@ pub trait UserTableExt { async fn users_exists_in_database(&self) -> ApiResult; /// Register new user async fn register_user(&self, public_key: &PublicKey, is_admin: bool) -> ApiResult<()>; + /// Returns user by its public key + async fn get_user_by_pubk(&self, public_key: &PublicKey) -> ApiResult>; } impl UserTableExt for DatabaseConnection { @@ -57,4 +59,13 @@ impl UserTableExt for DatabaseConnection { Ok(()) } + + #[logcall] + async fn get_user_by_pubk(&self, public_key: &PublicKey) -> ApiResult> { + UserEntity::find() + .filter(UserColumn::PublicKey.eq(public_key.to_string())) + .one(self) + .await + .map_err(ApiError::SeaOrm) + } } -- 2.45.2 From b4b3b537fd33fbf1155a5ad9407c3898a2d7bfa7 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 10 Jul 2024 20:23:29 +0300 Subject: [PATCH 10/55] feat: Websocket error macro `ws_errors` macro to make adding new errors easy Signed-off-by: Awiteb --- crates/oxidetalis/src/macros.rs | 59 +++++++++++++++++++++++ crates/oxidetalis/src/main.rs | 1 + crates/oxidetalis/src/websocket/errors.rs | 42 +++------------- 3 files changed, 67 insertions(+), 35 deletions(-) create mode 100644 crates/oxidetalis/src/macros.rs diff --git a/crates/oxidetalis/src/macros.rs b/crates/oxidetalis/src/macros.rs new file mode 100644 index 0000000..26a8732 --- /dev/null +++ b/crates/oxidetalis/src/macros.rs @@ -0,0 +1,59 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! OxideTalis server macros, to make the code more readable and easier to +//! write. + +/// Macro to create the `WsError` enum with the given error names and reasons. +/// +/// ## Example +/// ```rust,ignore +/// ws_errors! { +/// FirstError = "This is the first error", +/// SecondError = "This is the second error", +/// } +/// ``` +#[macro_export] +macro_rules! ws_errors { + ($($name:ident = $reason:tt),+ $(,)?) => { + #[derive(Debug)] + #[doc = "Websocket errors, returned in the websocket communication"] + pub enum WsError { + $( + #[doc = $reason] + $name + ),+ + } + impl WsError { + #[doc = "Returns error name"] + pub const fn name(&self) -> &'static str { + match self { + $( + WsError::$name => stringify!($name) + ),+ + } + } + #[doc = "Returns the error reason"] + pub const fn reason(&self) -> &'static str { + match self { + $( + WsError::$name => $reason + ),+ + } + } + } + }; +} diff --git a/crates/oxidetalis/src/main.rs b/crates/oxidetalis/src/main.rs index 53ad66d..b15cfb5 100644 --- a/crates/oxidetalis/src/main.rs +++ b/crates/oxidetalis/src/main.rs @@ -26,6 +26,7 @@ use salvo::{conn::TcpListener, Listener, Server}; mod database; mod errors; mod extensions; +mod macros; mod middlewares; mod nonce; mod routes; diff --git a/crates/oxidetalis/src/websocket/errors.rs b/crates/oxidetalis/src/websocket/errors.rs index b05e550..e2fab4b 100644 --- a/crates/oxidetalis/src/websocket/errors.rs +++ b/crates/oxidetalis/src/websocket/errors.rs @@ -16,42 +16,14 @@ //! Websocket errors +use crate::ws_errors; + /// Result type of websocket pub type WsResult = Result; -/// Websocket errors, returned in the websocket communication -#[derive(Debug)] -pub enum WsError { - /// The signature is invalid - InvalidSignature, - /// Message type must be text - NotTextMessage, - /// Invalid json data - InvalidJsonData, - /// Unknown client event - UnknownClientEvent, -} - -impl WsError { - /// Returns error name - pub const fn name(&self) -> &'static str { - match self { - WsError::InvalidSignature => "InvalidSignature", - WsError::NotTextMessage => "NotTextMessage", - WsError::InvalidJsonData => "InvalidJsonData", - WsError::UnknownClientEvent => "UnknownClientEvent", - } - } - - /// Returns the error reason - pub const fn reason(&self) -> &'static str { - match self { - WsError::InvalidSignature => "Invalid event signature", - WsError::NotTextMessage => "The websocket message must be text message", - WsError::InvalidJsonData => "Received invalid json data, the text must be valid json", - WsError::UnknownClientEvent => { - "Unknown client event, the event is not recognized by the server" - } - } - } +ws_errors! { + InvalidSignature = "Invalid event signature", + NotTextMessage = "The websocket message must be text message", + InvalidJsonData = "Received invalid json data, the text must be valid json", + UnknownClientEvent = "Unknown client event, the event is not recognized by the server", } -- 2.45.2 From 65d6b147d9c78291fce67bb5f76cde0be4048dfc Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 10 Jul 2024 20:24:47 +0300 Subject: [PATCH 11/55] chore: Returns database connection as `Arc` Signed-off-by: Awiteb --- crates/oxidetalis/src/extensions.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/oxidetalis/src/extensions.rs b/crates/oxidetalis/src/extensions.rs index eeee250..f0fa891 100644 --- a/crates/oxidetalis/src/extensions.rs +++ b/crates/oxidetalis/src/extensions.rs @@ -31,7 +31,7 @@ use crate::{ /// Extension trait for the Depot. pub trait DepotExt { /// Returns the database connection - fn db_conn(&self) -> &DatabaseConnection; + fn db_conn(&self) -> Arc; /// Returns the server configuration fn config(&self) -> &Config; /// Retutns the nonce cache @@ -57,9 +57,11 @@ pub trait OnlineUsersExt { } impl DepotExt for Depot { - fn db_conn(&self) -> &DatabaseConnection { - self.obtain::>() - .expect("Database connection not found") + fn db_conn(&self) -> Arc { + Arc::clone( + self.obtain::>() + .expect("Database connection not found"), + ) } fn config(&self) -> &Config { -- 2.45.2 From 226df1961bca890d580cb6c8b4a41d67bbc76d18 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 10 Jul 2024 20:25:56 +0300 Subject: [PATCH 12/55] chore: Use `sea_orm::ModelTrait` in entities prelude Signed-off-by: Awiteb --- crates/oxidetalis_entities/src/prelude.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/oxidetalis_entities/src/prelude.rs b/crates/oxidetalis_entities/src/prelude.rs index 67477f1..8d23382 100644 --- a/crates/oxidetalis_entities/src/prelude.rs +++ b/crates/oxidetalis_entities/src/prelude.rs @@ -24,6 +24,7 @@ pub use sea_orm::{ ColumnTrait, EntityTrait, IntoActiveModel, + ModelTrait, Order, PaginatorTrait, QueryFilter, -- 2.45.2 From b72fb8e7f6e06f0560aea77eeb70a7f5a29d4755 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Fri, 12 Jul 2024 22:48:54 +0300 Subject: [PATCH 13/55] feat: Macro to try the websocket error Signed-off-by: Awiteb --- crates/oxidetalis/src/macros.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/oxidetalis/src/macros.rs b/crates/oxidetalis/src/macros.rs index 26a8732..54c7149 100644 --- a/crates/oxidetalis/src/macros.rs +++ b/crates/oxidetalis/src/macros.rs @@ -17,6 +17,37 @@ //! OxideTalis server macros, to make the code more readable and easier to //! write. +/// Macro to return a [`ServerEvent`] with a [`WsError::InternalServerError`] if +/// the result of an expression is an [`Err`]. +/// +/// ## Example +/// ```rust,ignore +/// fn example() -> ServerEvent { +/// // some_function() returns a Result, if it's an Err, return an +/// // ServerEvent::InternalServerError +/// let result = try_ws!(some_function()); +/// ServerEvent::from(result) +/// } +/// ``` +/// +/// [`ServerEvent`]: crate::websocket::ServerEvent +/// [`WsError::InternalServerError`]: crate::websocket::errors::WsError::InternalServerError +/// [`Err`]: std::result::Result::Err +#[macro_export] +macro_rules! try_ws { + ($result_expr:expr) => { + match $result_expr { + Ok(val) => val, + Err(err) => { + log::error!("Error in try_ws macro: {err:?}"); + return $crate::websocket::ServerEvent::<$crate::websocket::Unsigned>::from( + $crate::websocket::errors::WsError::InternalServerError, + ); + } + } + }; +} + /// Macro to create the `WsError` enum with the given error names and reasons. /// /// ## Example -- 2.45.2 From 4de83b02cd4f86f9b8c4a9bdf78acc5a9632b621 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Fri, 12 Jul 2024 22:50:24 +0300 Subject: [PATCH 14/55] chore: Functions to send an event to user & check if it is online Signed-off-by: Awiteb --- crates/oxidetalis/src/extensions.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/oxidetalis/src/extensions.rs b/crates/oxidetalis/src/extensions.rs index f0fa891..558c948 100644 --- a/crates/oxidetalis/src/extensions.rs +++ b/crates/oxidetalis/src/extensions.rs @@ -18,6 +18,7 @@ use std::sync::Arc; use chrono::Utc; use oxidetalis_config::Config; +use oxidetalis_core::types::PublicKey; use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use salvo::Depot; use sea_orm::DatabaseConnection; @@ -25,7 +26,7 @@ use uuid::Uuid; use crate::{ nonce::NonceCache, - websocket::{OnlineUsers, ServerEvent, SocketUserData}, + websocket::{OnlineUsers, ServerEvent, SocketUserData, Unsigned}, }; /// Extension trait for the Depot. @@ -54,6 +55,12 @@ pub trait OnlineUsersExt { /// Disconnect inactive users (who not respond for the ping event) async fn disconnect_inactive_users(&self); + + /// Returns the connection id of the user, if it is online + async fn is_online(&self, public_key: &PublicKey) -> Option; + + /// Send an event to user by connection id + async fn send(&self, conn_id: &Uuid, event: ServerEvent); } impl DepotExt for Depot { @@ -113,4 +120,20 @@ impl OnlineUsersExt for OnlineUsers { true }); } + + async fn is_online(&self, public_key: &PublicKey) -> Option { + self.read() + .await + .iter() + .find(|(_, u)| &u.public_key == public_key) + .map(|(c, _)| *c) + } + + async fn send(&self, conn_id: &Uuid, event: ServerEvent) { + if let Some((_, user)) = self.read().await.iter().find(|(c, _)| *c == conn_id) { + let _ = user + .sender + .unbounded_send(Ok(event.sign(&user.shared_secret).as_ref().into())); + } + } } -- 2.45.2 From 90cd947e717947efb02f60a4e18a0c494fac2c9e Mon Sep 17 00:00:00 2001 From: Awiteb Date: Fri, 12 Jul 2024 22:51:57 +0300 Subject: [PATCH 15/55] feat: New tables utils The tables is: - `blacklist` - `incoming_chat_requests` - `whitelist` - `outgoing_chat_requests` Signed-off-by: Awiteb --- crates/oxidetalis/src/database/blacklist.rs | 52 +++++++++++ .../src/database/in_chat_requests.rs | 66 ++++++++++++++ crates/oxidetalis/src/database/mod.rs | 8 ++ .../src/database/out_chat_requests.rs | 86 ++++++++++++++++++ crates/oxidetalis/src/database/whitelist.rs | 89 +++++++++++++++++++ 5 files changed, 301 insertions(+) create mode 100644 crates/oxidetalis/src/database/blacklist.rs create mode 100644 crates/oxidetalis/src/database/in_chat_requests.rs create mode 100644 crates/oxidetalis/src/database/out_chat_requests.rs create mode 100644 crates/oxidetalis/src/database/whitelist.rs diff --git a/crates/oxidetalis/src/database/blacklist.rs b/crates/oxidetalis/src/database/blacklist.rs new file mode 100644 index 0000000..a35eccb --- /dev/null +++ b/crates/oxidetalis/src/database/blacklist.rs @@ -0,0 +1,52 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Database extension to work with the balcklist table + +use oxidetalis_core::types::PublicKey; +use oxidetalis_entities::prelude::*; +use sea_orm::DatabaseConnection; + +use crate::errors::ApiResult; + +/// Extension trait for the `DatabaseConnection` to work with the blacklist +/// table +pub trait BlackListExt { + /// Returns ture if the `blacklister` are blacklisted the + /// `target_public_key` + async fn is_blacklisted( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ApiResult; +} + +impl BlackListExt for DatabaseConnection { + #[logcall::logcall] + async fn is_blacklisted( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ApiResult { + blacklister + .find_related(BlacklistEntity) + .filter(BlacklistColumn::Target.eq(target_public_key.to_string())) + .one(self) + .await + .map(|u| u.is_some()) + .map_err(Into::into) + } +} diff --git a/crates/oxidetalis/src/database/in_chat_requests.rs b/crates/oxidetalis/src/database/in_chat_requests.rs new file mode 100644 index 0000000..137fee3 --- /dev/null +++ b/crates/oxidetalis/src/database/in_chat_requests.rs @@ -0,0 +1,66 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Database extension for the `in_chat_requests` table. + +use chrono::Utc; +use oxidetalis_core::types::PublicKey; +use oxidetalis_entities::prelude::*; +use sea_orm::DatabaseConnection; + +use crate::websocket::errors::{WsError, WsResult}; + +/// Extension trait for the `in_chat_requests` table. +pub trait InChatRequestsExt { + /// Save the chat request in the recipient table + async fn save_in_chat_request( + &self, + requester: &PublicKey, + recipient: &UserModel, + ) -> WsResult<()>; +} + +impl InChatRequestsExt for DatabaseConnection { + #[logcall::logcall] + async fn save_in_chat_request( + &self, + sender: &PublicKey, + recipient: &UserModel, + ) -> WsResult<()> { + if sender.to_string() == recipient.public_key { + return Err(WsError::CannotSendChatRequestToSelf); + } + if recipient + .find_related(InChatRequestsEntity) + .filter(InChatRequestsColumn::Sender.eq(sender.to_string())) + .one(self) + .await + .map_err(|_| WsError::InternalServerError)? + .is_none() + { + InChatRequestsActiveModel { + recipient_id: Set(recipient.id), + sender: Set(sender.to_string()), + in_on: Set(Utc::now()), + ..Default::default() + } + .save(self) + .await + .map_err(|_| WsError::InternalServerError)?; + } + Ok(()) + } +} diff --git a/crates/oxidetalis/src/database/mod.rs b/crates/oxidetalis/src/database/mod.rs index a9578ae..dc03f6a 100644 --- a/crates/oxidetalis/src/database/mod.rs +++ b/crates/oxidetalis/src/database/mod.rs @@ -16,6 +16,14 @@ //! Database utilities for the OxideTalis homeserver. +mod blacklist; +mod in_chat_requests; +mod out_chat_requests; mod user; +mod whitelist; +pub use blacklist::*; +pub use in_chat_requests::*; +pub use out_chat_requests::*; pub use user::*; +pub use whitelist::*; diff --git a/crates/oxidetalis/src/database/out_chat_requests.rs b/crates/oxidetalis/src/database/out_chat_requests.rs new file mode 100644 index 0000000..b687997 --- /dev/null +++ b/crates/oxidetalis/src/database/out_chat_requests.rs @@ -0,0 +1,86 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Database extension for the `out_chat_requests` table. + +use chrono::Utc; +use oxidetalis_core::types::PublicKey; +use oxidetalis_entities::prelude::*; +use sea_orm::DatabaseConnection; + +use crate::{ + errors::ApiResult, + websocket::errors::{WsError, WsResult}, +}; + +/// Extension trait for the `out_chat_requests` table. +pub trait OutChatRequestsExt { + /// Returns true if the `user` have a sended chat request to the `recipient` + async fn have_chat_request_to( + &self, + requester: &UserModel, + recipient: &PublicKey, + ) -> ApiResult; + + /// Save the chat request in the requester table + async fn save_out_chat_request( + &self, + requester: &UserModel, + recipient: &PublicKey, + ) -> WsResult<()>; +} + +impl OutChatRequestsExt for DatabaseConnection { + #[logcall::logcall] + async fn have_chat_request_to( + &self, + requester: &UserModel, + recipient: &PublicKey, + ) -> ApiResult { + requester + .find_related(OutChatRequestsEntity) + .filter(OutChatRequestsColumn::Recipient.eq(recipient.to_string())) + .one(self) + .await + .map(|user| user.is_some()) + .map_err(Into::into) + } + + #[logcall::logcall] + async fn save_out_chat_request( + &self, + requester: &UserModel, + recipient: &PublicKey, + ) -> WsResult<()> { + if self + .have_chat_request_to(requester, recipient) + .await + .map_err(|_| WsError::InternalServerError)? + { + return Err(WsError::AlreadySendChatRequest); + } + OutChatRequestsActiveModel { + sender_id: Set(requester.id), + recipient: Set(recipient.to_string()), + out_on: Set(Utc::now()), + ..Default::default() + } + .save(self) + .await + .map_err(|_| WsError::InternalServerError)?; + Ok(()) + } +} diff --git a/crates/oxidetalis/src/database/whitelist.rs b/crates/oxidetalis/src/database/whitelist.rs new file mode 100644 index 0000000..7a810d2 --- /dev/null +++ b/crates/oxidetalis/src/database/whitelist.rs @@ -0,0 +1,89 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Database extension to work with the balcklist table + +use chrono::Utc; +use oxidetalis_core::types::PublicKey; +use oxidetalis_entities::prelude::*; +use sea_orm::DatabaseConnection; + +use crate::{ + errors::ApiResult, + websocket::errors::{WsError, WsResult}, +}; + +/// Extension trait for the `DatabaseConnection` to work with the whitelist +/// table +pub trait WhiteListExt { + /// Returns ture if the `whitelister` are whitelisted the + /// `target_public_key` + async fn is_whitelisted( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> ApiResult; + + /// Add the `target_public_key` to the whitelist of the `whitelister` + async fn add_to_whitelist( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> WsResult<()>; +} + +impl WhiteListExt for DatabaseConnection { + async fn is_whitelisted( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> ApiResult { + whitelister + .find_related(WhitelistEntity) + .filter(WhitelistColumn::Target.eq(target_public_key.to_string())) + .one(self) + .await + .map(|u| u.is_some()) + .map_err(Into::into) + } + + async fn add_to_whitelist( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> WsResult<()> { + if self + .is_whitelisted(whitelister, target_public_key) + .await + .map_err(|_| WsError::InternalServerError)? + { + return Err(WsError::AlreadyOnTheWhitelist); + } + if whitelister.public_key == target_public_key.to_string() { + return Err(WsError::CannotAddSelfToWhitelist); + } + WhitelistActiveModel { + user_id: Set(whitelister.id), + target: Set(target_public_key.to_string()), + whitelisted_at: Set(Utc::now()), + ..Default::default() + } + .save(self) + .await + .map_err(|_| WsError::InternalServerError)?; + Ok(()) + } +} -- 2.45.2 From 8333a6ba9a23e223af41e03d654835a86288a87f Mon Sep 17 00:00:00 2001 From: Awiteb Date: Fri, 12 Jul 2024 23:13:07 +0300 Subject: [PATCH 16/55] chore: New errors Signed-off-by: Awiteb --- crates/oxidetalis/src/websocket/errors.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/oxidetalis/src/websocket/errors.rs b/crates/oxidetalis/src/websocket/errors.rs index e2fab4b..ec39321 100644 --- a/crates/oxidetalis/src/websocket/errors.rs +++ b/crates/oxidetalis/src/websocket/errors.rs @@ -22,8 +22,15 @@ use crate::ws_errors; pub type WsResult = Result; ws_errors! { + InternalServerError = "Internal server error", InvalidSignature = "Invalid event signature", NotTextMessage = "The websocket message must be text message", InvalidJsonData = "Received invalid json data, the text must be valid json", UnknownClientEvent = "Unknown client event, the event is not recognized by the server", + RegistredUserEvent = "The event is only for registred users", + UserNotFound = "The user is not registered in the server", + AlreadyOnTheWhitelist = "The user is already on your whitelist", + AlreadySendChatRequest = "You have already sent a chat request to this user", + CannotSendChatRequestToSelf = "You cannot send a chat request to yourself", + CannotAddSelfToWhitelist = "You cannot add yourself to the whitelist", } -- 2.45.2 From ff37501b74215a211ae3458c8cc3694ca7f07966 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Fri, 12 Jul 2024 23:13:50 +0300 Subject: [PATCH 17/55] feat: New event called `ChatRequest` Signed-off-by: Awiteb --- crates/oxidetalis/src/websocket/events/client.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/oxidetalis/src/websocket/events/client.rs b/crates/oxidetalis/src/websocket/events/client.rs index a4dda31..23b8586 100644 --- a/crates/oxidetalis/src/websocket/events/client.rs +++ b/crates/oxidetalis/src/websocket/events/client.rs @@ -16,7 +16,7 @@ //! Events that the client send it -use oxidetalis_core::types::Signature; +use oxidetalis_core::types::{PublicKey, Signature}; use serde::{Deserialize, Serialize}; use crate::{nonce::NonceCache, utils}; @@ -24,6 +24,7 @@ use crate::{nonce::NonceCache, utils}; /// Client websocket event #[derive(Deserialize, Clone, Debug)] pub struct ClientEvent { + #[serde(flatten)] pub event: ClientEventType, signature: Signature, } @@ -36,6 +37,8 @@ pub enum ClientEventType { Ping { timestamp: u64 }, /// Pong event Pong { timestamp: u64 }, + /// Request to chat with a user + ChatRequest { to: PublicKey }, } impl ClientEventType { -- 2.45.2 From 9dca968b30d5fdce942ef434f8b61adc037f7966 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Fri, 12 Jul 2024 23:14:12 +0300 Subject: [PATCH 18/55] feat: Message and ChatRequest events Signed-off-by: Awiteb --- .../oxidetalis/src/websocket/events/server.rs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/oxidetalis/src/websocket/events/server.rs b/crates/oxidetalis/src/websocket/events/server.rs index 9bd990a..84e7e15 100644 --- a/crates/oxidetalis/src/websocket/events/server.rs +++ b/crates/oxidetalis/src/websocket/events/server.rs @@ -19,7 +19,10 @@ use std::marker::PhantomData; use chrono::Utc; -use oxidetalis_core::{cipher::K256Secret, types::Signature}; +use oxidetalis_core::{ + cipher::K256Secret, + types::{PublicKey, Signature}, +}; use salvo::websocket::Message; use serde::Serialize; @@ -42,12 +45,16 @@ pub struct ServerEvent { /// server websocket event type #[derive(Serialize, Clone, Eq, PartialEq, Debug)] -#[serde(rename_all = "PascalCase")] +#[serde(rename_all = "PascalCase", tag = "event", content = "data")] pub enum ServerEventType { /// Ping event Ping { timestamp: u64 }, /// Pong event Pong { timestamp: u64 }, + /// Message event, contain a message to the client + Message { msg: String }, + /// New chat request from someone + ChatRequest { from: PublicKey }, /// Error event Error { name: &'static str, @@ -88,6 +95,16 @@ impl ServerEvent { }) } + /// Create message event + pub fn message(msg: impl Into) -> Self { + Self::new(ServerEventType::Message { msg: msg.into() }) + } + + /// Create chat request event + pub fn chat_request(from: &PublicKey) -> Self { + Self::new(ServerEventType::ChatRequest { from: *from }) + } + /// Sign the event pub fn sign(self, shared_secret: &[u8; 32]) -> ServerEvent { ServerEvent:: { -- 2.45.2 From 8cc6aef67baf0ae6ae941543f521ea006d0a376b Mon Sep 17 00:00:00 2001 From: Awiteb Date: Fri, 12 Jul 2024 23:15:19 +0300 Subject: [PATCH 19/55] feat: ChatRequest event handler Signed-off-by: Awiteb --- .../src/websocket/handlers/chat_request.rs | 77 +++++++++++++++++++ .../oxidetalis/src/websocket/handlers/mod.rs | 21 +++++ 2 files changed, 98 insertions(+) create mode 100644 crates/oxidetalis/src/websocket/handlers/chat_request.rs create mode 100644 crates/oxidetalis/src/websocket/handlers/mod.rs diff --git a/crates/oxidetalis/src/websocket/handlers/chat_request.rs b/crates/oxidetalis/src/websocket/handlers/chat_request.rs new file mode 100644 index 0000000..0f43230 --- /dev/null +++ b/crates/oxidetalis/src/websocket/handlers/chat_request.rs @@ -0,0 +1,77 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Handler for incoming and outgoing chat requests. + +use std::str::FromStr; + +use oxidetalis_core::types::PublicKey; +use oxidetalis_entities::prelude::*; +use sea_orm::DatabaseConnection; + +use crate::database::InChatRequestsExt; +use crate::extensions::OnlineUsersExt; +use crate::{ + database::{BlackListExt, OutChatRequestsExt, UserTableExt, WhiteListExt}, + try_ws, + websocket::{errors::WsError, ServerEvent, Unsigned, ONLINE_USERS}, +}; + +/// Handle a chat request from a user. +pub async fn handle_chat_request( + db: &DatabaseConnection, + from: Option<&UserModel>, + to_public_key: &PublicKey, +) -> ServerEvent { + let Some(from_user) = from else { + return WsError::RegistredUserEvent.into(); + }; + let Some(to_user) = try_ws!(db.get_user_by_pubk(to_public_key).await) else { + return WsError::UserNotFound.into(); + }; + if from_user.id == to_user.id { + return WsError::CannotSendChatRequestToSelf.into(); + } + // FIXME: When change the entity public key to a PublicKey type, change this + let from_public_key = PublicKey::from_str(&from_user.public_key).expect("Is valid public key"); + + if try_ws!(db.is_blacklisted(&to_user, &from_public_key).await) { + return ServerEvent::message( + "You are unable to send a chat request because you are on the recipient's blacklist.", + ); + } + if try_ws!(db.have_chat_request_to(from_user, to_public_key).await) { + return ServerEvent::message("You have already sent a chat request to this user."); + } + + try_ws!(db.add_to_whitelist(from_user, to_public_key).await); + + if try_ws!(db.is_whitelisted(&to_user, &from_public_key).await) { + return ServerEvent::message( + "You are already on the recipient's whitelist, so you can now chat with them.", + ); + } + + try_ws!(db.save_out_chat_request(from_user, to_public_key).await); + if let Some(conn_id) = ONLINE_USERS.is_online(to_public_key).await { + ONLINE_USERS + .send(&conn_id, ServerEvent::chat_request(&from_public_key)) + .await; + } else { + try_ws!(db.save_in_chat_request(&from_public_key, &to_user).await); + } + ServerEvent::message("Chat request sent successfully.") +} diff --git a/crates/oxidetalis/src/websocket/handlers/mod.rs b/crates/oxidetalis/src/websocket/handlers/mod.rs new file mode 100644 index 0000000..5f632a3 --- /dev/null +++ b/crates/oxidetalis/src/websocket/handlers/mod.rs @@ -0,0 +1,21 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Websocket event handlers. + +mod chat_request; + +pub use chat_request::*; -- 2.45.2 From 927a345802871092d50b496dc18c28090780d4bf Mon Sep 17 00:00:00 2001 From: Awiteb Date: Fri, 12 Jul 2024 23:15:33 +0300 Subject: [PATCH 20/55] feat: Handle `ChatRequest` event Signed-off-by: Awiteb --- crates/oxidetalis/src/websocket/mod.rs | 42 ++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/crates/oxidetalis/src/websocket/mod.rs b/crates/oxidetalis/src/websocket/mod.rs index ed4a901..8607236 100644 --- a/crates/oxidetalis/src/websocket/mod.rs +++ b/crates/oxidetalis/src/websocket/mod.rs @@ -21,6 +21,7 @@ use errors::{WsError, WsResult}; use futures::{channel::mpsc, FutureExt, StreamExt, TryStreamExt}; use once_cell::sync::Lazy; use oxidetalis_core::{cipher::K256Secret, types::PublicKey}; +use oxidetalis_entities::prelude::*; use salvo::{ handler, http::StatusError, @@ -30,15 +31,18 @@ use salvo::{ Response, Router, }; +use sea_orm::DatabaseConnection; use tokio::{sync::RwLock, task::spawn as tokio_spawn, time::sleep as tokio_sleep}; -mod errors; +pub mod errors; mod events; +mod handlers; pub use events::*; use uuid::Uuid; use crate::{ + database::UserTableExt, extensions::{DepotExt, OnlineUsersExt}, middlewares, nonce::NonceCache, @@ -92,6 +96,7 @@ pub async fn user_connected( depot: &Depot, ) -> Result<(), StatusError> { let nonce_cache = depot.nonce_cache(); + let db_conn = depot.db_conn(); let public_key = utils::extract_public_key(req).expect("The public key was checked in the middleware"); // FIXME: The config should hold `K256Secret` not `PrivateKey` @@ -100,7 +105,7 @@ pub async fn user_connected( WebSocketUpgrade::new() .upgrade(req, res, move |ws| { - handle_socket(ws, nonce_cache, public_key, shared_secret) + handle_socket(ws, db_conn, nonce_cache, public_key, shared_secret) }) .await } @@ -108,6 +113,7 @@ pub async fn user_connected( /// Handle the websocket connection async fn handle_socket( ws: WebSocket, + db_conn: Arc, nonce_cache: Arc, user_public_key: PublicKey, user_shared_secret: [u8; 32], @@ -123,15 +129,31 @@ async fn handle_socket( }); tokio_spawn(fut); let conn_id = Uuid::new_v4(); - let user = SocketUserData::new(user_public_key, user_shared_secret, sender.clone()); - ONLINE_USERS.add_user(&conn_id, user).await; + let Ok(user) = db_conn.get_user_by_pubk(&user_public_key).await else { + let _ = sender.unbounded_send(Ok(ServerEvent::from(WsError::InternalServerError) + .sign(&user_shared_secret) + .as_ref() + .into())); + return; + }; + ONLINE_USERS + .add_user( + &conn_id, + SocketUserData::new(user_public_key, user_shared_secret, sender.clone()), + ) + .await; log::info!("New user connected: ConnId(={conn_id}) PublicKey(={user_public_key})"); + // TODO: Send the incoming chat request to the user, while they are offline. + // This after adding last_login col to the user table + let fut = async move { while let Some(Ok(msg)) = user_ws_receiver.next().await { match handle_ws_msg(msg, &nonce_cache, &user_shared_secret).await { Ok(event) => { - if let Some(server_event) = handle_events(event, &conn_id).await { + if let Some(server_event) = + handle_events(event, &db_conn, &conn_id, user.as_ref()).await + { if let Err(err) = sender.unbounded_send(Ok(server_event .sign(&user_shared_secret) .as_ref() @@ -182,13 +204,21 @@ async fn handle_ws_msg( } /// Handle user events, and return the server event if needed -async fn handle_events(event: ClientEvent, conn_id: &Uuid) -> Option> { +async fn handle_events( + event: ClientEvent, + db: &DatabaseConnection, + conn_id: &Uuid, + user: Option<&UserModel>, +) -> Option> { match &event.event { ClientEventType::Ping { .. } => Some(ServerEvent::pong()), ClientEventType::Pong { .. } => { ONLINE_USERS.update_pong(conn_id).await; None } + ClientEventType::ChatRequest { to } => { + Some(handlers::handle_chat_request(db, user, to).await) + } } } -- 2.45.2 From abbdec49e7e3b42e323dadb8dc10aa8ef2c1e462 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Fri, 12 Jul 2024 23:23:16 +0300 Subject: [PATCH 21/55] change: Rename `DuplicatedUser` error to `AlreadyRegistered` Signed-off-by: Awiteb --- crates/oxidetalis/src/database/user.rs | 2 +- crates/oxidetalis/src/errors.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/oxidetalis/src/database/user.rs b/crates/oxidetalis/src/database/user.rs index 3230aa3..4cc27a0 100644 --- a/crates/oxidetalis/src/database/user.rs +++ b/crates/oxidetalis/src/database/user.rs @@ -53,7 +53,7 @@ impl UserTableExt for DatabaseConnection { .await { if let Some(SqlErr::UniqueConstraintViolation(_)) = err.sql_err() { - return Err(ApiError::DuplicatedUser); + return Err(ApiError::AlreadyRegistered); } } diff --git a/crates/oxidetalis/src/errors.rs b/crates/oxidetalis/src/errors.rs index 95f6b2f..325cfc3 100644 --- a/crates/oxidetalis/src/errors.rs +++ b/crates/oxidetalis/src/errors.rs @@ -48,7 +48,7 @@ pub enum ApiError { RegistrationClosed, /// The entered public key is already registered (400 Bad Request) #[error("The entered public key is already registered")] - DuplicatedUser, + AlreadyRegistered, /// The user entered two different public keys /// one in the header and other in the request body /// (400 Bad Request) @@ -62,7 +62,7 @@ impl ApiError { match self { Self::SeaOrm(_) => StatusCode::INTERNAL_SERVER_ERROR, Self::RegistrationClosed => StatusCode::FORBIDDEN, - Self::DuplicatedUser | Self::TwoDifferentKeys => StatusCode::BAD_REQUEST, + Self::AlreadyRegistered | Self::TwoDifferentKeys => StatusCode::BAD_REQUEST, } } } -- 2.45.2 From 2bfff6c05d601eee385a19ca39635fd676494c24 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Sat, 13 Jul 2024 23:15:09 +0300 Subject: [PATCH 22/55] refactor: Refactor server errors Signed-off-by: Awiteb --- crates/oxidetalis/src/database/blacklist.rs | 6 +- .../src/database/in_chat_requests.rs | 15 ++-- .../src/database/out_chat_requests.rs | 24 ++---- crates/oxidetalis/src/database/user.rs | 20 ++--- crates/oxidetalis/src/database/whitelist.rs | 26 +++---- crates/oxidetalis/src/errors.rs | 76 ++++++++----------- crates/oxidetalis/src/macros.rs | 7 +- crates/oxidetalis/src/main.rs | 6 +- crates/oxidetalis/src/routes/errors.rs | 68 +++++++++++++++++ crates/oxidetalis/src/routes/mod.rs | 3 + crates/oxidetalis/src/routes/user.rs | 2 +- 11 files changed, 147 insertions(+), 106 deletions(-) create mode 100644 crates/oxidetalis/src/routes/errors.rs diff --git a/crates/oxidetalis/src/database/blacklist.rs b/crates/oxidetalis/src/database/blacklist.rs index a35eccb..8448843 100644 --- a/crates/oxidetalis/src/database/blacklist.rs +++ b/crates/oxidetalis/src/database/blacklist.rs @@ -20,7 +20,7 @@ use oxidetalis_core::types::PublicKey; use oxidetalis_entities::prelude::*; use sea_orm::DatabaseConnection; -use crate::errors::ApiResult; +use crate::errors::ServerResult; /// Extension trait for the `DatabaseConnection` to work with the blacklist /// table @@ -31,7 +31,7 @@ pub trait BlackListExt { &self, blacklister: &UserModel, target_public_key: &PublicKey, - ) -> ApiResult; + ) -> ServerResult; } impl BlackListExt for DatabaseConnection { @@ -40,7 +40,7 @@ impl BlackListExt for DatabaseConnection { &self, blacklister: &UserModel, target_public_key: &PublicKey, - ) -> ApiResult { + ) -> ServerResult { blacklister .find_related(BlacklistEntity) .filter(BlacklistColumn::Target.eq(target_public_key.to_string())) diff --git a/crates/oxidetalis/src/database/in_chat_requests.rs b/crates/oxidetalis/src/database/in_chat_requests.rs index 137fee3..537b3fe 100644 --- a/crates/oxidetalis/src/database/in_chat_requests.rs +++ b/crates/oxidetalis/src/database/in_chat_requests.rs @@ -21,7 +21,7 @@ use oxidetalis_core::types::PublicKey; use oxidetalis_entities::prelude::*; use sea_orm::DatabaseConnection; -use crate::websocket::errors::{WsError, WsResult}; +use crate::errors::ServerResult; /// Extension trait for the `in_chat_requests` table. pub trait InChatRequestsExt { @@ -30,7 +30,7 @@ pub trait InChatRequestsExt { &self, requester: &PublicKey, recipient: &UserModel, - ) -> WsResult<()>; + ) -> ServerResult<()>; } impl InChatRequestsExt for DatabaseConnection { @@ -39,16 +39,12 @@ impl InChatRequestsExt for DatabaseConnection { &self, sender: &PublicKey, recipient: &UserModel, - ) -> WsResult<()> { - if sender.to_string() == recipient.public_key { - return Err(WsError::CannotSendChatRequestToSelf); - } + ) -> ServerResult<()> { if recipient .find_related(InChatRequestsEntity) .filter(InChatRequestsColumn::Sender.eq(sender.to_string())) .one(self) - .await - .map_err(|_| WsError::InternalServerError)? + .await? .is_none() { InChatRequestsActiveModel { @@ -58,8 +54,7 @@ impl InChatRequestsExt for DatabaseConnection { ..Default::default() } .save(self) - .await - .map_err(|_| WsError::InternalServerError)?; + .await?; } Ok(()) } diff --git a/crates/oxidetalis/src/database/out_chat_requests.rs b/crates/oxidetalis/src/database/out_chat_requests.rs index b687997..b51274d 100644 --- a/crates/oxidetalis/src/database/out_chat_requests.rs +++ b/crates/oxidetalis/src/database/out_chat_requests.rs @@ -21,10 +21,7 @@ use oxidetalis_core::types::PublicKey; use oxidetalis_entities::prelude::*; use sea_orm::DatabaseConnection; -use crate::{ - errors::ApiResult, - websocket::errors::{WsError, WsResult}, -}; +use crate::{errors::ServerResult, websocket::errors::WsError}; /// Extension trait for the `out_chat_requests` table. pub trait OutChatRequestsExt { @@ -33,14 +30,14 @@ pub trait OutChatRequestsExt { &self, requester: &UserModel, recipient: &PublicKey, - ) -> ApiResult; + ) -> ServerResult; /// Save the chat request in the requester table async fn save_out_chat_request( &self, requester: &UserModel, recipient: &PublicKey, - ) -> WsResult<()>; + ) -> ServerResult<()>; } impl OutChatRequestsExt for DatabaseConnection { @@ -49,7 +46,7 @@ impl OutChatRequestsExt for DatabaseConnection { &self, requester: &UserModel, recipient: &PublicKey, - ) -> ApiResult { + ) -> ServerResult { requester .find_related(OutChatRequestsEntity) .filter(OutChatRequestsColumn::Recipient.eq(recipient.to_string())) @@ -64,13 +61,9 @@ impl OutChatRequestsExt for DatabaseConnection { &self, requester: &UserModel, recipient: &PublicKey, - ) -> WsResult<()> { - if self - .have_chat_request_to(requester, recipient) - .await - .map_err(|_| WsError::InternalServerError)? - { - return Err(WsError::AlreadySendChatRequest); + ) -> ServerResult<()> { + if self.have_chat_request_to(requester, recipient).await? { + return Err(WsError::AlreadySendChatRequest.into()); } OutChatRequestsActiveModel { sender_id: Set(requester.id), @@ -79,8 +72,7 @@ impl OutChatRequestsExt for DatabaseConnection { ..Default::default() } .save(self) - .await - .map_err(|_| WsError::InternalServerError)?; + .await?; Ok(()) } } diff --git a/crates/oxidetalis/src/database/user.rs b/crates/oxidetalis/src/database/user.rs index 4cc27a0..4e94901 100644 --- a/crates/oxidetalis/src/database/user.rs +++ b/crates/oxidetalis/src/database/user.rs @@ -21,29 +21,29 @@ use oxidetalis_core::types::PublicKey; use oxidetalis_entities::prelude::*; use sea_orm::DatabaseConnection; -use crate::errors::{ApiError, ApiResult}; +use crate::{errors::ServerResult, routes::ApiError}; pub trait UserTableExt { /// Returns true if there is users in the database - async fn users_exists_in_database(&self) -> ApiResult; + async fn users_exists_in_database(&self) -> ServerResult; /// Register new user - async fn register_user(&self, public_key: &PublicKey, is_admin: bool) -> ApiResult<()>; + async fn register_user(&self, public_key: &PublicKey, is_admin: bool) -> ServerResult<()>; /// Returns user by its public key - async fn get_user_by_pubk(&self, public_key: &PublicKey) -> ApiResult>; + async fn get_user_by_pubk(&self, public_key: &PublicKey) -> ServerResult>; } impl UserTableExt for DatabaseConnection { #[logcall] - async fn users_exists_in_database(&self) -> ApiResult { + async fn users_exists_in_database(&self) -> ServerResult { UserEntity::find() .one(self) .await - .map_err(Into::into) .map(|u| u.is_some()) + .map_err(Into::into) } #[logcall] - async fn register_user(&self, public_key: &PublicKey, is_admin: bool) -> ApiResult<()> { + async fn register_user(&self, public_key: &PublicKey, is_admin: bool) -> ServerResult<()> { if let Err(err) = (UserActiveModel { public_key: Set(public_key.to_string()), is_admin: Set(is_admin), @@ -53,7 +53,7 @@ impl UserTableExt for DatabaseConnection { .await { if let Some(SqlErr::UniqueConstraintViolation(_)) = err.sql_err() { - return Err(ApiError::AlreadyRegistered); + return Err(ApiError::AlreadyRegistered.into()); } } @@ -61,11 +61,11 @@ impl UserTableExt for DatabaseConnection { } #[logcall] - async fn get_user_by_pubk(&self, public_key: &PublicKey) -> ApiResult> { + async fn get_user_by_pubk(&self, public_key: &PublicKey) -> ServerResult> { UserEntity::find() .filter(UserColumn::PublicKey.eq(public_key.to_string())) .one(self) .await - .map_err(ApiError::SeaOrm) + .map_err(Into::into) } } diff --git a/crates/oxidetalis/src/database/whitelist.rs b/crates/oxidetalis/src/database/whitelist.rs index 7a810d2..91d3fa2 100644 --- a/crates/oxidetalis/src/database/whitelist.rs +++ b/crates/oxidetalis/src/database/whitelist.rs @@ -21,10 +21,7 @@ use oxidetalis_core::types::PublicKey; use oxidetalis_entities::prelude::*; use sea_orm::DatabaseConnection; -use crate::{ - errors::ApiResult, - websocket::errors::{WsError, WsResult}, -}; +use crate::{errors::ServerResult, websocket::errors::WsError}; /// Extension trait for the `DatabaseConnection` to work with the whitelist /// table @@ -35,14 +32,14 @@ pub trait WhiteListExt { &self, whitelister: &UserModel, target_public_key: &PublicKey, - ) -> ApiResult; + ) -> ServerResult; /// Add the `target_public_key` to the whitelist of the `whitelister` async fn add_to_whitelist( &self, whitelister: &UserModel, target_public_key: &PublicKey, - ) -> WsResult<()>; + ) -> ServerResult<()>; } impl WhiteListExt for DatabaseConnection { @@ -50,7 +47,7 @@ impl WhiteListExt for DatabaseConnection { &self, whitelister: &UserModel, target_public_key: &PublicKey, - ) -> ApiResult { + ) -> ServerResult { whitelister .find_related(WhitelistEntity) .filter(WhitelistColumn::Target.eq(target_public_key.to_string())) @@ -64,16 +61,12 @@ impl WhiteListExt for DatabaseConnection { &self, whitelister: &UserModel, target_public_key: &PublicKey, - ) -> WsResult<()> { - if self - .is_whitelisted(whitelister, target_public_key) - .await - .map_err(|_| WsError::InternalServerError)? - { - return Err(WsError::AlreadyOnTheWhitelist); + ) -> ServerResult<()> { + if self.is_whitelisted(whitelister, target_public_key).await? { + return Err(WsError::AlreadyOnTheWhitelist.into()); } if whitelister.public_key == target_public_key.to_string() { - return Err(WsError::CannotAddSelfToWhitelist); + return Err(WsError::CannotAddSelfToWhitelist.into()); } WhitelistActiveModel { user_id: Set(whitelister.id), @@ -82,8 +75,7 @@ impl WhiteListExt for DatabaseConnection { ..Default::default() } .save(self) - .await - .map_err(|_| WsError::InternalServerError)?; + .await?; Ok(()) } } diff --git a/crates/oxidetalis/src/errors.rs b/crates/oxidetalis/src/errors.rs index 325cfc3..68ef86a 100644 --- a/crates/oxidetalis/src/errors.rs +++ b/crates/oxidetalis/src/errors.rs @@ -14,24 +14,16 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use salvo::{ - http::StatusCode, - oapi::{Components as OapiComponents, EndpointOutRegister, Operation as OapiOperation}, - Response, - Scribe, -}; +use sea_orm::DbErr; -use crate::{routes::write_json_body, schemas::MessageSchema}; +use crate::{routes::ApiError, websocket::errors::WsError}; /// Result type of the homeserver -#[allow(clippy::absolute_paths)] -pub(crate) type Result = std::result::Result; -#[allow(clippy::absolute_paths)] -pub type ApiResult = std::result::Result; +pub(crate) type ServerResult = Result; /// The homeserver errors #[derive(Debug, thiserror::Error)] -pub(crate) enum Error { +pub enum InternalError { #[error("Database Error: {0}")] Database(#[from] sea_orm::DbErr), #[error("{0}")] @@ -39,43 +31,39 @@ pub(crate) enum Error { } #[derive(Debug, thiserror::Error)] -pub enum ApiError { - /// Error from the database (500 Internal Server Error) - #[error("Internal server error")] - SeaOrm(#[from] sea_orm::DbErr), - /// The server registration is closed (403 Forbidden) - #[error("Server registration is closed")] - RegistrationClosed, - /// The entered public key is already registered (400 Bad Request) - #[error("The entered public key is already registered")] - AlreadyRegistered, - /// The user entered two different public keys - /// one in the header and other in the request body - /// (400 Bad Request) - #[error("You entered two different public keys")] - TwoDifferentKeys, +/// The homeserver errors +pub enum ServerError { + /// Internal server error, should not be exposed to the user + #[error("{0}")] + Internal(#[from] InternalError), + /// API error, errors happening in the API + #[error("{0}")] + Api(#[from] ApiError), + /// WebSocket error, errors happening in the WebSocket + #[error("{0}")] + Ws(#[from] WsError), } -impl ApiError { - /// Status code of the error - pub const fn status_code(&self) -> StatusCode { - match self { - Self::SeaOrm(_) => StatusCode::INTERNAL_SERVER_ERROR, - Self::RegistrationClosed => StatusCode::FORBIDDEN, - Self::AlreadyRegistered | Self::TwoDifferentKeys => StatusCode::BAD_REQUEST, +impl From for ServerError { + fn from(err: DbErr) -> Self { + Self::Internal(err.into()) + } +} + +impl From for WsError { + fn from(err: ServerError) -> Self { + match err { + ServerError::Internal(_) | ServerError::Api(_) => WsError::InternalServerError, + ServerError::Ws(err) => err, } } } -impl EndpointOutRegister for ApiError { - fn register(_: &mut OapiComponents, _: &mut OapiOperation) {} -} - -impl Scribe for ApiError { - fn render(self, res: &mut Response) { - log::error!("Error: {self}"); - - res.status_code(self.status_code()); - write_json_body(res, MessageSchema::new(self.to_string())); +impl From for ApiError { + fn from(err: ServerError) -> Self { + match err { + ServerError::Internal(_) | ServerError::Ws(_) => ApiError::Internal, + ServerError::Api(err) => err, + } } } diff --git a/crates/oxidetalis/src/macros.rs b/crates/oxidetalis/src/macros.rs index 54c7149..2943e59 100644 --- a/crates/oxidetalis/src/macros.rs +++ b/crates/oxidetalis/src/macros.rs @@ -39,9 +39,9 @@ macro_rules! try_ws { match $result_expr { Ok(val) => val, Err(err) => { - log::error!("Error in try_ws macro: {err:?}"); + log::error!("{err}"); return $crate::websocket::ServerEvent::<$crate::websocket::Unsigned>::from( - $crate::websocket::errors::WsError::InternalServerError, + $crate::websocket::errors::WsError::from(err), ); } } @@ -60,11 +60,12 @@ macro_rules! try_ws { #[macro_export] macro_rules! ws_errors { ($($name:ident = $reason:tt),+ $(,)?) => { - #[derive(Debug)] + #[derive(Debug, thiserror::Error)] #[doc = "Websocket errors, returned in the websocket communication"] pub enum WsError { $( #[doc = $reason] + #[error($reason)] $name ),+ } diff --git a/crates/oxidetalis/src/main.rs b/crates/oxidetalis/src/main.rs index b15cfb5..3b373a0 100644 --- a/crates/oxidetalis/src/main.rs +++ b/crates/oxidetalis/src/main.rs @@ -19,6 +19,7 @@ use std::process::ExitCode; +use errors::ServerError; use oxidetalis_config::{CliArgs, Parser}; use oxidetalis_migrations::MigratorTrait; use salvo::{conn::TcpListener, Listener, Server}; @@ -34,11 +35,12 @@ mod schemas; mod utils; mod websocket; -async fn try_main() -> errors::Result<()> { +async fn try_main() -> errors::ServerResult<()> { pretty_env_logger::init_timed(); log::info!("Parsing configuration"); - let config = oxidetalis_config::Config::load(CliArgs::parse())?; + let config = oxidetalis_config::Config::load(CliArgs::parse()) + .map_err(|err| ServerError::Internal(err.into()))?; log::info!("Configuration parsed successfully"); log::info!("Connecting to the database"); let connection = sea_orm::Database::connect(utils::postgres_url(&config.postgresdb)).await?; diff --git a/crates/oxidetalis/src/routes/errors.rs b/crates/oxidetalis/src/routes/errors.rs new file mode 100644 index 0000000..29686de --- /dev/null +++ b/crates/oxidetalis/src/routes/errors.rs @@ -0,0 +1,68 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use salvo::{ + http::StatusCode, + oapi::{Components as OapiComponents, EndpointOutRegister, Operation as OapiOperation}, + Response, + Scribe, +}; + +use crate::{routes::write_json_body, schemas::MessageSchema}; + +/// Result type of the API +pub type ApiResult = Result; + +#[derive(Debug, thiserror::Error)] +pub enum ApiError { + #[error("Internal server error")] + Internal, + /// The server registration is closed (403 Forbidden) + #[error("Server registration is closed")] + RegistrationClosed, + /// The entered public key is already registered (400 Bad Request) + #[error("The entered public key is already registered")] + AlreadyRegistered, + /// The user entered two different public keys + /// one in the header and other in the request body + /// (400 Bad Request) + #[error("You entered two different public keys")] + TwoDifferentKeys, +} + +impl ApiError { + /// Status code of the error + pub const fn status_code(&self) -> StatusCode { + match self { + Self::Internal => StatusCode::INTERNAL_SERVER_ERROR, + Self::RegistrationClosed => StatusCode::FORBIDDEN, + Self::AlreadyRegistered | Self::TwoDifferentKeys => StatusCode::BAD_REQUEST, + } + } +} + +impl EndpointOutRegister for ApiError { + fn register(_: &mut OapiComponents, _: &mut OapiOperation) {} +} + +impl Scribe for ApiError { + fn render(self, res: &mut Response) { + log::error!("Error: {self}"); + + res.status_code(self.status_code()); + write_json_body(res, MessageSchema::new(self.to_string())); + } +} diff --git a/crates/oxidetalis/src/routes/mod.rs b/crates/oxidetalis/src/routes/mod.rs index 45bdbd5..1923cb6 100644 --- a/crates/oxidetalis/src/routes/mod.rs +++ b/crates/oxidetalis/src/routes/mod.rs @@ -27,8 +27,11 @@ use crate::nonce::NonceCache; use crate::schemas::MessageSchema; use crate::{middlewares, websocket}; +mod errors; mod user; +pub use errors::*; + pub fn write_json_body(res: &mut Response, json_body: impl serde::Serialize) { res.write_body(serde_json::to_string(&json_body).expect("Json serialization can't be fail")) .ok(); diff --git a/crates/oxidetalis/src/routes/user.rs b/crates/oxidetalis/src/routes/user.rs index ccefe73..16a3e95 100644 --- a/crates/oxidetalis/src/routes/user.rs +++ b/crates/oxidetalis/src/routes/user.rs @@ -26,9 +26,9 @@ use salvo::{ Writer, }; +use super::{ApiError, ApiResult}; use crate::{ database::UserTableExt, - errors::{ApiError, ApiResult}, extensions::DepotExt, middlewares, schemas::{EmptySchema, MessageSchema, RegisterUserBody}, -- 2.45.2 From 24e038f482c97e7ad75de2f496c14626df6420da Mon Sep 17 00:00:00 2001 From: Awiteb Date: Sun, 14 Jul 2024 15:04:33 +0300 Subject: [PATCH 23/55] feat: ChatRequestResponse event Signed-off-by: Awiteb --- crates/oxidetalis/src/database/blacklist.rs | 31 +++++++++- .../src/database/out_chat_requests.rs | 29 +++++++-- crates/oxidetalis/src/websocket/errors.rs | 6 +- .../oxidetalis/src/websocket/events/client.rs | 5 ++ .../oxidetalis/src/websocket/events/server.rs | 7 +++ .../src/websocket/handlers/chat_request.rs | 59 ++++++++++++++++++- crates/oxidetalis/src/websocket/mod.rs | 3 + 7 files changed, 133 insertions(+), 7 deletions(-) diff --git a/crates/oxidetalis/src/database/blacklist.rs b/crates/oxidetalis/src/database/blacklist.rs index 8448843..9a4a712 100644 --- a/crates/oxidetalis/src/database/blacklist.rs +++ b/crates/oxidetalis/src/database/blacklist.rs @@ -16,11 +16,12 @@ //! Database extension to work with the balcklist table +use chrono::Utc; use oxidetalis_core::types::PublicKey; use oxidetalis_entities::prelude::*; use sea_orm::DatabaseConnection; -use crate::errors::ServerResult; +use crate::{errors::ServerResult, websocket::errors::WsError}; /// Extension trait for the `DatabaseConnection` to work with the blacklist /// table @@ -32,6 +33,12 @@ pub trait BlackListExt { blacklister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult; + /// Add the `target_public_key` to the blacklist of the `blacklister` + async fn add_to_blacklist( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()>; } impl BlackListExt for DatabaseConnection { @@ -49,4 +56,26 @@ impl BlackListExt for DatabaseConnection { .map(|u| u.is_some()) .map_err(Into::into) } + + async fn add_to_blacklist( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()> { + if self.is_blacklisted(blacklister, target_public_key).await? { + return Err(WsError::AlreadyOnTheblacklist.into()); + } + if blacklister.public_key == target_public_key.to_string() { + return Err(WsError::CannotAddSelfToBlacklist.into()); + } + BlacklistActiveModel { + user_id: Set(blacklister.id), + target: Set(target_public_key.to_string()), + blacklisted_at: Set(Utc::now()), + ..Default::default() + } + .save(self) + .await?; + Ok(()) + } } diff --git a/crates/oxidetalis/src/database/out_chat_requests.rs b/crates/oxidetalis/src/database/out_chat_requests.rs index b51274d..0994e4b 100644 --- a/crates/oxidetalis/src/database/out_chat_requests.rs +++ b/crates/oxidetalis/src/database/out_chat_requests.rs @@ -30,7 +30,7 @@ pub trait OutChatRequestsExt { &self, requester: &UserModel, recipient: &PublicKey, - ) -> ServerResult; + ) -> ServerResult>; /// Save the chat request in the requester table async fn save_out_chat_request( @@ -38,6 +38,13 @@ pub trait OutChatRequestsExt { requester: &UserModel, recipient: &PublicKey, ) -> ServerResult<()>; + + /// Remove the chat request from requester table + async fn remove_out_chat_request( + &self, + requester: &UserModel, + recipient: &PublicKey, + ) -> ServerResult<()>; } impl OutChatRequestsExt for DatabaseConnection { @@ -46,13 +53,12 @@ impl OutChatRequestsExt for DatabaseConnection { &self, requester: &UserModel, recipient: &PublicKey, - ) -> ServerResult { + ) -> ServerResult> { requester .find_related(OutChatRequestsEntity) .filter(OutChatRequestsColumn::Recipient.eq(recipient.to_string())) .one(self) .await - .map(|user| user.is_some()) .map_err(Into::into) } @@ -62,7 +68,11 @@ impl OutChatRequestsExt for DatabaseConnection { requester: &UserModel, recipient: &PublicKey, ) -> ServerResult<()> { - if self.have_chat_request_to(requester, recipient).await? { + if self + .have_chat_request_to(requester, recipient) + .await? + .is_some() + { return Err(WsError::AlreadySendChatRequest.into()); } OutChatRequestsActiveModel { @@ -75,4 +85,15 @@ impl OutChatRequestsExt for DatabaseConnection { .await?; Ok(()) } + + async fn remove_out_chat_request( + &self, + requester: &UserModel, + recipient: &PublicKey, + ) -> ServerResult<()> { + if let Some(out_model) = self.have_chat_request_to(requester, recipient).await? { + out_model.delete(self).await?; + } + Ok(()) + } } diff --git a/crates/oxidetalis/src/websocket/errors.rs b/crates/oxidetalis/src/websocket/errors.rs index ec39321..d9ce4c7 100644 --- a/crates/oxidetalis/src/websocket/errors.rs +++ b/crates/oxidetalis/src/websocket/errors.rs @@ -30,7 +30,11 @@ ws_errors! { RegistredUserEvent = "The event is only for registred users", UserNotFound = "The user is not registered in the server", AlreadyOnTheWhitelist = "The user is already on your whitelist", + CannotAddSelfToWhitelist = "You cannot add yourself to the whitelist", + AlreadyOnTheblacklist = "The user is already on your blacklist", + CannotAddSelfToBlacklist = "You cannot add yourself to the blacklist", AlreadySendChatRequest = "You have already sent a chat request to this user", CannotSendChatRequestToSelf = "You cannot send a chat request to yourself", - CannotAddSelfToWhitelist = "You cannot add yourself to the whitelist", + CannotRespondToOwnChatRequest = "You cannot respond to your own chat request", + NoChatRequestFromRecipient = "You do not have a chat request from the recipient", } diff --git a/crates/oxidetalis/src/websocket/events/client.rs b/crates/oxidetalis/src/websocket/events/client.rs index 23b8586..524c3e1 100644 --- a/crates/oxidetalis/src/websocket/events/client.rs +++ b/crates/oxidetalis/src/websocket/events/client.rs @@ -29,6 +29,9 @@ pub struct ClientEvent { signature: Signature, } +// ## Important for contuributors +// Please make sure to order the event data alphabetically. + /// Client websocket event type #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)] #[serde(rename_all = "PascalCase", tag = "event", content = "data")] @@ -39,6 +42,8 @@ pub enum ClientEventType { Pong { timestamp: u64 }, /// Request to chat with a user ChatRequest { to: PublicKey }, + /// Response to a chat request + ChatRequestResponse { accepted: bool, to: PublicKey }, } impl ClientEventType { diff --git a/crates/oxidetalis/src/websocket/events/server.rs b/crates/oxidetalis/src/websocket/events/server.rs index 84e7e15..9824931 100644 --- a/crates/oxidetalis/src/websocket/events/server.rs +++ b/crates/oxidetalis/src/websocket/events/server.rs @@ -55,6 +55,8 @@ pub enum ServerEventType { Message { msg: String }, /// New chat request from someone ChatRequest { from: PublicKey }, + /// New chat request response from someone + ChatRequestResponse { from: PublicKey, accepted: bool }, /// Error event Error { name: &'static str, @@ -105,6 +107,11 @@ impl ServerEvent { Self::new(ServerEventType::ChatRequest { from: *from }) } + /// Create chat request response event + pub fn chat_request_response(from: PublicKey, accepted: bool) -> Self { + Self::new(ServerEventType::ChatRequestResponse { from, accepted }) + } + /// Sign the event pub fn sign(self, shared_secret: &[u8; 32]) -> ServerEvent { ServerEvent:: { diff --git a/crates/oxidetalis/src/websocket/handlers/chat_request.rs b/crates/oxidetalis/src/websocket/handlers/chat_request.rs index 0f43230..9519347 100644 --- a/crates/oxidetalis/src/websocket/handlers/chat_request.rs +++ b/crates/oxidetalis/src/websocket/handlers/chat_request.rs @@ -53,7 +53,7 @@ pub async fn handle_chat_request( "You are unable to send a chat request because you are on the recipient's blacklist.", ); } - if try_ws!(db.have_chat_request_to(from_user, to_public_key).await) { + if try_ws!(db.have_chat_request_to(from_user, to_public_key).await).is_some() { return ServerEvent::message("You have already sent a chat request to this user."); } @@ -75,3 +75,60 @@ pub async fn handle_chat_request( } ServerEvent::message("Chat request sent successfully.") } + +pub async fn handle_chat_response( + db: &DatabaseConnection, + recipient: Option<&UserModel>, + sender_public_key: &PublicKey, + accepted: bool, +) -> ServerEvent { + let Some(recipient_user) = recipient else { + return WsError::RegistredUserEvent.into(); + }; + let Some(sender_user) = try_ws!(db.get_user_by_pubk(sender_public_key).await) else { + return WsError::UserNotFound.into(); + }; + if recipient_user.id == sender_user.id { + return WsError::CannotRespondToOwnChatRequest.into(); + } + + // FIXME: When change the entity public key to a PublicKey type, change this + let recipient_public_key = + PublicKey::from_str(&recipient_user.public_key).expect("Is valid public key"); + + if try_ws!( + db.have_chat_request_to(&sender_user, &recipient_public_key) + .await + ) + .is_none() + { + return WsError::NoChatRequestFromRecipient.into(); + } + + if let Some(conn_id) = ONLINE_USERS.is_online(sender_public_key).await { + ONLINE_USERS + .send( + &conn_id, + ServerEvent::chat_request_response(recipient_public_key, accepted), + ) + .await; + } else { + // TODO: Create a table for chat request responses, and send them when + // the user logs in + } + + // We don't need to handle the case where the sender is blacklisted or + // whitelisted already, just add it if it is not already there + let _ = if accepted { + db.add_to_whitelist(recipient_user, sender_public_key).await + } else { + db.add_to_blacklist(recipient_user, sender_public_key).await + }; + + try_ws!( + db.remove_out_chat_request(&sender_user, &recipient_public_key) + .await + ); + + ServerEvent::message("Chat request response sent successfully.") +} diff --git a/crates/oxidetalis/src/websocket/mod.rs b/crates/oxidetalis/src/websocket/mod.rs index 8607236..8702700 100644 --- a/crates/oxidetalis/src/websocket/mod.rs +++ b/crates/oxidetalis/src/websocket/mod.rs @@ -219,6 +219,9 @@ async fn handle_events( ClientEventType::ChatRequest { to } => { Some(handlers::handle_chat_request(db, user, to).await) } + ClientEventType::ChatRequestResponse { to, accepted } => { + Some(handlers::handle_chat_response(db, user, to, *accepted).await) + } } } -- 2.45.2 From 558ceac74f30e22a9fa889e31af8045255138fb1 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 16 Jul 2024 15:40:04 +0300 Subject: [PATCH 24/55] chore: Fix typos Signed-off-by: Awiteb --- crates/oxidetalis/src/database/blacklist.rs | 2 +- crates/oxidetalis/src/database/whitelist.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/oxidetalis/src/database/blacklist.rs b/crates/oxidetalis/src/database/blacklist.rs index 9a4a712..2215ad2 100644 --- a/crates/oxidetalis/src/database/blacklist.rs +++ b/crates/oxidetalis/src/database/blacklist.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! Database extension to work with the balcklist table +//! Database extension to work with the blacklist table use chrono::Utc; use oxidetalis_core::types::PublicKey; diff --git a/crates/oxidetalis/src/database/whitelist.rs b/crates/oxidetalis/src/database/whitelist.rs index 91d3fa2..370407e 100644 --- a/crates/oxidetalis/src/database/whitelist.rs +++ b/crates/oxidetalis/src/database/whitelist.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! Database extension to work with the balcklist table +//! Database extension to work with the whitelist table use chrono::Utc; use oxidetalis_core::types::PublicKey; -- 2.45.2 From 89b3c091814ab9b5779b38c4117013678a3f0b2b Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 16 Jul 2024 15:37:00 +0300 Subject: [PATCH 25/55] chore: Add `EntityOrSelect` to entities prelude Signed-off-by: Awiteb --- crates/oxidetalis_entities/src/prelude.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/oxidetalis_entities/src/prelude.rs b/crates/oxidetalis_entities/src/prelude.rs index 8d23382..44eb108 100644 --- a/crates/oxidetalis_entities/src/prelude.rs +++ b/crates/oxidetalis_entities/src/prelude.rs @@ -22,6 +22,7 @@ pub use sea_orm::{ ActiveModelTrait, ColumnTrait, + EntityOrSelect, EntityTrait, IntoActiveModel, ModelTrait, -- 2.45.2 From b02e2d8b4db5b477a5da083dec3ea56f90abe62f Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 16 Jul 2024 15:38:49 +0300 Subject: [PATCH 26/55] feat: Create `WhiteListedUser` schema Signed-off-by: Awiteb --- crates/oxidetalis/src/schemas/user.rs | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/crates/oxidetalis/src/schemas/user.rs b/crates/oxidetalis/src/schemas/user.rs index 321ce28..dd90f09 100644 --- a/crates/oxidetalis/src/schemas/user.rs +++ b/crates/oxidetalis/src/schemas/user.rs @@ -14,7 +14,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use std::str::FromStr; + +use chrono::{DateTime, Utc}; use oxidetalis_core::{cipher::K256Secret, types::PublicKey}; +use oxidetalis_entities::prelude::*; use salvo::oapi::ToSchema; use serde::{Deserialize, Serialize}; @@ -25,3 +29,30 @@ pub struct RegisterUserBody { /// The public key of the user pub public_key: PublicKey, } + +#[derive(Serialize, Deserialize, Clone, Debug, ToSchema, derive_new::new)] +#[salvo(schema(name = WhiteListedUser, example = json!(WhiteListedUser::default())))] +pub struct WhiteListedUser { + /// User's public key + pub public_key: PublicKey, + /// When the user was whitelisted + pub whitelisted_at: DateTime, +} + +impl Default for WhiteListedUser { + fn default() -> Self { + WhiteListedUser::new( + PublicKey::from_str("bYhbrm61ov8GLZfskUYbsCLJTfaacMsuTBYgBABEH9dy").expect("is valid"), + Utc::now(), + ) + } +} + +impl From for WhiteListedUser { + fn from(user: WhitelistModel) -> Self { + Self { + public_key: PublicKey::from_str(&user.target).expect("Is valid public key"), + whitelisted_at: user.whitelisted_at, + } + } +} -- 2.45.2 From d7a63ea0922bd6dfe9494a018cffeca442034c0e Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 16 Jul 2024 15:41:02 +0300 Subject: [PATCH 27/55] feat: Whitelist extension to return user whitelisted users Signed-off-by: Awiteb --- crates/oxidetalis/src/database/whitelist.rs | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/oxidetalis/src/database/whitelist.rs b/crates/oxidetalis/src/database/whitelist.rs index 370407e..e09f6e0 100644 --- a/crates/oxidetalis/src/database/whitelist.rs +++ b/crates/oxidetalis/src/database/whitelist.rs @@ -16,6 +16,8 @@ //! Database extension to work with the whitelist table +use std::num::{NonZeroU32, NonZeroU8}; + use chrono::Utc; use oxidetalis_core::types::PublicKey; use oxidetalis_entities::prelude::*; @@ -40,6 +42,14 @@ pub trait WhiteListExt { whitelister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult<()>; + + /// Returns the whitelist of the user + async fn user_whitelist( + &self, + whitelister: &UserModel, + page: NonZeroU32, + page_size: NonZeroU8, + ) -> ServerResult>; } impl WhiteListExt for DatabaseConnection { @@ -78,4 +88,19 @@ impl WhiteListExt for DatabaseConnection { .await?; Ok(()) } + + async fn user_whitelist( + &self, + whitelister: &UserModel, + page: NonZeroU32, + page_size: NonZeroU8, + ) -> ServerResult> { + whitelister + .find_related(WhitelistEntity) + .select() + .paginate(self, u64::from(page_size.get())) + .fetch_page(u64::from(page.get() - 1)) + .await + .map_err(Into::into) + } } -- 2.45.2 From 43778a62fe6ce151e8fb0de78b8396f5b6c2e908 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 16 Jul 2024 15:44:21 +0300 Subject: [PATCH 28/55] feat: New API errors, `Querys` and `NotRegisteredUser` Signed-off-by: Awiteb --- crates/oxidetalis/src/errors.rs | 2 ++ crates/oxidetalis/src/routes/errors.rs | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/oxidetalis/src/errors.rs b/crates/oxidetalis/src/errors.rs index 68ef86a..f9b5057 100644 --- a/crates/oxidetalis/src/errors.rs +++ b/crates/oxidetalis/src/errors.rs @@ -53,6 +53,7 @@ impl From for ServerError { impl From for WsError { fn from(err: ServerError) -> Self { match err { + ServerError::Api(ApiError::NotRegisteredUser) => WsError::RegistredUserEvent, ServerError::Internal(_) | ServerError::Api(_) => WsError::InternalServerError, ServerError::Ws(err) => err, } @@ -62,6 +63,7 @@ impl From for WsError { impl From for ApiError { fn from(err: ServerError) -> Self { match err { + ServerError::Ws(WsError::RegistredUserEvent) => ApiError::NotRegisteredUser, ServerError::Internal(_) | ServerError::Ws(_) => ApiError::Internal, ServerError::Api(err) => err, } diff --git a/crates/oxidetalis/src/routes/errors.rs b/crates/oxidetalis/src/routes/errors.rs index 29686de..788559b 100644 --- a/crates/oxidetalis/src/routes/errors.rs +++ b/crates/oxidetalis/src/routes/errors.rs @@ -41,6 +41,13 @@ pub enum ApiError { /// (400 Bad Request) #[error("You entered two different public keys")] TwoDifferentKeys, + /// Error in the query parameters (400 Bad Request) + #[error("{0}")] + Querys(String), + /// Non registered user tried to access to registered user only endpoint + /// (403 Forbidden) + #[error("You are not a registered user, please register first")] + NotRegisteredUser, } impl ApiError { @@ -48,8 +55,10 @@ impl ApiError { pub const fn status_code(&self) -> StatusCode { match self { Self::Internal => StatusCode::INTERNAL_SERVER_ERROR, - Self::RegistrationClosed => StatusCode::FORBIDDEN, - Self::AlreadyRegistered | Self::TwoDifferentKeys => StatusCode::BAD_REQUEST, + Self::RegistrationClosed | Self::NotRegisteredUser => StatusCode::FORBIDDEN, + Self::AlreadyRegistered | Self::TwoDifferentKeys | Self::Querys(_) => { + StatusCode::BAD_REQUEST + } } } } -- 2.45.2 From d065d7ea956c417aa7dd7e2519d8b3d8d36f5120 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 16 Jul 2024 15:46:17 +0300 Subject: [PATCH 29/55] feat: Module for parameters This module comes with `Pagination` parameter Signed-off-by: Awiteb --- crates/oxidetalis/src/main.rs | 1 + crates/oxidetalis/src/parameters/mod.rs | 21 +++ .../oxidetalis/src/parameters/pagination.rs | 121 ++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 crates/oxidetalis/src/parameters/mod.rs create mode 100644 crates/oxidetalis/src/parameters/pagination.rs diff --git a/crates/oxidetalis/src/main.rs b/crates/oxidetalis/src/main.rs index 3b373a0..637cefc 100644 --- a/crates/oxidetalis/src/main.rs +++ b/crates/oxidetalis/src/main.rs @@ -30,6 +30,7 @@ mod extensions; mod macros; mod middlewares; mod nonce; +mod parameters; mod routes; mod schemas; mod utils; diff --git a/crates/oxidetalis/src/parameters/mod.rs b/crates/oxidetalis/src/parameters/mod.rs new file mode 100644 index 0000000..4fea6e9 --- /dev/null +++ b/crates/oxidetalis/src/parameters/mod.rs @@ -0,0 +1,21 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Set of route parameters for the API + +mod pagination; + +pub use pagination::*; diff --git a/crates/oxidetalis/src/parameters/pagination.rs b/crates/oxidetalis/src/parameters/pagination.rs new file mode 100644 index 0000000..c31a912 --- /dev/null +++ b/crates/oxidetalis/src/parameters/pagination.rs @@ -0,0 +1,121 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Pagination parameters for the API + +use std::{ + fmt, + num::{NonZeroU32, NonZeroU8}, + str::FromStr, +}; + +use salvo::{ + extract::Metadata as ExtractMetadata, + oapi::{ + Components as OapiComponents, + Object, + Parameter, + ParameterIn, + Parameters, + SchemaType, + ToParameters, + }, + Extractible, + Request, +}; +use serde_json::json; + +use crate::routes::{ApiError, ApiResult}; + +#[derive(Debug)] +pub struct Pagination { + /// The page number of the result + pub page: NonZeroU32, + /// The page size + pub page_size: NonZeroU8, +} + +impl<'ex> Extractible<'ex> for Pagination { + fn metadata() -> &'ex ExtractMetadata { + static METADATA: ExtractMetadata = ExtractMetadata::new(""); + &METADATA + } + + #[allow(refining_impl_trait)] + async fn extract(req: &'ex mut Request) -> ApiResult { + let page = extract_query(req, "page", NonZeroU32::new(1).expect("is non-zero"))?; + let page_size = extract_query(req, "page_size", NonZeroU8::new(10).expect("is non-zero"))?; + + Ok(Self { page, page_size }) + } + + #[allow(refining_impl_trait)] + async fn extract_with_arg(req: &'ex mut Request, _arg: &str) -> ApiResult { + Self::extract(req).await + } +} + +impl ToParameters<'_> for Pagination { + fn to_parameters(_components: &mut OapiComponents) -> Parameters { + Parameters::new() + .parameter(create_parameter( + "page", + "Page number, starting from 1", + 1, + f64::from(u32::MAX), + )) + .parameter(create_parameter( + "page_size", + "How many items per page, starting from 1", + 10, + f64::from(u8::MAX), + )) + } +} + +/// Extract a query parameter from the request +fn extract_query(req: &Request, name: &str, default_value: T) -> ApiResult +where + ::Err: fmt::Display, +{ + Ok(req + .queries() + .get(name) + .map(|p| p.parse::()) + .transpose() + .map_err(|err| ApiError::Querys(format!("Invalid value for `{name}` query ({err})")))? + .unwrap_or(default_value)) +} + +/// Create a parameter for the pagination +fn create_parameter(name: &str, description: &str, default: usize, max: f64) -> Parameter { + Parameter::new(name) + .parameter_in(ParameterIn::Query) + .required(false) + .description(description) + .example(json!(default)) + .schema( + Object::new() + .name(name) + .description(description) + .schema_type(SchemaType::Integer) + .default_value(json!(default)) + .example(json!(default)) + .maximum(max) + .minimum(1.0) + .read_only(true), + ) +} -- 2.45.2 From 411f7e37f0fad803c7128bdd37eacbb193deae19 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 16 Jul 2024 15:46:53 +0300 Subject: [PATCH 30/55] feat: Endpoint to returns user whitelisted users Signed-off-by: Awiteb --- crates/oxidetalis/src/routes/user.rs | 47 ++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/crates/oxidetalis/src/routes/user.rs b/crates/oxidetalis/src/routes/user.rs index 16a3e95..febdc14 100644 --- a/crates/oxidetalis/src/routes/user.rs +++ b/crates/oxidetalis/src/routes/user.rs @@ -20,7 +20,9 @@ use oxidetalis_core::types::{PublicKey, Signature}; use salvo::{ http::StatusCode, oapi::{endpoint, extract::JsonBody}, + writing::Json, Depot, + Extractible, Request, Router, Writer, @@ -28,10 +30,11 @@ use salvo::{ use super::{ApiError, ApiResult}; use crate::{ - database::UserTableExt, + database::{UserTableExt, WhiteListExt}, extensions::DepotExt, middlewares, - schemas::{EmptySchema, MessageSchema, RegisterUserBody}, + parameters::Pagination, + schemas::{EmptySchema, MessageSchema, RegisterUserBody, WhiteListedUser}, utils, }; @@ -79,10 +82,50 @@ pub async fn register( Ok(EmptySchema::new(StatusCode::CREATED)) } +/// (🔐) Get whitelisted users +#[endpoint( + operation_id = "whitelist", + tags("User"), + responses( + (status_code = 200, description = "Returns whitelisted users", content_type = "application/json", body = Vec), + (status_code = 403, description = "Not registered user, must register first", content_type = "application/json", body = MessageSchema), + (status_code = 401, description = "The entered signature or public key is invalid", content_type = "application/json", body = MessageSchema), + (status_code = 429, description = "Too many requests", content_type = "application/json", body = MessageSchema), + (status_code = 500, description = "Internal server error", content_type = "application/json", body = MessageSchema), + ), + parameters( + Pagination, + ("X-OTMP-PUBLIC" = PublicKey, Header, description = "Public key of the sender"), + ("X-OTMP-SIGNATURE" = Signature, Header, description = "Signature of the request"), + ), +)] +async fn user_whitelist( + req: &mut Request, + depot: &mut Depot, +) -> ApiResult>> { + let pagination = Pagination::extract(req).await?; + let conn = depot.db_conn(); + let user = conn + .get_user_by_pubk( + &utils::extract_public_key(req) + .expect("Public key should be checked in the middleware"), + ) + .await? + .ok_or(ApiError::NotRegisteredUser)?; + Ok(Json( + conn.user_whitelist(&user, pagination.page, pagination.page_size) + .await? + .into_iter() + .map(Into::into) + .collect(), + )) +} + /// The route of the endpoints of this module pub fn route() -> Router { Router::new() .push(Router::with_path("register").post(register)) + .push(Router::with_path("whitelist").get(user_whitelist)) .hoop(middlewares::public_key_check) .hoop(middlewares::signature_check) } -- 2.45.2 From 747e27c4af3144db069ad214a128de4ba38c41fb Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 16 Jul 2024 17:03:54 +0300 Subject: [PATCH 31/55] feat: Endpoint to returns user blacklisted users Signed-off-by: Awiteb --- crates/oxidetalis/src/database/blacklist.rs | 25 ++++++++++++ crates/oxidetalis/src/routes/user.rs | 44 ++++++++++++++++++++- crates/oxidetalis/src/schemas/user.rs | 27 +++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/crates/oxidetalis/src/database/blacklist.rs b/crates/oxidetalis/src/database/blacklist.rs index 2215ad2..f2f9229 100644 --- a/crates/oxidetalis/src/database/blacklist.rs +++ b/crates/oxidetalis/src/database/blacklist.rs @@ -16,6 +16,8 @@ //! Database extension to work with the blacklist table +use std::num::{NonZeroU32, NonZeroU8}; + use chrono::Utc; use oxidetalis_core::types::PublicKey; use oxidetalis_entities::prelude::*; @@ -39,6 +41,14 @@ pub trait BlackListExt { blacklister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult<()>; + + /// Returns the blacklist of the user + async fn user_blacklist( + &self, + blacklister: &UserModel, + page: NonZeroU32, + page_size: NonZeroU8, + ) -> ServerResult>; } impl BlackListExt for DatabaseConnection { @@ -78,4 +88,19 @@ impl BlackListExt for DatabaseConnection { .await?; Ok(()) } + + async fn user_blacklist( + &self, + blacklister: &UserModel, + page: NonZeroU32, + page_size: NonZeroU8, + ) -> ServerResult> { + blacklister + .find_related(BlacklistEntity) + .select() + .paginate(self, u64::from(page_size.get())) + .fetch_page(u64::from(page.get() - 1)) + .await + .map_err(Into::into) + } } diff --git a/crates/oxidetalis/src/routes/user.rs b/crates/oxidetalis/src/routes/user.rs index febdc14..abd384d 100644 --- a/crates/oxidetalis/src/routes/user.rs +++ b/crates/oxidetalis/src/routes/user.rs @@ -30,11 +30,11 @@ use salvo::{ use super::{ApiError, ApiResult}; use crate::{ - database::{UserTableExt, WhiteListExt}, + database::{BlackListExt, UserTableExt, WhiteListExt}, extensions::DepotExt, middlewares, parameters::Pagination, - schemas::{EmptySchema, MessageSchema, RegisterUserBody, WhiteListedUser}, + schemas::{BlackListedUser, EmptySchema, MessageSchema, RegisterUserBody, WhiteListedUser}, utils, }; @@ -121,11 +121,51 @@ async fn user_whitelist( )) } +/// (🔐) Get blacklisted users +#[endpoint( + operation_id = "blacklist", + tags("User"), + responses( + (status_code = 200, description = "Returns blacklisted users", content_type = "application/json", body = Vec), + (status_code = 403, description = "Not registered user, must register first", content_type = "application/json", body = MessageSchema), + (status_code = 401, description = "The entered signature or public key is invalid", content_type = "application/json", body = MessageSchema), + (status_code = 429, description = "Too many requests", content_type = "application/json", body = MessageSchema), + (status_code = 500, description = "Internal server error", content_type = "application/json", body = MessageSchema), + ), + parameters( + Pagination, + ("X-OTMP-PUBLIC" = PublicKey, Header, description = "Public key of the sender"), + ("X-OTMP-SIGNATURE" = Signature, Header, description = "Signature of the request"), + ), +)] +async fn user_blacklist( + req: &mut Request, + depot: &mut Depot, +) -> ApiResult>> { + let pagination = Pagination::extract(req).await?; + let conn = depot.db_conn(); + let user = conn + .get_user_by_pubk( + &utils::extract_public_key(req) + .expect("Public key should be checked in the middleware"), + ) + .await? + .ok_or(ApiError::NotRegisteredUser)?; + Ok(Json( + conn.user_blacklist(&user, pagination.page, pagination.page_size) + .await? + .into_iter() + .map(Into::into) + .collect(), + )) +} + /// The route of the endpoints of this module pub fn route() -> Router { Router::new() .push(Router::with_path("register").post(register)) .push(Router::with_path("whitelist").get(user_whitelist)) + .push(Router::with_path("blacklist").get(user_blacklist)) .hoop(middlewares::public_key_check) .hoop(middlewares::signature_check) } diff --git a/crates/oxidetalis/src/schemas/user.rs b/crates/oxidetalis/src/schemas/user.rs index dd90f09..c853ebd 100644 --- a/crates/oxidetalis/src/schemas/user.rs +++ b/crates/oxidetalis/src/schemas/user.rs @@ -39,6 +39,15 @@ pub struct WhiteListedUser { pub whitelisted_at: DateTime, } +#[derive(Serialize, Deserialize, Clone, Debug, ToSchema, derive_new::new)] +#[salvo(schema(name = BlackListedUser, example = json!(BlackListedUser::default())))] +pub struct BlackListedUser { + /// User's public key + pub public_key: PublicKey, + /// When the user was blacklisted + pub blacklisted_at: DateTime, +} + impl Default for WhiteListedUser { fn default() -> Self { WhiteListedUser::new( @@ -56,3 +65,21 @@ impl From for WhiteListedUser { } } } + +impl Default for BlackListedUser { + fn default() -> Self { + BlackListedUser::new( + PublicKey::from_str("bYhbrm61ov8GLZfskUYbsCLJTfaacMsuTBYgBABEH9dy").expect("is valid"), + Utc::now(), + ) + } +} + +impl From for BlackListedUser { + fn from(user: BlacklistModel) -> Self { + Self { + public_key: PublicKey::from_str(&user.target).expect("Is valid public key"), + blacklisted_at: user.blacklisted_at, + } + } +} -- 2.45.2 From 1857365aac68aa581da8d2210f92feccae9a6778 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 16 Jul 2024 17:23:18 +0300 Subject: [PATCH 32/55] fix: Remove user from whitelist table before add it to blacklist one Same thing with blacklist table Signed-off-by: Awiteb --- crates/oxidetalis/src/database/blacklist.rs | 31 ++++++++++++++++++++- crates/oxidetalis/src/database/whitelist.rs | 30 +++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/crates/oxidetalis/src/database/blacklist.rs b/crates/oxidetalis/src/database/blacklist.rs index f2f9229..1749b96 100644 --- a/crates/oxidetalis/src/database/blacklist.rs +++ b/crates/oxidetalis/src/database/blacklist.rs @@ -23,6 +23,7 @@ use oxidetalis_core::types::PublicKey; use oxidetalis_entities::prelude::*; use sea_orm::DatabaseConnection; +use super::WhiteListExt; use crate::{errors::ServerResult, websocket::errors::WsError}; /// Extension trait for the `DatabaseConnection` to work with the blacklist @@ -35,13 +36,22 @@ pub trait BlackListExt { blacklister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult; - /// Add the `target_public_key` to the blacklist of the `blacklister` + + /// Add the `target_public_key` to the blacklist of the `blacklister` and + /// remove it from the whitelist table (if it's there) async fn add_to_blacklist( &self, blacklister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult<()>; + /// Remove the target from blacklist table + async fn remove_from_blacklist( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()>; + /// Returns the blacklist of the user async fn user_blacklist( &self, @@ -78,6 +88,9 @@ impl BlackListExt for DatabaseConnection { if blacklister.public_key == target_public_key.to_string() { return Err(WsError::CannotAddSelfToBlacklist.into()); } + self.remove_from_whitelist(blacklister, target_public_key) + .await?; + BlacklistActiveModel { user_id: Set(blacklister.id), target: Set(target_public_key.to_string()), @@ -89,6 +102,22 @@ impl BlackListExt for DatabaseConnection { Ok(()) } + async fn remove_from_blacklist( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()> { + if let Some(target_user) = blacklister + .find_related(BlacklistEntity) + .filter(BlacklistColumn::Target.eq(target_public_key.to_string())) + .one(self) + .await? + { + target_user.delete(self).await?; + } + Ok(()) + } + async fn user_blacklist( &self, blacklister: &UserModel, diff --git a/crates/oxidetalis/src/database/whitelist.rs b/crates/oxidetalis/src/database/whitelist.rs index e09f6e0..7cb74ba 100644 --- a/crates/oxidetalis/src/database/whitelist.rs +++ b/crates/oxidetalis/src/database/whitelist.rs @@ -23,6 +23,7 @@ use oxidetalis_core::types::PublicKey; use oxidetalis_entities::prelude::*; use sea_orm::DatabaseConnection; +use super::BlackListExt; use crate::{errors::ServerResult, websocket::errors::WsError}; /// Extension trait for the `DatabaseConnection` to work with the whitelist @@ -36,13 +37,21 @@ pub trait WhiteListExt { target_public_key: &PublicKey, ) -> ServerResult; - /// Add the `target_public_key` to the whitelist of the `whitelister` + /// Add the `target_public_key` to the whitelist of the `whitelister` and + /// remove it from the blacklist table (if it's there) async fn add_to_whitelist( &self, whitelister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult<()>; + /// Remove the target from whitelist table + async fn remove_from_whitelist( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()>; + /// Returns the whitelist of the user async fn user_whitelist( &self, @@ -78,6 +87,9 @@ impl WhiteListExt for DatabaseConnection { if whitelister.public_key == target_public_key.to_string() { return Err(WsError::CannotAddSelfToWhitelist.into()); } + self.remove_from_blacklist(whitelister, target_public_key) + .await?; + WhitelistActiveModel { user_id: Set(whitelister.id), target: Set(target_public_key.to_string()), @@ -89,6 +101,22 @@ impl WhiteListExt for DatabaseConnection { Ok(()) } + async fn remove_from_whitelist( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()> { + if let Some(target_user) = whitelister + .find_related(WhitelistEntity) + .filter(WhitelistColumn::Target.eq(target_public_key.to_string())) + .one(self) + .await? + { + target_user.delete(self).await?; + } + Ok(()) + } + async fn user_whitelist( &self, whitelister: &UserModel, -- 2.45.2 From 727b985847d7b1da4d6210891867ef77bf6d3422 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 18:30:55 +0300 Subject: [PATCH 33/55] chore: Fix typo in the `AlreadyOnTheBlacklist` error name Signed-off-by: Awiteb --- crates/oxidetalis/src/websocket/errors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxidetalis/src/websocket/errors.rs b/crates/oxidetalis/src/websocket/errors.rs index d9ce4c7..c24798c 100644 --- a/crates/oxidetalis/src/websocket/errors.rs +++ b/crates/oxidetalis/src/websocket/errors.rs @@ -31,7 +31,7 @@ ws_errors! { UserNotFound = "The user is not registered in the server", AlreadyOnTheWhitelist = "The user is already on your whitelist", CannotAddSelfToWhitelist = "You cannot add yourself to the whitelist", - AlreadyOnTheblacklist = "The user is already on your blacklist", + AlreadyOnTheBlacklist = "The user is already on your blacklist", CannotAddSelfToBlacklist = "You cannot add yourself to the blacklist", AlreadySendChatRequest = "You have already sent a chat request to this user", CannotSendChatRequestToSelf = "You cannot send a chat request to yourself", -- 2.45.2 From a894089b194fb469827ef048003a0c514fd07973 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 18:33:34 +0300 Subject: [PATCH 34/55] chore: Make the `Users` enum public Signed-off-by: Awiteb --- crates/oxidetalis_migrations/src/create_users_table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxidetalis_migrations/src/create_users_table.rs b/crates/oxidetalis_migrations/src/create_users_table.rs index 6e1b671..e069b89 100644 --- a/crates/oxidetalis_migrations/src/create_users_table.rs +++ b/crates/oxidetalis_migrations/src/create_users_table.rs @@ -58,7 +58,7 @@ impl MigrationTrait for Migration { } #[derive(DeriveIden)] -enum Users { +pub enum Users { Table, Id, PublicKey, -- 2.45.2 From 3d086e511cd6ac1a3254de278e39b42c8a2811ca Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 18:34:39 +0300 Subject: [PATCH 35/55] feat: Replace `whitelist` & `blacklist` tables with `users_status` table Signed-off-by: Awiteb --- crates/oxidetalis/src/database/blacklist.rs | 135 -------- crates/oxidetalis/src/database/mod.rs | 6 +- crates/oxidetalis/src/database/user_status.rs | 287 ++++++++++++++++++ crates/oxidetalis/src/database/whitelist.rs | 134 -------- crates/oxidetalis/src/routes/user.rs | 2 +- crates/oxidetalis/src/schemas/user.rs | 12 +- .../src/websocket/handlers/chat_request.rs | 2 +- crates/oxidetalis_entities/src/lib.rs | 3 +- crates/oxidetalis_entities/src/prelude.rs | 17 +- crates/oxidetalis_entities/src/users.rs | 16 +- .../src/{blacklist.rs => users_status.rs} | 20 +- crates/oxidetalis_entities/src/whitelist.rs | 56 ---- ...cklist_table.rs => create_users_status.rs} | 67 +++- .../src/create_whitelist_table.rs | 63 ---- crates/oxidetalis_migrations/src/lib.rs | 6 +- 15 files changed, 383 insertions(+), 443 deletions(-) delete mode 100644 crates/oxidetalis/src/database/blacklist.rs create mode 100644 crates/oxidetalis/src/database/user_status.rs delete mode 100644 crates/oxidetalis/src/database/whitelist.rs rename crates/oxidetalis_entities/src/{blacklist.rs => users_status.rs} (78%) delete mode 100644 crates/oxidetalis_entities/src/whitelist.rs rename crates/oxidetalis_migrations/src/{create_blacklist_table.rs => create_users_status.rs} (52%) delete mode 100644 crates/oxidetalis_migrations/src/create_whitelist_table.rs diff --git a/crates/oxidetalis/src/database/blacklist.rs b/crates/oxidetalis/src/database/blacklist.rs deleted file mode 100644 index 1749b96..0000000 --- a/crates/oxidetalis/src/database/blacklist.rs +++ /dev/null @@ -1,135 +0,0 @@ -// OxideTalis Messaging Protocol homeserver implementation -// Copyright (C) 2024 OxideTalis Developers -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -//! Database extension to work with the blacklist table - -use std::num::{NonZeroU32, NonZeroU8}; - -use chrono::Utc; -use oxidetalis_core::types::PublicKey; -use oxidetalis_entities::prelude::*; -use sea_orm::DatabaseConnection; - -use super::WhiteListExt; -use crate::{errors::ServerResult, websocket::errors::WsError}; - -/// Extension trait for the `DatabaseConnection` to work with the blacklist -/// table -pub trait BlackListExt { - /// Returns ture if the `blacklister` are blacklisted the - /// `target_public_key` - async fn is_blacklisted( - &self, - blacklister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult; - - /// Add the `target_public_key` to the blacklist of the `blacklister` and - /// remove it from the whitelist table (if it's there) - async fn add_to_blacklist( - &self, - blacklister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult<()>; - - /// Remove the target from blacklist table - async fn remove_from_blacklist( - &self, - blacklister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult<()>; - - /// Returns the blacklist of the user - async fn user_blacklist( - &self, - blacklister: &UserModel, - page: NonZeroU32, - page_size: NonZeroU8, - ) -> ServerResult>; -} - -impl BlackListExt for DatabaseConnection { - #[logcall::logcall] - async fn is_blacklisted( - &self, - blacklister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult { - blacklister - .find_related(BlacklistEntity) - .filter(BlacklistColumn::Target.eq(target_public_key.to_string())) - .one(self) - .await - .map(|u| u.is_some()) - .map_err(Into::into) - } - - async fn add_to_blacklist( - &self, - blacklister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult<()> { - if self.is_blacklisted(blacklister, target_public_key).await? { - return Err(WsError::AlreadyOnTheblacklist.into()); - } - if blacklister.public_key == target_public_key.to_string() { - return Err(WsError::CannotAddSelfToBlacklist.into()); - } - self.remove_from_whitelist(blacklister, target_public_key) - .await?; - - BlacklistActiveModel { - user_id: Set(blacklister.id), - target: Set(target_public_key.to_string()), - blacklisted_at: Set(Utc::now()), - ..Default::default() - } - .save(self) - .await?; - Ok(()) - } - - async fn remove_from_blacklist( - &self, - blacklister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult<()> { - if let Some(target_user) = blacklister - .find_related(BlacklistEntity) - .filter(BlacklistColumn::Target.eq(target_public_key.to_string())) - .one(self) - .await? - { - target_user.delete(self).await?; - } - Ok(()) - } - - async fn user_blacklist( - &self, - blacklister: &UserModel, - page: NonZeroU32, - page_size: NonZeroU8, - ) -> ServerResult> { - blacklister - .find_related(BlacklistEntity) - .select() - .paginate(self, u64::from(page_size.get())) - .fetch_page(u64::from(page.get() - 1)) - .await - .map_err(Into::into) - } -} diff --git a/crates/oxidetalis/src/database/mod.rs b/crates/oxidetalis/src/database/mod.rs index dc03f6a..c4011ac 100644 --- a/crates/oxidetalis/src/database/mod.rs +++ b/crates/oxidetalis/src/database/mod.rs @@ -16,14 +16,12 @@ //! Database utilities for the OxideTalis homeserver. -mod blacklist; mod in_chat_requests; mod out_chat_requests; mod user; -mod whitelist; +mod user_status; -pub use blacklist::*; pub use in_chat_requests::*; pub use out_chat_requests::*; pub use user::*; -pub use whitelist::*; +pub use user_status::*; diff --git a/crates/oxidetalis/src/database/user_status.rs b/crates/oxidetalis/src/database/user_status.rs new file mode 100644 index 0000000..152e1c8 --- /dev/null +++ b/crates/oxidetalis/src/database/user_status.rs @@ -0,0 +1,287 @@ +// OxideTalis Messaging Protocol homeserver implementation +// Copyright (C) 2024 OxideTalis Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Database extension to work with the whitelist table + +use std::num::{NonZeroU32, NonZeroU8}; + +use chrono::Utc; +use oxidetalis_core::types::PublicKey; +use oxidetalis_entities::prelude::*; +use sea_orm::DatabaseConnection; + +use crate::{errors::ServerResult, websocket::errors::WsError}; + +/// Extension trait for the `DatabaseConnection` to work with the whitelist +/// table +pub trait UsersStatusExt { + /// Returns ture if the `whitelister` are whitelisted the + /// `target_public_key` + async fn is_whitelisted( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult; + + /// Returns ture if the `blacklister` are blacklisted the + /// `target_public_key` + async fn is_blacklisted( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult; + + /// Add the `target_public_key` to the whitelist of the `whitelister` and + /// remove it from the blacklist table (if it's there) + async fn add_to_whitelist( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()>; + + /// Add the `target_public_key` to the blacklist of the `blacklister` and + /// remove it from the whitelist table (if it's there) + async fn add_to_blacklist( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()>; + + /// Remove the target from whitelist table + // FIXME(awiteb): This method will be used when I work on decentralization, So, I'm keeping it + // for now + #[allow(dead_code)] + async fn remove_from_whitelist( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()>; + + /// Remove the target from blacklist table + // FIXME(awiteb): This method will be used when I work on decentralization, So, I'm keeping it + // for now + #[allow(dead_code)] + async fn remove_from_blacklist( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()>; + + /// Returns the whitelist of the user + async fn user_whitelist( + &self, + whitelister: &UserModel, + page: NonZeroU32, + page_size: NonZeroU8, + ) -> ServerResult>; + + /// Returns the blacklist of the user + async fn user_blacklist( + &self, + blacklister: &UserModel, + page: NonZeroU32, + page_size: NonZeroU8, + ) -> ServerResult>; +} + +impl UsersStatusExt for DatabaseConnection { + async fn is_whitelisted( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult { + whitelister + .find_related(UsersStatusEntity) + .filter( + UsersStatusColumn::Target + .eq(target_public_key.to_string()) + .and(UsersStatusColumn::Status.eq(AccessStatus::Whitelisted)), + ) + .one(self) + .await + .map(|u| u.is_some()) + .map_err(Into::into) + } + + async fn is_blacklisted( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult { + blacklister + .find_related(UsersStatusEntity) + .filter( + UsersStatusColumn::Target + .eq(target_public_key.to_string()) + .and(UsersStatusColumn::Status.eq(AccessStatus::Blacklisted)), + ) + .one(self) + .await + .map(|u| u.is_some()) + .map_err(Into::into) + } + + async fn add_to_whitelist( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()> { + if self.is_whitelisted(whitelister, target_public_key).await? { + return Err(WsError::AlreadyOnTheWhitelist.into()); + } + if whitelister.public_key == target_public_key.to_string() { + return Err(WsError::CannotAddSelfToWhitelist.into()); + } + + if let Some(mut user) = get_user_status(self, whitelister, target_public_key) + .await? + .map(IntoActiveModel::into_active_model) + { + user.status = Set(AccessStatus::Whitelisted); + user.updated_at = Set(Utc::now()); + user.update(self).await?; + } else { + UsersStatusActiveModel { + user_id: Set(whitelister.id), + target: Set(target_public_key.to_string()), + status: Set(AccessStatus::Whitelisted), + updated_at: Set(Utc::now()), + ..Default::default() + } + .save(self) + .await?; + } + + Ok(()) + } + + async fn add_to_blacklist( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()> { + if self.is_blacklisted(blacklister, target_public_key).await? { + return Err(WsError::AlreadyOnTheBlacklist.into()); + } + if blacklister.public_key == target_public_key.to_string() { + return Err(WsError::CannotAddSelfToBlacklist.into()); + } + + if let Some(mut user) = get_user_status(self, blacklister, target_public_key) + .await? + .map(IntoActiveModel::into_active_model) + { + user.status = Set(AccessStatus::Blacklisted); + user.updated_at = Set(Utc::now()); + user.update(self).await?; + } else { + UsersStatusActiveModel { + user_id: Set(blacklister.id), + target: Set(target_public_key.to_string()), + status: Set(AccessStatus::Blacklisted), + updated_at: Set(Utc::now()), + ..Default::default() + } + .save(self) + .await?; + } + + Ok(()) + } + + async fn remove_from_whitelist( + &self, + whitelister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()> { + if let Some(target_user) = whitelister + .find_related(UsersStatusEntity) + .filter( + UsersStatusColumn::Target + .eq(target_public_key.to_string()) + .and(UsersStatusColumn::Status.eq(AccessStatus::Whitelisted)), + ) + .one(self) + .await? + { + target_user.delete(self).await?; + } + Ok(()) + } + + async fn remove_from_blacklist( + &self, + blacklister: &UserModel, + target_public_key: &PublicKey, + ) -> ServerResult<()> { + if let Some(target_user) = blacklister + .find_related(UsersStatusEntity) + .filter( + UsersStatusColumn::Target + .eq(target_public_key.to_string()) + .and(UsersStatusColumn::Status.eq(AccessStatus::Blacklisted)), + ) + .one(self) + .await? + { + target_user.delete(self).await?; + } + Ok(()) + } + + async fn user_whitelist( + &self, + whitelister: &UserModel, + page: NonZeroU32, + page_size: NonZeroU8, + ) -> ServerResult> { + whitelister + .find_related(UsersStatusEntity) + .filter(UsersStatusColumn::Status.eq(AccessStatus::Whitelisted)) + .paginate(self, u64::from(page_size.get())) + .fetch_page(u64::from(page.get() - 1)) + .await + .map_err(Into::into) + } + + async fn user_blacklist( + &self, + blacklister: &UserModel, + page: NonZeroU32, + page_size: NonZeroU8, + ) -> ServerResult> { + blacklister + .find_related(UsersStatusEntity) + .filter(UsersStatusColumn::Status.eq(AccessStatus::Blacklisted)) + .paginate(self, u64::from(page_size.get())) + .fetch_page(u64::from(page.get() - 1)) + .await + .map_err(Into::into) + } +} + +/// Returns user from user_status table by the entered and target public key +async fn get_user_status( + conn: &DatabaseConnection, + user: &UserModel, + target_public_key: &PublicKey, +) -> ServerResult> { + user.find_related(UsersStatusEntity) + .filter(UsersStatusColumn::Target.eq(target_public_key.to_string())) + .one(conn) + .await + .map_err(Into::into) +} diff --git a/crates/oxidetalis/src/database/whitelist.rs b/crates/oxidetalis/src/database/whitelist.rs deleted file mode 100644 index 7cb74ba..0000000 --- a/crates/oxidetalis/src/database/whitelist.rs +++ /dev/null @@ -1,134 +0,0 @@ -// OxideTalis Messaging Protocol homeserver implementation -// Copyright (C) 2024 OxideTalis Developers -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -//! Database extension to work with the whitelist table - -use std::num::{NonZeroU32, NonZeroU8}; - -use chrono::Utc; -use oxidetalis_core::types::PublicKey; -use oxidetalis_entities::prelude::*; -use sea_orm::DatabaseConnection; - -use super::BlackListExt; -use crate::{errors::ServerResult, websocket::errors::WsError}; - -/// Extension trait for the `DatabaseConnection` to work with the whitelist -/// table -pub trait WhiteListExt { - /// Returns ture if the `whitelister` are whitelisted the - /// `target_public_key` - async fn is_whitelisted( - &self, - whitelister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult; - - /// Add the `target_public_key` to the whitelist of the `whitelister` and - /// remove it from the blacklist table (if it's there) - async fn add_to_whitelist( - &self, - whitelister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult<()>; - - /// Remove the target from whitelist table - async fn remove_from_whitelist( - &self, - whitelister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult<()>; - - /// Returns the whitelist of the user - async fn user_whitelist( - &self, - whitelister: &UserModel, - page: NonZeroU32, - page_size: NonZeroU8, - ) -> ServerResult>; -} - -impl WhiteListExt for DatabaseConnection { - async fn is_whitelisted( - &self, - whitelister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult { - whitelister - .find_related(WhitelistEntity) - .filter(WhitelistColumn::Target.eq(target_public_key.to_string())) - .one(self) - .await - .map(|u| u.is_some()) - .map_err(Into::into) - } - - async fn add_to_whitelist( - &self, - whitelister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult<()> { - if self.is_whitelisted(whitelister, target_public_key).await? { - return Err(WsError::AlreadyOnTheWhitelist.into()); - } - if whitelister.public_key == target_public_key.to_string() { - return Err(WsError::CannotAddSelfToWhitelist.into()); - } - self.remove_from_blacklist(whitelister, target_public_key) - .await?; - - WhitelistActiveModel { - user_id: Set(whitelister.id), - target: Set(target_public_key.to_string()), - whitelisted_at: Set(Utc::now()), - ..Default::default() - } - .save(self) - .await?; - Ok(()) - } - - async fn remove_from_whitelist( - &self, - whitelister: &UserModel, - target_public_key: &PublicKey, - ) -> ServerResult<()> { - if let Some(target_user) = whitelister - .find_related(WhitelistEntity) - .filter(WhitelistColumn::Target.eq(target_public_key.to_string())) - .one(self) - .await? - { - target_user.delete(self).await?; - } - Ok(()) - } - - async fn user_whitelist( - &self, - whitelister: &UserModel, - page: NonZeroU32, - page_size: NonZeroU8, - ) -> ServerResult> { - whitelister - .find_related(WhitelistEntity) - .select() - .paginate(self, u64::from(page_size.get())) - .fetch_page(u64::from(page.get() - 1)) - .await - .map_err(Into::into) - } -} diff --git a/crates/oxidetalis/src/routes/user.rs b/crates/oxidetalis/src/routes/user.rs index abd384d..7a0f423 100644 --- a/crates/oxidetalis/src/routes/user.rs +++ b/crates/oxidetalis/src/routes/user.rs @@ -30,7 +30,7 @@ use salvo::{ use super::{ApiError, ApiResult}; use crate::{ - database::{BlackListExt, UserTableExt, WhiteListExt}, + database::{UserTableExt, UsersStatusExt}, extensions::DepotExt, middlewares, parameters::Pagination, diff --git a/crates/oxidetalis/src/schemas/user.rs b/crates/oxidetalis/src/schemas/user.rs index c853ebd..4351d77 100644 --- a/crates/oxidetalis/src/schemas/user.rs +++ b/crates/oxidetalis/src/schemas/user.rs @@ -57,11 +57,11 @@ impl Default for WhiteListedUser { } } -impl From for WhiteListedUser { - fn from(user: WhitelistModel) -> Self { +impl From for WhiteListedUser { + fn from(user: UsersStatusModel) -> Self { Self { public_key: PublicKey::from_str(&user.target).expect("Is valid public key"), - whitelisted_at: user.whitelisted_at, + whitelisted_at: user.updated_at, } } } @@ -75,11 +75,11 @@ impl Default for BlackListedUser { } } -impl From for BlackListedUser { - fn from(user: BlacklistModel) -> Self { +impl From for BlackListedUser { + fn from(user: UsersStatusModel) -> Self { Self { public_key: PublicKey::from_str(&user.target).expect("Is valid public key"), - blacklisted_at: user.blacklisted_at, + blacklisted_at: user.updated_at, } } } diff --git a/crates/oxidetalis/src/websocket/handlers/chat_request.rs b/crates/oxidetalis/src/websocket/handlers/chat_request.rs index 9519347..c3c31e3 100644 --- a/crates/oxidetalis/src/websocket/handlers/chat_request.rs +++ b/crates/oxidetalis/src/websocket/handlers/chat_request.rs @@ -25,7 +25,7 @@ use sea_orm::DatabaseConnection; use crate::database::InChatRequestsExt; use crate::extensions::OnlineUsersExt; use crate::{ - database::{BlackListExt, OutChatRequestsExt, UserTableExt, WhiteListExt}, + database::{OutChatRequestsExt, UserTableExt, UsersStatusExt}, try_ws, websocket::{errors::WsError, ServerEvent, Unsigned, ONLINE_USERS}, }; diff --git a/crates/oxidetalis_entities/src/lib.rs b/crates/oxidetalis_entities/src/lib.rs index 3c72884..7ed4e2c 100644 --- a/crates/oxidetalis_entities/src/lib.rs +++ b/crates/oxidetalis_entities/src/lib.rs @@ -19,9 +19,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -pub mod blacklist; pub mod incoming_chat_requests; pub mod outgoing_chat_requests; pub mod prelude; pub mod users; -pub mod whitelist; +pub mod users_status; diff --git a/crates/oxidetalis_entities/src/prelude.rs b/crates/oxidetalis_entities/src/prelude.rs index 44eb108..5adcf81 100644 --- a/crates/oxidetalis_entities/src/prelude.rs +++ b/crates/oxidetalis_entities/src/prelude.rs @@ -38,12 +38,6 @@ pub use sea_orm::{ /// User ID type pub type UserId = i64; -pub use super::blacklist::{ - ActiveModel as BlacklistActiveModel, - Column as BlacklistColumn, - Entity as BlacklistEntity, - Model as BlacklistModel, -}; pub use super::incoming_chat_requests::{ ActiveModel as InChatRequestsActiveModel, Column as InChatRequestsColumn, @@ -62,9 +56,10 @@ pub use super::users::{ Entity as UserEntity, Model as UserModel, }; -pub use super::whitelist::{ - ActiveModel as WhitelistActiveModel, - Column as WhitelistColumn, - Entity as WhitelistEntity, - Model as WhitelistModel, +pub use super::users_status::{ + AccessStatus, + ActiveModel as UsersStatusActiveModel, + Column as UsersStatusColumn, + Entity as UsersStatusEntity, + Model as UsersStatusModel, }; diff --git a/crates/oxidetalis_entities/src/users.rs b/crates/oxidetalis_entities/src/users.rs index cd6b172..675e589 100644 --- a/crates/oxidetalis_entities/src/users.rs +++ b/crates/oxidetalis_entities/src/users.rs @@ -38,10 +38,8 @@ pub enum Relation { InChatRequests, #[sea_orm(has_many = "OutChatRequestsEntity")] OutChatRequests, - #[sea_orm(has_many = "BlacklistEntity")] - Blacklist, - #[sea_orm(has_many = "WhitelistEntity")] - Whitelist, + #[sea_orm(has_many = "UsersStatusEntity")] + UsersStatus, } impl Related for Entity { @@ -56,15 +54,9 @@ impl Related for Entity { } } -impl Related for Entity { +impl Related for Entity { fn to() -> RelationDef { - Relation::Blacklist.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Whitelist.def() + Relation::UsersStatus.def() } } diff --git a/crates/oxidetalis_entities/src/blacklist.rs b/crates/oxidetalis_entities/src/users_status.rs similarity index 78% rename from crates/oxidetalis_entities/src/blacklist.rs rename to crates/oxidetalis_entities/src/users_status.rs index 848432d..3459192 100644 --- a/crates/oxidetalis_entities/src/blacklist.rs +++ b/crates/oxidetalis_entities/src/users_status.rs @@ -24,15 +24,25 @@ use sea_orm::entity::prelude::*; use crate::prelude::*; +#[derive(Debug, Clone, Eq, PartialEq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "access_status")] +pub enum AccessStatus { + #[sea_orm(string_value = "whitelisted")] + Whitelisted, + #[sea_orm(string_value = "blacklisted")] + Blacklisted, +} + #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "blacklist")] +#[sea_orm(table_name = "users_status")] pub struct Model { #[sea_orm(primary_key)] - pub id: UserId, - pub user_id: UserId, + pub id: UserId, + pub user_id: UserId, /// Public key of the target - pub target: String, - pub blacklisted_at: chrono::DateTime, + pub target: String, + pub status: AccessStatus, + pub updated_at: chrono::DateTime, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/oxidetalis_entities/src/whitelist.rs b/crates/oxidetalis_entities/src/whitelist.rs deleted file mode 100644 index b1bee22..0000000 --- a/crates/oxidetalis_entities/src/whitelist.rs +++ /dev/null @@ -1,56 +0,0 @@ -// OxideTalis Messaging Protocol homeserver core implementation -// Copyright (c) 2024 OxideTalis Developers -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -use chrono::Utc; -use sea_orm::entity::prelude::*; - -use crate::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "whitelist")] -pub struct Model { - #[sea_orm(primary_key)] - pub id: UserId, - pub user_id: UserId, - /// Public key of the target - pub target: String, - pub whitelisted_at: chrono::DateTime, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "UserEntity", - from = "Column::UserId", - to = "super::users::Column::Id" - on_update = "NoAction", - on_delete = "Cascade" - )] - UserId, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::UserId.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/oxidetalis_migrations/src/create_blacklist_table.rs b/crates/oxidetalis_migrations/src/create_users_status.rs similarity index 52% rename from crates/oxidetalis_migrations/src/create_blacklist_table.rs rename to crates/oxidetalis_migrations/src/create_users_status.rs index 2eaf805..ad57d00 100644 --- a/crates/oxidetalis_migrations/src/create_blacklist_table.rs +++ b/crates/oxidetalis_migrations/src/create_users_status.rs @@ -19,31 +19,59 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use std::fmt; + +use sea_orm::sea_query::extension::postgres::Type; use sea_orm_migration::prelude::*; +use super::create_users_table::Users; + #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_type( + Type::create() + .as_enum(AccessStatus::Name) + .values(vec![AccessStatus::Whitelisted, AccessStatus::Blacklisted]) + .to_owned(), + ) + .await?; + manager .create_table( Table::create() - .table(Blacklist::Table) + .table(UsersStatus::Table) .if_not_exists() .col( - ColumnDef::new(Blacklist::Id) + ColumnDef::new(UsersStatus::Id) .big_integer() .not_null() .auto_increment() .primary_key(), ) - .col(ColumnDef::new(Blacklist::UserId).big_integer().not_null()) - .col(ColumnDef::new(Blacklist::Target).string().not_null()) - .col(ColumnDef::new(Blacklist::Reason).string_len(400)) + .foreign_key( + ForeignKey::create() + .name("fk-users_status-users") + .from(UsersStatus::Table, UsersStatus::UserId) + .to(Users::Table, Users::Id) + .on_update(ForeignKeyAction::NoAction) + .on_delete(ForeignKeyAction::Cascade), + ) + .col(ColumnDef::new(UsersStatus::Target).string().not_null()) .col( - ColumnDef::new(Blacklist::BlacklistedAt) + ColumnDef::new(UsersStatus::Status) + .enumeration( + AccessStatus::Name, + [AccessStatus::Whitelisted, AccessStatus::Blacklisted], + ) + .not_null(), + ) + .col( + ColumnDef::new(UsersStatus::UpdatedAt) .timestamp_with_time_zone() .not_null(), ) @@ -53,13 +81,34 @@ impl MigrationTrait for Migration { } } +enum AccessStatus { + Name, + Whitelisted, + Blacklisted, +} + #[derive(DeriveIden)] -enum Blacklist { +enum UsersStatus { Table, Id, UserId, /// Public key of the target Target, - Reason, - BlacklistedAt, + Status, + UpdatedAt, +} + +impl Iden for AccessStatus { + fn unquoted(&self, s: &mut dyn fmt::Write) { + write!( + s, + "{}", + match self { + Self::Name => "access_status", + Self::Whitelisted => "whitelisted", + Self::Blacklisted => "blacklisted", + } + ) + .expect("is a string") + } } diff --git a/crates/oxidetalis_migrations/src/create_whitelist_table.rs b/crates/oxidetalis_migrations/src/create_whitelist_table.rs deleted file mode 100644 index d29f837..0000000 --- a/crates/oxidetalis_migrations/src/create_whitelist_table.rs +++ /dev/null @@ -1,63 +0,0 @@ -// OxideTalis Messaging Protocol homeserver core implementation -// Copyright (c) 2024 OxideTalis Developers -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -use sea_orm_migration::prelude::*; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .create_table( - Table::create() - .table(Whitelist::Table) - .if_not_exists() - .col( - ColumnDef::new(Whitelist::Id) - .big_integer() - .not_null() - .auto_increment() - .primary_key(), - ) - .col(ColumnDef::new(Whitelist::UserId).big_integer().not_null()) - .col(ColumnDef::new(Whitelist::Target).string().not_null()) - .col( - ColumnDef::new(Whitelist::WhitelistedAt) - .timestamp_with_time_zone() - .not_null(), - ) - .to_owned(), - ) - .await - } -} - -#[derive(DeriveIden)] -enum Whitelist { - Table, - Id, - UserId, - /// Public key of the target - Target, - WhitelistedAt, -} diff --git a/crates/oxidetalis_migrations/src/lib.rs b/crates/oxidetalis_migrations/src/lib.rs index 0108570..e94142e 100644 --- a/crates/oxidetalis_migrations/src/lib.rs +++ b/crates/oxidetalis_migrations/src/lib.rs @@ -21,11 +21,10 @@ pub use sea_orm_migration::prelude::*; -mod create_blacklist_table; mod create_incoming_chat_requests_table; mod create_outgoing_chat_requests_table; +mod create_users_status; mod create_users_table; -mod create_whitelist_table; pub struct Migrator; @@ -36,8 +35,7 @@ impl MigratorTrait for Migrator { Box::new(create_users_table::Migration), Box::new(create_incoming_chat_requests_table::Migration), Box::new(create_outgoing_chat_requests_table::Migration), - Box::new(create_blacklist_table::Migration), - Box::new(create_whitelist_table::Migration), + Box::new(create_users_status::Migration), ] } } -- 2.45.2 From 7ef02d05d015e2d433cdcd3f6883e0a6bf9aff9c Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 19:15:05 +0300 Subject: [PATCH 36/55] feat: Use `TableCreateStatement::foreign_key` for foreign keys Signed-off-by: Awiteb --- .../src/create_incoming_chat_requests_table.rs | 13 +++++++++---- .../src/create_outgoing_chat_requests_table.rs | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs index 7289225..fee1dec 100644 --- a/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs +++ b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs @@ -21,6 +21,8 @@ use sea_orm_migration::prelude::*; +use crate::create_users_table::Users; + #[derive(DeriveMigrationName)] pub struct Migration; @@ -39,10 +41,13 @@ impl MigrationTrait for Migration { .auto_increment() .primary_key(), ) - .col( - ColumnDef::new(InChatRequests::RecipientId) - .big_integer() - .not_null(), + .foreign_key( + ForeignKey::create() + .name("fk-in_chat_requests-users") + .from(InChatRequests::Table, InChatRequests::RecipientId) + .to(Users::Table, Users::Id) + .on_update(ForeignKeyAction::NoAction) + .on_delete(ForeignKeyAction::Cascade), ) .col(ColumnDef::new(InChatRequests::Sender).string().not_null()) .col( diff --git a/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs index 7db4b81..6880157 100644 --- a/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs +++ b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs @@ -21,6 +21,8 @@ use sea_orm_migration::prelude::*; +use crate::create_users_table::Users; + #[derive(DeriveMigrationName)] pub struct Migration; @@ -39,10 +41,13 @@ impl MigrationTrait for Migration { .auto_increment() .primary_key(), ) - .col( - ColumnDef::new(OutChatRequests::SenderId) - .big_integer() - .not_null(), + .foreign_key( + ForeignKey::create() + .name("fk-out_chat_requests-users") + .from(OutChatRequests::Table, OutChatRequests::SenderId) + .to(Users::Table, Users::Id) + .on_update(ForeignKeyAction::NoAction) + .on_delete(ForeignKeyAction::Cascade), ) .col( ColumnDef::new(OutChatRequests::Recipient) -- 2.45.2 From 05167042bebf95524064ad8d4fac4e641ce1ee40 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 19:22:40 +0300 Subject: [PATCH 37/55] chore: Fix typos Signed-off-by: Awiteb --- crates/oxidetalis/src/database/out_chat_requests.rs | 3 ++- crates/oxidetalis/src/database/user_status.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/oxidetalis/src/database/out_chat_requests.rs b/crates/oxidetalis/src/database/out_chat_requests.rs index 0994e4b..df8840c 100644 --- a/crates/oxidetalis/src/database/out_chat_requests.rs +++ b/crates/oxidetalis/src/database/out_chat_requests.rs @@ -25,7 +25,8 @@ use crate::{errors::ServerResult, websocket::errors::WsError}; /// Extension trait for the `out_chat_requests` table. pub trait OutChatRequestsExt { - /// Returns true if the `user` have a sended chat request to the `recipient` + /// Returns the outgoing chat request if the `user` have a sent chat request + /// to the `recipient` async fn have_chat_request_to( &self, requester: &UserModel, diff --git a/crates/oxidetalis/src/database/user_status.rs b/crates/oxidetalis/src/database/user_status.rs index 152e1c8..ab123f2 100644 --- a/crates/oxidetalis/src/database/user_status.rs +++ b/crates/oxidetalis/src/database/user_status.rs @@ -28,7 +28,7 @@ use crate::{errors::ServerResult, websocket::errors::WsError}; /// Extension trait for the `DatabaseConnection` to work with the whitelist /// table pub trait UsersStatusExt { - /// Returns ture if the `whitelister` are whitelisted the + /// Returns true if the `whitelister` has whitelisted the /// `target_public_key` async fn is_whitelisted( &self, @@ -36,7 +36,7 @@ pub trait UsersStatusExt { target_public_key: &PublicKey, ) -> ServerResult; - /// Returns ture if the `blacklister` are blacklisted the + /// Returns true if the `blacklister` has blacklisted the /// `target_public_key` async fn is_blacklisted( &self, -- 2.45.2 From 48acab251920ed057ee88eb45e9a11e6eca158b7 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 19:35:37 +0300 Subject: [PATCH 38/55] chore: Use `HashMap::get` instead of `Iter::find` Signed-off-by: Awiteb --- crates/oxidetalis/src/extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxidetalis/src/extensions.rs b/crates/oxidetalis/src/extensions.rs index 558c948..fb1eab3 100644 --- a/crates/oxidetalis/src/extensions.rs +++ b/crates/oxidetalis/src/extensions.rs @@ -130,7 +130,7 @@ impl OnlineUsersExt for OnlineUsers { } async fn send(&self, conn_id: &Uuid, event: ServerEvent) { - if let Some((_, user)) = self.read().await.iter().find(|(c, _)| *c == conn_id) { + if let Some(user) = self.read().await.get(conn_id) { let _ = user .sender .unbounded_send(Ok(event.sign(&user.shared_secret).as_ref().into())); -- 2.45.2 From c13d0fc59b1422e6893998e9e5523cb3e0b3be68 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 19:38:13 +0300 Subject: [PATCH 39/55] fix: Sort `ServerEventType::ChatRequestResponse` event alphabetically Signed-off-by: Awiteb --- crates/oxidetalis/src/websocket/events/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxidetalis/src/websocket/events/server.rs b/crates/oxidetalis/src/websocket/events/server.rs index 9824931..3542e59 100644 --- a/crates/oxidetalis/src/websocket/events/server.rs +++ b/crates/oxidetalis/src/websocket/events/server.rs @@ -56,7 +56,7 @@ pub enum ServerEventType { /// New chat request from someone ChatRequest { from: PublicKey }, /// New chat request response from someone - ChatRequestResponse { from: PublicKey, accepted: bool }, + ChatRequestResponse { accepted: bool, from: PublicKey }, /// Error event Error { name: &'static str, -- 2.45.2 From c517fea6fdf736fb80a9cd73682c432b7a43360a Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 21:15:05 +0300 Subject: [PATCH 40/55] chore: Add new websocket errors Signed-off-by: Awiteb --- crates/oxidetalis/src/websocket/errors.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/oxidetalis/src/websocket/errors.rs b/crates/oxidetalis/src/websocket/errors.rs index c24798c..036801d 100644 --- a/crates/oxidetalis/src/websocket/errors.rs +++ b/crates/oxidetalis/src/websocket/errors.rs @@ -37,4 +37,6 @@ ws_errors! { CannotSendChatRequestToSelf = "You cannot send a chat request to yourself", CannotRespondToOwnChatRequest = "You cannot respond to your own chat request", NoChatRequestFromRecipient = "You do not have a chat request from the recipient", + RecipientBlacklist = "You cannot send a chat request because you are on the recipient's blacklist.", + AlreadyInRecipientWhitelist = "You are already on the recipient's whitelist and can chat with them." } -- 2.45.2 From 09f90b060f1ab56dd9387a5c8a9d3f814ade7015 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 21:16:36 +0300 Subject: [PATCH 41/55] chore: Returns `Option` of `ServerEvent` in chat request handlers Signed-off-by: Awiteb --- crates/oxidetalis/src/macros.rs | 8 ++- .../src/websocket/handlers/chat_request.rs | 57 +++++++++---------- crates/oxidetalis/src/websocket/mod.rs | 6 +- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/crates/oxidetalis/src/macros.rs b/crates/oxidetalis/src/macros.rs index 2943e59..f2d95e2 100644 --- a/crates/oxidetalis/src/macros.rs +++ b/crates/oxidetalis/src/macros.rs @@ -35,13 +35,15 @@ /// [`Err`]: std::result::Result::Err #[macro_export] macro_rules! try_ws { - ($result_expr:expr) => { + (Some $result_expr:expr) => { match $result_expr { Ok(val) => val, Err(err) => { log::error!("{err}"); - return $crate::websocket::ServerEvent::<$crate::websocket::Unsigned>::from( - $crate::websocket::errors::WsError::from(err), + return Some( + $crate::websocket::ServerEvent::<$crate::websocket::Unsigned>::from( + $crate::websocket::errors::WsError::from(err), + ), ); } } diff --git a/crates/oxidetalis/src/websocket/handlers/chat_request.rs b/crates/oxidetalis/src/websocket/handlers/chat_request.rs index c3c31e3..b41580c 100644 --- a/crates/oxidetalis/src/websocket/handlers/chat_request.rs +++ b/crates/oxidetalis/src/websocket/handlers/chat_request.rs @@ -35,45 +35,42 @@ pub async fn handle_chat_request( db: &DatabaseConnection, from: Option<&UserModel>, to_public_key: &PublicKey, -) -> ServerEvent { +) -> Option> { let Some(from_user) = from else { - return WsError::RegistredUserEvent.into(); + return Some(WsError::RegistredUserEvent.into()); }; - let Some(to_user) = try_ws!(db.get_user_by_pubk(to_public_key).await) else { - return WsError::UserNotFound.into(); + let Some(to_user) = try_ws!(Some db.get_user_by_pubk(to_public_key).await) else { + return Some(WsError::UserNotFound.into()); }; if from_user.id == to_user.id { - return WsError::CannotSendChatRequestToSelf.into(); + return Some(WsError::CannotSendChatRequestToSelf.into()); } // FIXME: When change the entity public key to a PublicKey type, change this let from_public_key = PublicKey::from_str(&from_user.public_key).expect("Is valid public key"); - if try_ws!(db.is_blacklisted(&to_user, &from_public_key).await) { - return ServerEvent::message( - "You are unable to send a chat request because you are on the recipient's blacklist.", - ); - } - if try_ws!(db.have_chat_request_to(from_user, to_public_key).await).is_some() { - return ServerEvent::message("You have already sent a chat request to this user."); + if try_ws!(Some db.have_chat_request_to(from_user, to_public_key).await).is_some() { + return Some(WsError::AlreadySendChatRequest.into()); } - try_ws!(db.add_to_whitelist(from_user, to_public_key).await); - - if try_ws!(db.is_whitelisted(&to_user, &from_public_key).await) { - return ServerEvent::message( - "You are already on the recipient's whitelist, so you can now chat with them.", - ); + if try_ws!(Some db.is_blacklisted(&to_user, &from_public_key).await) { + return Some(WsError::RecipientBlacklist.into()); } - try_ws!(db.save_out_chat_request(from_user, to_public_key).await); + try_ws!(Some db.add_to_whitelist(from_user, to_public_key).await); + + if try_ws!(Some db.is_whitelisted(&to_user, &from_public_key).await) { + return Some(WsError::AlreadyInRecipientWhitelist.into()); + } + + try_ws!(Some db.save_out_chat_request(from_user, to_public_key).await); if let Some(conn_id) = ONLINE_USERS.is_online(to_public_key).await { ONLINE_USERS .send(&conn_id, ServerEvent::chat_request(&from_public_key)) .await; } else { - try_ws!(db.save_in_chat_request(&from_public_key, &to_user).await); + try_ws!(Some db.save_in_chat_request(&from_public_key, &to_user).await); } - ServerEvent::message("Chat request sent successfully.") + None } pub async fn handle_chat_response( @@ -81,28 +78,28 @@ pub async fn handle_chat_response( recipient: Option<&UserModel>, sender_public_key: &PublicKey, accepted: bool, -) -> ServerEvent { +) -> Option> { let Some(recipient_user) = recipient else { - return WsError::RegistredUserEvent.into(); + return Some(WsError::RegistredUserEvent.into()); }; - let Some(sender_user) = try_ws!(db.get_user_by_pubk(sender_public_key).await) else { - return WsError::UserNotFound.into(); + let Some(sender_user) = try_ws!(Some db.get_user_by_pubk(sender_public_key).await) else { + return Some(WsError::UserNotFound.into()); }; if recipient_user.id == sender_user.id { - return WsError::CannotRespondToOwnChatRequest.into(); + return Some(WsError::CannotRespondToOwnChatRequest.into()); } // FIXME: When change the entity public key to a PublicKey type, change this let recipient_public_key = PublicKey::from_str(&recipient_user.public_key).expect("Is valid public key"); - if try_ws!( + if try_ws!(Some db.have_chat_request_to(&sender_user, &recipient_public_key) .await ) .is_none() { - return WsError::NoChatRequestFromRecipient.into(); + return Some(WsError::NoChatRequestFromRecipient.into()); } if let Some(conn_id) = ONLINE_USERS.is_online(sender_public_key).await { @@ -125,10 +122,10 @@ pub async fn handle_chat_response( db.add_to_blacklist(recipient_user, sender_public_key).await }; - try_ws!( + try_ws!(Some db.remove_out_chat_request(&sender_user, &recipient_public_key) .await ); - ServerEvent::message("Chat request response sent successfully.") + None } diff --git a/crates/oxidetalis/src/websocket/mod.rs b/crates/oxidetalis/src/websocket/mod.rs index 8702700..cd5d4ec 100644 --- a/crates/oxidetalis/src/websocket/mod.rs +++ b/crates/oxidetalis/src/websocket/mod.rs @@ -216,11 +216,9 @@ async fn handle_events( ONLINE_USERS.update_pong(conn_id).await; None } - ClientEventType::ChatRequest { to } => { - Some(handlers::handle_chat_request(db, user, to).await) - } + ClientEventType::ChatRequest { to } => handlers::handle_chat_request(db, user, to).await, ClientEventType::ChatRequestResponse { to, accepted } => { - Some(handlers::handle_chat_response(db, user, to, *accepted).await) + handlers::handle_chat_response(db, user, to, *accepted).await } } } -- 2.45.2 From 8f791acd5d027d7b3678b61ba64a8accea1960ab Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 21:17:54 +0300 Subject: [PATCH 42/55] remove: Remove `ServerEvent::Message` event Signed-off-by: Awiteb --- crates/oxidetalis/src/websocket/events/server.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/oxidetalis/src/websocket/events/server.rs b/crates/oxidetalis/src/websocket/events/server.rs index 3542e59..6629487 100644 --- a/crates/oxidetalis/src/websocket/events/server.rs +++ b/crates/oxidetalis/src/websocket/events/server.rs @@ -51,8 +51,6 @@ pub enum ServerEventType { Ping { timestamp: u64 }, /// Pong event Pong { timestamp: u64 }, - /// Message event, contain a message to the client - Message { msg: String }, /// New chat request from someone ChatRequest { from: PublicKey }, /// New chat request response from someone @@ -97,11 +95,6 @@ impl ServerEvent { }) } - /// Create message event - pub fn message(msg: impl Into) -> Self { - Self::new(ServerEventType::Message { msg: msg.into() }) - } - /// Create chat request event pub fn chat_request(from: &PublicKey) -> Self { Self::new(ServerEventType::ChatRequest { from: *from }) -- 2.45.2 From 92492288c0400e7cc243f9969b0ff177e80bcd3e Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 21:45:15 +0300 Subject: [PATCH 43/55] fix: Bug when requester already has recipient in whitelist Signed-off-by: Awiteb --- crates/oxidetalis/src/websocket/handlers/chat_request.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/oxidetalis/src/websocket/handlers/chat_request.rs b/crates/oxidetalis/src/websocket/handlers/chat_request.rs index b41580c..e9044c9 100644 --- a/crates/oxidetalis/src/websocket/handlers/chat_request.rs +++ b/crates/oxidetalis/src/websocket/handlers/chat_request.rs @@ -23,6 +23,7 @@ use oxidetalis_entities::prelude::*; use sea_orm::DatabaseConnection; use crate::database::InChatRequestsExt; +use crate::errors::ServerError; use crate::extensions::OnlineUsersExt; use crate::{ database::{OutChatRequestsExt, UserTableExt, UsersStatusExt}, @@ -56,7 +57,11 @@ pub async fn handle_chat_request( return Some(WsError::RecipientBlacklist.into()); } - try_ws!(Some db.add_to_whitelist(from_user, to_public_key).await); + // To ignore the error if the requester added the recipient to the whitelist + // table before send a request to them + if let Err(ServerError::Internal(_)) = db.add_to_whitelist(from_user, to_public_key).await { + return Some(WsError::InternalServerError.into()); + } if try_ws!(Some db.is_whitelisted(&to_user, &from_public_key).await) { return Some(WsError::AlreadyInRecipientWhitelist.into()); -- 2.45.2 From 6268b5fe4823b6c22840e4fac959d37574596ce6 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 21:57:47 +0300 Subject: [PATCH 44/55] refactor: Reorder code blocks for better readability Signed-off-by: Awiteb --- .../src/websocket/handlers/chat_request.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/oxidetalis/src/websocket/handlers/chat_request.rs b/crates/oxidetalis/src/websocket/handlers/chat_request.rs index e9044c9..13fca2b 100644 --- a/crates/oxidetalis/src/websocket/handlers/chat_request.rs +++ b/crates/oxidetalis/src/websocket/handlers/chat_request.rs @@ -107,18 +107,6 @@ pub async fn handle_chat_response( return Some(WsError::NoChatRequestFromRecipient.into()); } - if let Some(conn_id) = ONLINE_USERS.is_online(sender_public_key).await { - ONLINE_USERS - .send( - &conn_id, - ServerEvent::chat_request_response(recipient_public_key, accepted), - ) - .await; - } else { - // TODO: Create a table for chat request responses, and send them when - // the user logs in - } - // We don't need to handle the case where the sender is blacklisted or // whitelisted already, just add it if it is not already there let _ = if accepted { @@ -132,5 +120,17 @@ pub async fn handle_chat_response( .await ); + if let Some(conn_id) = ONLINE_USERS.is_online(sender_public_key).await { + ONLINE_USERS + .send( + &conn_id, + ServerEvent::chat_request_response(recipient_public_key, accepted), + ) + .await; + } else { + // TODO: Create a table for chat request responses, and send them when + // the user logs in + } + None } -- 2.45.2 From 02a0ac09cc2de953c6a3123626ddb7f7642172a6 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 22:27:30 +0300 Subject: [PATCH 45/55] chore: Private use of `sea_orm_migration` prelude Signed-off-by: Awiteb --- crates/oxidetalis_migrations/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/oxidetalis_migrations/src/lib.rs b/crates/oxidetalis_migrations/src/lib.rs index e94142e..846a4ef 100644 --- a/crates/oxidetalis_migrations/src/lib.rs +++ b/crates/oxidetalis_migrations/src/lib.rs @@ -19,7 +19,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -pub use sea_orm_migration::prelude::*; +use sea_orm_migration::prelude::*; +pub use sea_orm_migration::MigratorTrait; mod create_incoming_chat_requests_table; mod create_outgoing_chat_requests_table; -- 2.45.2 From 9039ee66f091fbdba6ab0f15a27dcf76f90503b8 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 22:28:36 +0300 Subject: [PATCH 46/55] feat: Create unique index for `RecipientId` and `Sender` Signed-off-by: Awiteb --- .../src/database/in_chat_requests.rs | 34 +++++++++---------- .../create_incoming_chat_requests_table.rs | 12 +++++++ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/crates/oxidetalis/src/database/in_chat_requests.rs b/crates/oxidetalis/src/database/in_chat_requests.rs index 537b3fe..0e36c74 100644 --- a/crates/oxidetalis/src/database/in_chat_requests.rs +++ b/crates/oxidetalis/src/database/in_chat_requests.rs @@ -19,7 +19,7 @@ use chrono::Utc; use oxidetalis_core::types::PublicKey; use oxidetalis_entities::prelude::*; -use sea_orm::DatabaseConnection; +use sea_orm::{sea_query::OnConflict, DatabaseConnection}; use crate::errors::ServerResult; @@ -40,22 +40,22 @@ impl InChatRequestsExt for DatabaseConnection { sender: &PublicKey, recipient: &UserModel, ) -> ServerResult<()> { - if recipient - .find_related(InChatRequestsEntity) - .filter(InChatRequestsColumn::Sender.eq(sender.to_string())) - .one(self) - .await? - .is_none() - { - InChatRequestsActiveModel { - recipient_id: Set(recipient.id), - sender: Set(sender.to_string()), - in_on: Set(Utc::now()), - ..Default::default() - } - .save(self) - .await?; - } + InChatRequestsEntity::insert(InChatRequestsActiveModel { + recipient_id: Set(recipient.id), + sender: Set(sender.to_string()), + in_on: Set(Utc::now()), + ..Default::default() + }) + .on_conflict( + OnConflict::columns([ + InChatRequestsColumn::RecipientId, + InChatRequestsColumn::Sender, + ]) + .do_nothing() + .to_owned(), + ) + .exec(self) + .await?; Ok(()) } } diff --git a/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs index fee1dec..d33a5b3 100644 --- a/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs +++ b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs @@ -29,6 +29,18 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_index( + Index::create() + .if_not_exists() + .name("sep_request") + .table(InChatRequests::Table) + .col(InChatRequests::RecipientId) + .col(InChatRequests::Sender) + .unique() + .to_owned(), + ) + .await?; manager .create_table( Table::create() -- 2.45.2 From 9c1ba977913fe64c2a3ce4916900a91cbd857bbc Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 22:59:47 +0300 Subject: [PATCH 47/55] feat: Create unique index for `SenderId` and `Recipient` Signed-off-by: Awiteb --- .../src/database/out_chat_requests.rs | 20 ++++++++++--------- .../create_outgoing_chat_requests_table.rs | 13 ++++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/oxidetalis/src/database/out_chat_requests.rs b/crates/oxidetalis/src/database/out_chat_requests.rs index df8840c..f0cb280 100644 --- a/crates/oxidetalis/src/database/out_chat_requests.rs +++ b/crates/oxidetalis/src/database/out_chat_requests.rs @@ -69,21 +69,23 @@ impl OutChatRequestsExt for DatabaseConnection { requester: &UserModel, recipient: &PublicKey, ) -> ServerResult<()> { - if self - .have_chat_request_to(requester, recipient) - .await? - .is_some() - { - return Err(WsError::AlreadySendChatRequest.into()); - } - OutChatRequestsActiveModel { + if let Err(err) = (OutChatRequestsActiveModel { sender_id: Set(requester.id), recipient: Set(recipient.to_string()), out_on: Set(Utc::now()), ..Default::default() } .save(self) - .await?; + .await) + { + match err.sql_err() { + Some(SqlErr::UniqueConstraintViolation(_)) => { + return Err(WsError::AlreadySendChatRequest.into()); + } + _ => return Err(err.into()), + } + } + Ok(()) } diff --git a/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs index 6880157..d033bc3 100644 --- a/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs +++ b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs @@ -29,6 +29,19 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_index( + Index::create() + .if_not_exists() + .name("sep_request") + .table(OutChatRequests::Table) + .col(OutChatRequests::SenderId) + .col(OutChatRequests::Recipient) + .unique() + .to_owned(), + ) + .await?; + manager .create_table( Table::create() -- 2.45.2 From 868ec66b6826ae15aa46d7d7a97c0a7d30c18f27 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Wed, 17 Jul 2024 23:22:24 +0300 Subject: [PATCH 48/55] feat: Create unique index for `UserId` and `Target` Signed-off-by: Awiteb --- crates/oxidetalis/src/database/user_status.rs | 83 ++++++++++++------- .../src/create_users_status.rs | 12 +++ 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/crates/oxidetalis/src/database/user_status.rs b/crates/oxidetalis/src/database/user_status.rs index ab123f2..24d96ae 100644 --- a/crates/oxidetalis/src/database/user_status.rs +++ b/crates/oxidetalis/src/database/user_status.rs @@ -139,30 +139,38 @@ impl UsersStatusExt for DatabaseConnection { whitelister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult<()> { - if self.is_whitelisted(whitelister, target_public_key).await? { - return Err(WsError::AlreadyOnTheWhitelist.into()); - } if whitelister.public_key == target_public_key.to_string() { return Err(WsError::CannotAddSelfToWhitelist.into()); } - if let Some(mut user) = get_user_status(self, whitelister, target_public_key) - .await? - .map(IntoActiveModel::into_active_model) + if let Some(mut user) = get_user_status( + self, + whitelister, + target_public_key, + AccessStatus::Blacklisted, + ) + .await? + .map(IntoActiveModel::into_active_model) { user.status = Set(AccessStatus::Whitelisted); user.updated_at = Set(Utc::now()); user.update(self).await?; - } else { - UsersStatusActiveModel { - user_id: Set(whitelister.id), - target: Set(target_public_key.to_string()), - status: Set(AccessStatus::Whitelisted), - updated_at: Set(Utc::now()), - ..Default::default() + } else if let Err(err) = (UsersStatusActiveModel { + user_id: Set(whitelister.id), + target: Set(target_public_key.to_string()), + status: Set(AccessStatus::Whitelisted), + updated_at: Set(Utc::now()), + ..Default::default() + } + .save(self) + .await) + { + match err.sql_err() { + Some(SqlErr::UniqueConstraintViolation(_)) => { + return Err(WsError::AlreadyOnTheWhitelist.into()); + } + _ => return Err(err.into()), } - .save(self) - .await?; } Ok(()) @@ -173,30 +181,38 @@ impl UsersStatusExt for DatabaseConnection { blacklister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult<()> { - if self.is_blacklisted(blacklister, target_public_key).await? { - return Err(WsError::AlreadyOnTheBlacklist.into()); - } if blacklister.public_key == target_public_key.to_string() { return Err(WsError::CannotAddSelfToBlacklist.into()); } - if let Some(mut user) = get_user_status(self, blacklister, target_public_key) - .await? - .map(IntoActiveModel::into_active_model) + if let Some(mut user) = get_user_status( + self, + blacklister, + target_public_key, + AccessStatus::Whitelisted, + ) + .await? + .map(IntoActiveModel::into_active_model) { user.status = Set(AccessStatus::Blacklisted); user.updated_at = Set(Utc::now()); user.update(self).await?; - } else { - UsersStatusActiveModel { - user_id: Set(blacklister.id), - target: Set(target_public_key.to_string()), - status: Set(AccessStatus::Blacklisted), - updated_at: Set(Utc::now()), - ..Default::default() + } else if let Err(err) = (UsersStatusActiveModel { + user_id: Set(blacklister.id), + target: Set(target_public_key.to_string()), + status: Set(AccessStatus::Blacklisted), + updated_at: Set(Utc::now()), + ..Default::default() + } + .save(self) + .await) + { + match err.sql_err() { + Some(SqlErr::UniqueConstraintViolation(_)) => { + return Err(WsError::AlreadyOnTheBlacklist.into()); + } + _ => return Err(err.into()), } - .save(self) - .await?; } Ok(()) @@ -278,9 +294,14 @@ async fn get_user_status( conn: &DatabaseConnection, user: &UserModel, target_public_key: &PublicKey, + status: AccessStatus, ) -> ServerResult> { user.find_related(UsersStatusEntity) - .filter(UsersStatusColumn::Target.eq(target_public_key.to_string())) + .filter( + UsersStatusColumn::Target + .eq(target_public_key.to_string()) + .and(UsersStatusColumn::Status.eq(status)), + ) .one(conn) .await .map_err(Into::into) diff --git a/crates/oxidetalis_migrations/src/create_users_status.rs b/crates/oxidetalis_migrations/src/create_users_status.rs index ad57d00..a0ad213 100644 --- a/crates/oxidetalis_migrations/src/create_users_status.rs +++ b/crates/oxidetalis_migrations/src/create_users_status.rs @@ -40,6 +40,18 @@ impl MigrationTrait for Migration { .to_owned(), ) .await?; + manager + .create_index( + Index::create() + .if_not_exists() + .name("sep_status") + .table(UsersStatus::Table) + .col(UsersStatus::UserId) + .col(UsersStatus::Target) + .unique() + .to_owned(), + ) + .await?; manager .create_table( -- 2.45.2 From d0d048c135907dad221023f89c6081e28401a8fb Mon Sep 17 00:00:00 2001 From: Awiteb Date: Thu, 18 Jul 2024 10:57:52 +0300 Subject: [PATCH 49/55] feat: Impl `EndpointArgRegister` for `Pagination` Signed-off-by: Awiteb --- crates/oxidetalis/src/parameters/pagination.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/oxidetalis/src/parameters/pagination.rs b/crates/oxidetalis/src/parameters/pagination.rs index c31a912..5dab644 100644 --- a/crates/oxidetalis/src/parameters/pagination.rs +++ b/crates/oxidetalis/src/parameters/pagination.rs @@ -26,7 +26,9 @@ use salvo::{ extract::Metadata as ExtractMetadata, oapi::{ Components as OapiComponents, + EndpointArgRegister, Object, + Operation as OapiOperation, Parameter, ParameterIn, Parameters, @@ -86,6 +88,14 @@ impl ToParameters<'_> for Pagination { } } +impl EndpointArgRegister for Pagination { + fn register(components: &mut OapiComponents, operation: &mut OapiOperation, _arg: &str) { + for parameter in Self::to_parameters(components) { + operation.parameters.insert(parameter); + } + } +} + /// Extract a query parameter from the request fn extract_query(req: &Request, name: &str, default_value: T) -> ApiResult where -- 2.45.2 From ccc0c2954bdc6111c5cf3f26cbbcd13c6f986a9c Mon Sep 17 00:00:00 2001 From: Awiteb Date: Thu, 18 Jul 2024 10:59:29 +0300 Subject: [PATCH 50/55] refactor: Use `pagination` directly as an argument Signed-off-by: Awiteb --- crates/oxidetalis/src/routes/user.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/oxidetalis/src/routes/user.rs b/crates/oxidetalis/src/routes/user.rs index 7a0f423..9f3b777 100644 --- a/crates/oxidetalis/src/routes/user.rs +++ b/crates/oxidetalis/src/routes/user.rs @@ -22,7 +22,6 @@ use salvo::{ oapi::{endpoint, extract::JsonBody}, writing::Json, Depot, - Extractible, Request, Router, Writer, @@ -88,13 +87,13 @@ pub async fn register( tags("User"), responses( (status_code = 200, description = "Returns whitelisted users", content_type = "application/json", body = Vec), - (status_code = 403, description = "Not registered user, must register first", content_type = "application/json", body = MessageSchema), + (status_code = 400, description = "Wrong query parameter", content_type = "application/json", body = MessageSchema), (status_code = 401, description = "The entered signature or public key is invalid", content_type = "application/json", body = MessageSchema), + (status_code = 403, description = "Not registered user, must register first", content_type = "application/json", body = MessageSchema), (status_code = 429, description = "Too many requests", content_type = "application/json", body = MessageSchema), (status_code = 500, description = "Internal server error", content_type = "application/json", body = MessageSchema), ), parameters( - Pagination, ("X-OTMP-PUBLIC" = PublicKey, Header, description = "Public key of the sender"), ("X-OTMP-SIGNATURE" = Signature, Header, description = "Signature of the request"), ), @@ -102,8 +101,8 @@ pub async fn register( async fn user_whitelist( req: &mut Request, depot: &mut Depot, + pagination: Pagination, ) -> ApiResult>> { - let pagination = Pagination::extract(req).await?; let conn = depot.db_conn(); let user = conn .get_user_by_pubk( @@ -127,13 +126,13 @@ async fn user_whitelist( tags("User"), responses( (status_code = 200, description = "Returns blacklisted users", content_type = "application/json", body = Vec), - (status_code = 403, description = "Not registered user, must register first", content_type = "application/json", body = MessageSchema), + (status_code = 400, description = "Wrong query parameter", content_type = "application/json", body = MessageSchema), (status_code = 401, description = "The entered signature or public key is invalid", content_type = "application/json", body = MessageSchema), + (status_code = 403, description = "Not registered user, must register first", content_type = "application/json", body = MessageSchema), (status_code = 429, description = "Too many requests", content_type = "application/json", body = MessageSchema), (status_code = 500, description = "Internal server error", content_type = "application/json", body = MessageSchema), ), parameters( - Pagination, ("X-OTMP-PUBLIC" = PublicKey, Header, description = "Public key of the sender"), ("X-OTMP-SIGNATURE" = Signature, Header, description = "Signature of the request"), ), @@ -141,8 +140,8 @@ async fn user_whitelist( async fn user_blacklist( req: &mut Request, depot: &mut Depot, + pagination: Pagination, ) -> ApiResult>> { - let pagination = Pagination::extract(req).await?; let conn = depot.db_conn(); let user = conn .get_user_by_pubk( -- 2.45.2 From 530f3bc90bb45b9a87f725a9caae1ff9eacb198c Mon Sep 17 00:00:00 2001 From: Awiteb Date: Thu, 18 Jul 2024 11:01:30 +0300 Subject: [PATCH 51/55] fix: Create the foreign keys columns Signed-off-by: Awiteb --- .../src/create_incoming_chat_requests_table.rs | 5 +++++ .../src/create_outgoing_chat_requests_table.rs | 5 +++++ crates/oxidetalis_migrations/src/create_users_status.rs | 1 + 3 files changed, 11 insertions(+) diff --git a/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs index d33a5b3..a2bab90 100644 --- a/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs +++ b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs @@ -53,6 +53,11 @@ impl MigrationTrait for Migration { .auto_increment() .primary_key(), ) + .col( + ColumnDef::new(InChatRequests::RecipientId) + .big_integer() + .not_null(), + ) .foreign_key( ForeignKey::create() .name("fk-in_chat_requests-users") diff --git a/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs index d033bc3..8ef6161 100644 --- a/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs +++ b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs @@ -54,6 +54,11 @@ impl MigrationTrait for Migration { .auto_increment() .primary_key(), ) + .col( + ColumnDef::new(OutChatRequests::SenderId) + .big_integer() + .not_null(), + ) .foreign_key( ForeignKey::create() .name("fk-out_chat_requests-users") diff --git a/crates/oxidetalis_migrations/src/create_users_status.rs b/crates/oxidetalis_migrations/src/create_users_status.rs index a0ad213..dd7aa59 100644 --- a/crates/oxidetalis_migrations/src/create_users_status.rs +++ b/crates/oxidetalis_migrations/src/create_users_status.rs @@ -65,6 +65,7 @@ impl MigrationTrait for Migration { .auto_increment() .primary_key(), ) + .col(ColumnDef::new(UsersStatus::UserId).big_integer().not_null()) .foreign_key( ForeignKey::create() .name("fk-users_status-users") -- 2.45.2 From 4e50cb34c0932f324c1e4e702d19f7a9e88a8f75 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Thu, 18 Jul 2024 11:07:50 +0300 Subject: [PATCH 52/55] fix: Create the index after the table Signed-off-by: Awiteb --- .../create_incoming_chat_requests_table.rs | 24 ++++++++--------- .../create_outgoing_chat_requests_table.rs | 26 +++++++++---------- .../src/create_users_status.rs | 25 +++++++++--------- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs index a2bab90..51113e0 100644 --- a/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs +++ b/crates/oxidetalis_migrations/src/create_incoming_chat_requests_table.rs @@ -29,18 +29,6 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .create_index( - Index::create() - .if_not_exists() - .name("sep_request") - .table(InChatRequests::Table) - .col(InChatRequests::RecipientId) - .col(InChatRequests::Sender) - .unique() - .to_owned(), - ) - .await?; manager .create_table( Table::create() @@ -74,6 +62,18 @@ impl MigrationTrait for Migration { ) .to_owned(), ) + .await?; + manager + .create_index( + Index::create() + .if_not_exists() + .name("sep_request") + .table(InChatRequests::Table) + .col(InChatRequests::RecipientId) + .col(InChatRequests::Sender) + .unique() + .to_owned(), + ) .await } } diff --git a/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs index 8ef6161..4b03e99 100644 --- a/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs +++ b/crates/oxidetalis_migrations/src/create_outgoing_chat_requests_table.rs @@ -29,19 +29,6 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .create_index( - Index::create() - .if_not_exists() - .name("sep_request") - .table(OutChatRequests::Table) - .col(OutChatRequests::SenderId) - .col(OutChatRequests::Recipient) - .unique() - .to_owned(), - ) - .await?; - manager .create_table( Table::create() @@ -79,6 +66,19 @@ impl MigrationTrait for Migration { ) .to_owned(), ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("sep_request") + .table(OutChatRequests::Table) + .col(OutChatRequests::SenderId) + .col(OutChatRequests::Recipient) + .unique() + .to_owned(), + ) .await } } diff --git a/crates/oxidetalis_migrations/src/create_users_status.rs b/crates/oxidetalis_migrations/src/create_users_status.rs index dd7aa59..6f3b571 100644 --- a/crates/oxidetalis_migrations/src/create_users_status.rs +++ b/crates/oxidetalis_migrations/src/create_users_status.rs @@ -40,18 +40,6 @@ impl MigrationTrait for Migration { .to_owned(), ) .await?; - manager - .create_index( - Index::create() - .if_not_exists() - .name("sep_status") - .table(UsersStatus::Table) - .col(UsersStatus::UserId) - .col(UsersStatus::Target) - .unique() - .to_owned(), - ) - .await?; manager .create_table( @@ -90,6 +78,19 @@ impl MigrationTrait for Migration { ) .to_owned(), ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("sep_status") + .table(UsersStatus::Table) + .col(UsersStatus::UserId) + .col(UsersStatus::Target) + .unique() + .to_owned(), + ) .await } } -- 2.45.2 From 21880c5f825b5e79aa054996313d8a36b275299f Mon Sep 17 00:00:00 2001 From: Awiteb Date: Thu, 18 Jul 2024 11:14:49 +0300 Subject: [PATCH 53/55] refactor: Use `get_user_status` util in more places Signed-off-by: Awiteb --- crates/oxidetalis/src/database/user_status.rs | 72 +++++++++---------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/crates/oxidetalis/src/database/user_status.rs b/crates/oxidetalis/src/database/user_status.rs index 24d96ae..47d9303 100644 --- a/crates/oxidetalis/src/database/user_status.rs +++ b/crates/oxidetalis/src/database/user_status.rs @@ -103,17 +103,15 @@ impl UsersStatusExt for DatabaseConnection { whitelister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult { - whitelister - .find_related(UsersStatusEntity) - .filter( - UsersStatusColumn::Target - .eq(target_public_key.to_string()) - .and(UsersStatusColumn::Status.eq(AccessStatus::Whitelisted)), - ) - .one(self) - .await - .map(|u| u.is_some()) - .map_err(Into::into) + get_user_status( + self, + whitelister, + target_public_key, + AccessStatus::Whitelisted, + ) + .await + .map(|u| u.is_some()) + .map_err(Into::into) } async fn is_blacklisted( @@ -121,17 +119,15 @@ impl UsersStatusExt for DatabaseConnection { blacklister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult { - blacklister - .find_related(UsersStatusEntity) - .filter( - UsersStatusColumn::Target - .eq(target_public_key.to_string()) - .and(UsersStatusColumn::Status.eq(AccessStatus::Blacklisted)), - ) - .one(self) - .await - .map(|u| u.is_some()) - .map_err(Into::into) + get_user_status( + self, + blacklister, + target_public_key, + AccessStatus::Blacklisted, + ) + .await + .map(|u| u.is_some()) + .map_err(Into::into) } async fn add_to_whitelist( @@ -223,15 +219,13 @@ impl UsersStatusExt for DatabaseConnection { whitelister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult<()> { - if let Some(target_user) = whitelister - .find_related(UsersStatusEntity) - .filter( - UsersStatusColumn::Target - .eq(target_public_key.to_string()) - .and(UsersStatusColumn::Status.eq(AccessStatus::Whitelisted)), - ) - .one(self) - .await? + if let Some(target_user) = get_user_status( + self, + whitelister, + target_public_key, + AccessStatus::Whitelisted, + ) + .await? { target_user.delete(self).await?; } @@ -243,15 +237,13 @@ impl UsersStatusExt for DatabaseConnection { blacklister: &UserModel, target_public_key: &PublicKey, ) -> ServerResult<()> { - if let Some(target_user) = blacklister - .find_related(UsersStatusEntity) - .filter( - UsersStatusColumn::Target - .eq(target_public_key.to_string()) - .and(UsersStatusColumn::Status.eq(AccessStatus::Blacklisted)), - ) - .one(self) - .await? + if let Some(target_user) = get_user_status( + self, + blacklister, + target_public_key, + AccessStatus::Blacklisted, + ) + .await? { target_user.delete(self).await?; } -- 2.45.2 From 39e78619176fd7a2ebd466b4f058159c01603967 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Thu, 18 Jul 2024 11:22:56 +0300 Subject: [PATCH 54/55] change: Rename `OutChatRequestsExt::have_chat_request_to` function to `get_chat_request_to` Signed-off-by: Awiteb --- crates/oxidetalis/src/database/out_chat_requests.rs | 6 +++--- crates/oxidetalis/src/websocket/handlers/chat_request.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/oxidetalis/src/database/out_chat_requests.rs b/crates/oxidetalis/src/database/out_chat_requests.rs index f0cb280..c3a3ea1 100644 --- a/crates/oxidetalis/src/database/out_chat_requests.rs +++ b/crates/oxidetalis/src/database/out_chat_requests.rs @@ -27,7 +27,7 @@ use crate::{errors::ServerResult, websocket::errors::WsError}; pub trait OutChatRequestsExt { /// Returns the outgoing chat request if the `user` have a sent chat request /// to the `recipient` - async fn have_chat_request_to( + async fn get_chat_request_to( &self, requester: &UserModel, recipient: &PublicKey, @@ -50,7 +50,7 @@ pub trait OutChatRequestsExt { impl OutChatRequestsExt for DatabaseConnection { #[logcall::logcall] - async fn have_chat_request_to( + async fn get_chat_request_to( &self, requester: &UserModel, recipient: &PublicKey, @@ -94,7 +94,7 @@ impl OutChatRequestsExt for DatabaseConnection { requester: &UserModel, recipient: &PublicKey, ) -> ServerResult<()> { - if let Some(out_model) = self.have_chat_request_to(requester, recipient).await? { + if let Some(out_model) = self.get_chat_request_to(requester, recipient).await? { out_model.delete(self).await?; } Ok(()) diff --git a/crates/oxidetalis/src/websocket/handlers/chat_request.rs b/crates/oxidetalis/src/websocket/handlers/chat_request.rs index 13fca2b..6d0aa2a 100644 --- a/crates/oxidetalis/src/websocket/handlers/chat_request.rs +++ b/crates/oxidetalis/src/websocket/handlers/chat_request.rs @@ -49,7 +49,7 @@ pub async fn handle_chat_request( // FIXME: When change the entity public key to a PublicKey type, change this let from_public_key = PublicKey::from_str(&from_user.public_key).expect("Is valid public key"); - if try_ws!(Some db.have_chat_request_to(from_user, to_public_key).await).is_some() { + if try_ws!(Some db.get_chat_request_to(from_user, to_public_key).await).is_some() { return Some(WsError::AlreadySendChatRequest.into()); } @@ -99,7 +99,7 @@ pub async fn handle_chat_response( PublicKey::from_str(&recipient_user.public_key).expect("Is valid public key"); if try_ws!(Some - db.have_chat_request_to(&sender_user, &recipient_public_key) + db.get_chat_request_to(&sender_user, &recipient_public_key) .await ) .is_none() -- 2.45.2 From 6f0b88afb1d6f03b1174fe06a3ef5818f83aea61 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Thu, 18 Jul 2024 12:28:45 +0300 Subject: [PATCH 55/55] chore: More logging Signed-off-by: Awiteb --- crates/oxidetalis/src/database/user_status.rs | 6 ++++++ crates/oxidetalis/src/websocket/events/server.rs | 1 + crates/oxidetalis/src/websocket/handlers/chat_request.rs | 2 ++ 3 files changed, 9 insertions(+) diff --git a/crates/oxidetalis/src/database/user_status.rs b/crates/oxidetalis/src/database/user_status.rs index 47d9303..1997332 100644 --- a/crates/oxidetalis/src/database/user_status.rs +++ b/crates/oxidetalis/src/database/user_status.rs @@ -98,6 +98,7 @@ pub trait UsersStatusExt { } impl UsersStatusExt for DatabaseConnection { + #[logcall::logcall] async fn is_whitelisted( &self, whitelister: &UserModel, @@ -114,6 +115,7 @@ impl UsersStatusExt for DatabaseConnection { .map_err(Into::into) } + #[logcall::logcall] async fn is_blacklisted( &self, blacklister: &UserModel, @@ -130,6 +132,7 @@ impl UsersStatusExt for DatabaseConnection { .map_err(Into::into) } + #[logcall::logcall] async fn add_to_whitelist( &self, whitelister: &UserModel, @@ -172,6 +175,7 @@ impl UsersStatusExt for DatabaseConnection { Ok(()) } + #[logcall::logcall] async fn add_to_blacklist( &self, blacklister: &UserModel, @@ -214,6 +218,7 @@ impl UsersStatusExt for DatabaseConnection { Ok(()) } + #[logcall::logcall] async fn remove_from_whitelist( &self, whitelister: &UserModel, @@ -232,6 +237,7 @@ impl UsersStatusExt for DatabaseConnection { Ok(()) } + #[logcall::logcall] async fn remove_from_blacklist( &self, blacklister: &UserModel, diff --git a/crates/oxidetalis/src/websocket/events/server.rs b/crates/oxidetalis/src/websocket/events/server.rs index 6629487..6295891 100644 --- a/crates/oxidetalis/src/websocket/events/server.rs +++ b/crates/oxidetalis/src/websocket/events/server.rs @@ -31,6 +31,7 @@ use crate::websocket::errors::WsError; /// Signed marker, used to indicate that the event is signed pub struct Signed; /// Unsigned marker, used to indicate that the event is unsigned +#[derive(Debug)] pub struct Unsigned; /// Server websocket event diff --git a/crates/oxidetalis/src/websocket/handlers/chat_request.rs b/crates/oxidetalis/src/websocket/handlers/chat_request.rs index 6d0aa2a..bb8639c 100644 --- a/crates/oxidetalis/src/websocket/handlers/chat_request.rs +++ b/crates/oxidetalis/src/websocket/handlers/chat_request.rs @@ -32,6 +32,7 @@ use crate::{ }; /// Handle a chat request from a user. +#[logcall::logcall] pub async fn handle_chat_request( db: &DatabaseConnection, from: Option<&UserModel>, @@ -78,6 +79,7 @@ pub async fn handle_chat_request( None } +#[logcall::logcall] pub async fn handle_chat_response( db: &DatabaseConnection, recipient: Option<&UserModel>, -- 2.45.2