From b54b60c98830e1a5a7de730616d7a1267773acbe Mon Sep 17 00:00:00 2001 From: Nell Date: Sat, 25 Jul 2026 01:17:19 +0200 Subject: [PATCH] init --- .../src/m20220101_000001_create_table.rs | 94 ++++++++++++-- src/models/category.rs | 3 +- src/models/channel.rs | 3 +- src/models/mod.rs | 1 + src/models/prelude.rs | 1 + src/models/server_item_order.rs | 74 +++++++++++ src/repositories/server.rs | 116 +++++++++--------- src/repositories/types.rs | 16 +-- src/routes/category/dto.rs | 4 - src/routes/category/mapper.rs | 3 - src/routes/channel/dto.rs | 4 - src/routes/channel/mapper.rs | 3 - 12 files changed, 224 insertions(+), 98 deletions(-) create mode 100644 src/models/server_item_order.rs diff --git a/migration/src/m20220101_000001_create_table.rs b/migration/src/m20220101_000001_create_table.rs index f905dfa..6b216c4 100644 --- a/migration/src/m20220101_000001_create_table.rs +++ b/migration/src/m20220101_000001_create_table.rs @@ -243,12 +243,6 @@ impl MigrationTrait for Migration { ) .col(ColumnDef::new(Alias::new("server_id")).uuid().not_null()) .col(ColumnDef::new(Alias::new("name")).string().not_null()) - .col( - ColumnDef::new(Alias::new("position")) - .integer() - .not_null() - .default(0), - ) .col( ColumnDef::new(Alias::new("created_at")) .timestamp_with_time_zone() @@ -285,12 +279,6 @@ impl MigrationTrait for Migration { ) .col(ColumnDef::new(Alias::new("server_id")).uuid().null()) .col(ColumnDef::new(Alias::new("category_id")).uuid().null()) - .col( - ColumnDef::new(Alias::new("position")) - .integer() - .not_null() - .default(0), - ) .col( ColumnDef::new(Alias::new("channel_type")) .integer() @@ -327,6 +315,86 @@ impl MigrationTrait for Migration { ) .await?; + // --------------------------------------------------------------------- + // Ordre des catégories et des canaux (Polymorphique) + // --------------------------------------------------------------------- + + manager + .create_table( + Table::create() + .table(Alias::new("server_item_order")) + .if_not_exists() + .col( + ColumnDef::new(Alias::new("id")) + .uuid() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Alias::new("server_id")).uuid().not_null()) + .col(ColumnDef::new(Alias::new("resource_id")).uuid().not_null()) + .col( + ColumnDef::new(Alias::new("resource_type")) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(Alias::new("parent_category_id")) + .uuid() + .null(), + ) + .col( + ColumnDef::new(Alias::new("order_key")) + .big_integer() + .not_null(), + ) + .col( + ColumnDef::new(Alias::new("created_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(Alias::new("updated_at")) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .foreign_key( + ForeignKey::create() + .name("fk_server_item_order_server") + .from(Alias::new("server_item_order"), Alias::new("server_id")) + .to(Alias::new("server"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .name("fk_server_item_order_parent_category") + .from( + Alias::new("server_item_order"), + Alias::new("parent_category_id"), + ) + .to(Alias::new("category"), Alias::new("id")) + .on_delete(ForeignKeyAction::Cascade), + ) + .index( + Index::create() + .name("uq_server_item_order_resource") + .col(Alias::new("server_id")) + .col(Alias::new("resource_type")) + .col(Alias::new("resource_id")) + .unique(), + ) + .index( + Index::create() + .name("idx_server_item_order_scope") + .col(Alias::new("server_id")) + .col(Alias::new("parent_category_id")) + .col(Alias::new("order_key")), + ) + .to_owned(), + ) + .await?; + // --------------------------------------------------------------------- // Membres des canaux // --------------------------------------------------------------------- @@ -716,7 +784,6 @@ impl MigrationTrait for Migration { } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - // Les tables dépendantes doivent être supprimées avant leurs parents. let tables = [ "computed_permission", "channel_user_permission", @@ -726,6 +793,7 @@ impl MigrationTrait for Migration { "attachment", "message", "channel_user", + "server_item_order", "channel", "category", "role_user", diff --git a/src/models/category.rs b/src/models/category.rs index d40ed7b..f47e088 100644 --- a/src/models/category.rs +++ b/src/models/category.rs @@ -1,8 +1,8 @@ //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 +use sea_orm::Set; use sea_orm::entity::prelude::*; use sea_orm::prelude::async_trait::async_trait; -use sea_orm::Set; #[sea_orm::model] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] @@ -12,7 +12,6 @@ pub struct Model { pub id: Uuid, pub server_id: Uuid, pub name: String, - pub position: i32, pub created_at: DateTimeUtc, pub updated_at: DateTimeUtc, #[sea_orm(has_many)] diff --git a/src/models/channel.rs b/src/models/channel.rs index 04f6bae..ef80206 100644 --- a/src/models/channel.rs +++ b/src/models/channel.rs @@ -1,8 +1,8 @@ //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19 +use sea_orm::Set; use sea_orm::entity::prelude::*; use sea_orm::prelude::async_trait::async_trait; -use sea_orm::Set; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; @@ -28,7 +28,6 @@ pub struct Model { pub id: Uuid, pub server_id: Option, pub category_id: Option, - pub position: i32, pub channel_type: ChannelType, pub name: Option, pub created_at: DateTimeUtc, diff --git a/src/models/mod.rs b/src/models/mod.rs index ba05fa8..c8815a8 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -13,6 +13,7 @@ pub mod message; pub mod role; pub mod role_user; pub mod server; +pub mod server_item_order; pub mod server_role_permission; pub mod server_user; pub mod server_user_permission; diff --git a/src/models/prelude.rs b/src/models/prelude.rs index c997495..1bd2091 100644 --- a/src/models/prelude.rs +++ b/src/models/prelude.rs @@ -9,6 +9,7 @@ pub use super::message::Entity as Message; pub use super::role::Entity as Group; pub use super::role_user::Entity as GroupMember; pub use super::server::Entity as Server; +pub use super::server_item_order::Entity as ServerItemOrder; pub use super::server_role_permission::Entity as ServerRolePermission; pub use super::server_user::Entity as ServerUser; pub use super::server_user_permission::Entity as ServerUserPermission; diff --git a/src/models/server_item_order.rs b/src/models/server_item_order.rs new file mode 100644 index 0000000..947c4c0 --- /dev/null +++ b/src/models/server_item_order.rs @@ -0,0 +1,74 @@ +use sea_orm::Set; +use sea_orm::entity::prelude::*; +use sea_orm::prelude::async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; +use uuid::Uuid; + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize, ToSchema, +)] +#[sea_orm(rs_type = "i32", db_type = "Integer")] +#[serde(rename_all = "snake_case")] +pub enum OrderedResourceType { + #[sea_orm(num_value = 0)] + Channel, + + #[sea_orm(num_value = 1)] + Category, +} + +#[sea_orm::model] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "server_item_order")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + + pub server_id: Uuid, + + /// UUID du channel ou de la catégorie ordonné(e). + pub resource_id: Uuid, + + /// Type de la ressource référencée par resource_id. + pub resource_type: OrderedResourceType, + + /// NULL pour la liste racine du serveur. + /// Renseigné uniquement pour un channel placé dans une catégorie. + pub parent_category_id: Option, + + /// Clé de tri relative à (server_id, parent_category_id). + pub order_key: i64, + + pub created_at: DateTimeUtc, + + pub updated_at: DateTimeUtc, + + #[sea_orm( + belongs_to, + from = "server_id", + to = "id", + on_update = "Cascade", + on_delete = "Cascade" + )] + pub server: HasOne, + + #[sea_orm( + belongs_to, + from = "parent_category_id", + to = "id", + on_update = "Cascade", + on_delete = "Cascade" + )] + pub parent_category: HasOne, +} + +#[async_trait] +impl ActiveModelBehavior for ActiveModel { + fn new() -> Self { + Self { + id: Set(Uuid::new_v4()), + ..ActiveModelTrait::default() + } + } +} diff --git a/src/repositories/server.rs b/src/repositories/server.rs index bdd941f..2963ad4 100644 --- a/src/repositories/server.rs +++ b/src/repositories/server.rs @@ -1,7 +1,5 @@ -use super::types::{ServerExplorerItem, ServerTree}; use super::{AnyResult, RepositoryContext}; -use crate::models::{ - category, channel, role, server, server_role_permission, server_user, server_user_permission, +use crate::models::{role, server, server_role_permission, server_user, server_user_permission, }; use sea_orm::prelude::*; use sea_orm::{ActiveModelTrait, QuerySelect, Set}; @@ -204,59 +202,59 @@ impl ServerRepository { } // Helpers -impl ServerRepository { - pub async fn get_tree(&self, server_id: Uuid) -> AnyResult { - // 1. Récupération des catégories avec leurs channels - let categories_with_channels = category::Entity::find() - .filter(category::Column::ServerId.eq(server_id)) - .find_with_related(channel::Entity) - .all(&self.context.db) - .await?; - - // 2. Récupération des channels orphelins (sans catégorie) - let orphan_channels = channel::Entity::find() - .filter(channel::Column::ServerId.eq(server_id)) - .filter(channel::Column::CategoryId.is_null()) - .all(&self.context.db) - .await?; - - // 3. Transformation et tri des enfants - let mut items: Vec = Vec::new(); - - for (cat, mut channels) in categories_with_channels { - // On trie les channels internes (obligatoire car SQL ne garantit aucun ordre ici) - channels.sort_by(|a, b| { - a.position - .cmp(&b.position) - .then(a.created_at.cmp(&b.created_at)) - }); - items.push(ServerExplorerItem::Category(cat, channels)); - } - - for chan in orphan_channels { - items.push(ServerExplorerItem::Channel(chan)); - } - - // 4. Tri final de la liste globale (Mélange catégories et orphelins) - items.sort_by(|a, b| { - let pos_cmp = a.position().cmp(&b.position()); - - if pos_cmp == std::cmp::Ordering::Equal { - // Départage par date si position identique - let date_a = match a { - ServerExplorerItem::Category(c, _) => c.created_at, - ServerExplorerItem::Channel(c) => c.created_at, - }; - let date_b = match b { - ServerExplorerItem::Category(c, _) => c.created_at, - ServerExplorerItem::Channel(c) => c.created_at, - }; - date_a.cmp(&date_b) - } else { - pos_cmp - } - }); - - Ok(ServerTree { items }) - } -} +// impl ServerRepository { +// pub async fn get_tree(&self, server_id: Uuid) -> AnyResult { +// // 1. Récupération des catégories avec leurs channels +// let categories_with_channels = category::Entity::find() +// .filter(category::Column::ServerId.eq(server_id)) +// .find_with_related(channel::Entity) +// .all(&self.context.db) +// .await?; +// +// // 2. Récupération des channels orphelins (sans catégorie) +// let orphan_channels = channel::Entity::find() +// .filter(channel::Column::ServerId.eq(server_id)) +// .filter(channel::Column::CategoryId.is_null()) +// .all(&self.context.db) +// .await?; +// +// // 3. Transformation et tri des enfants +// let mut items: Vec = Vec::new(); +// +// for (cat, mut channels) in categories_with_channels { +// // On trie les channels internes (obligatoire car SQL ne garantit aucun ordre ici) +// channels.sort_by(|a, b| { +// a.position +// .cmp(&b.position) +// .then(a.created_at.cmp(&b.created_at)) +// }); +// items.push(ServerExplorerItem::Category(cat, channels)); +// } +// +// for chan in orphan_channels { +// items.push(ServerExplorerItem::Channel(chan)); +// } +// +// // 4. Tri final de la liste globale (Mélange catégories et orphelins) +// items.sort_by(|a, b| { +// let pos_cmp = a.position().cmp(&b.position()); +// +// if pos_cmp == std::cmp::Ordering::Equal { +// // Départage par date si position identique +// let date_a = match a { +// ServerExplorerItem::Category(c, _) => c.created_at, +// ServerExplorerItem::Channel(c) => c.created_at, +// }; +// let date_b = match b { +// ServerExplorerItem::Category(c, _) => c.created_at, +// ServerExplorerItem::Channel(c) => c.created_at, +// }; +// date_a.cmp(&date_b) +// } else { +// pos_cmp +// } +// }); +// +// Ok(ServerTree { items }) +// } +// } diff --git a/src/repositories/types.rs b/src/repositories/types.rs index aef3198..df203d9 100644 --- a/src/repositories/types.rs +++ b/src/repositories/types.rs @@ -6,14 +6,14 @@ pub enum ServerExplorerItem { } // Pour pouvoir trier facilement -impl ServerExplorerItem { - pub fn position(&self) -> i32 { - match self { - ServerExplorerItem::Category(cat, _) => cat.position, - ServerExplorerItem::Channel(chan) => chan.position, - } - } -} +// impl ServerExplorerItem { +// pub fn position(&self) -> i32 { +// match self { +// ServerExplorerItem::Category(cat, _) => cat.position, +// ServerExplorerItem::Channel(chan) => chan.position, +// } +// } +// } pub struct ServerTree { pub items: Vec, diff --git a/src/routes/category/dto.rs b/src/routes/category/dto.rs index bbb193e..dfa0c59 100644 --- a/src/routes/category/dto.rs +++ b/src/routes/category/dto.rs @@ -8,15 +8,12 @@ pub struct CreateCategoryRequest { pub server_id: Uuid, #[schema(example = "Discussion")] pub name: String, - #[serde(default)] - pub position: i32, } #[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct UpdateCategoryRequest { #[schema(example = "Discussion (Maj)")] pub name: String, - pub position: i32, } #[derive(Debug, Serialize, Deserialize, ToSchema)] @@ -24,7 +21,6 @@ pub struct CategoryResponse { pub id: Uuid, pub server_id: Uuid, pub name: String, - pub position: i32, pub created_at: DateTime, pub updated_at: DateTime, } diff --git a/src/routes/category/mapper.rs b/src/routes/category/mapper.rs index 6d3d97d..d872351 100644 --- a/src/routes/category/mapper.rs +++ b/src/routes/category/mapper.rs @@ -10,7 +10,6 @@ pub fn category_model_to_category_response(model: category::Model) -> CategoryRe id: model.id, server_id: model.server_id, name: model.name, - position: model.position, created_at: model.created_at, updated_at: model.updated_at, } @@ -21,7 +20,6 @@ pub fn create_request_to_am(req: CreateCategoryRequest) -> category::ActiveModel id: Set(Uuid::new_v4()), server_id: Set(req.server_id), name: Set(req.name), - position: Set(req.position), ..Default::default() } } @@ -35,7 +33,6 @@ pub fn update_request_to_am( id: Set(id), server_id: Set(server_id), name: Set(req.name), - position: Set(req.position), ..Default::default() } } diff --git a/src/routes/channel/dto.rs b/src/routes/channel/dto.rs index 187ffef..960c084 100644 --- a/src/routes/channel/dto.rs +++ b/src/routes/channel/dto.rs @@ -8,8 +8,6 @@ use uuid::Uuid; pub struct CreateChannelRequest { pub server_id: Option, pub category_id: Option, - #[serde(default)] - pub position: i32, pub channel_type: ChannelType, #[schema(example = "général")] pub name: Option, @@ -19,7 +17,6 @@ pub struct CreateChannelRequest { pub struct UpdateChannelRequest { pub server_id: Option, pub category_id: Option, - pub position: i32, pub channel_type: ChannelType, pub name: Option, } @@ -29,7 +26,6 @@ pub struct ChannelResponse { pub id: Uuid, pub server_id: Option, pub category_id: Option, - pub position: i32, pub channel_type: ChannelType, pub name: Option, pub created_at: DateTime, diff --git a/src/routes/channel/mapper.rs b/src/routes/channel/mapper.rs index 0a7f432..fa8c35d 100644 --- a/src/routes/channel/mapper.rs +++ b/src/routes/channel/mapper.rs @@ -11,7 +11,6 @@ pub fn channel_model_to_channel_response(model: channel::Model) -> ChannelRespon id: model.id, server_id: model.server_id, category_id: model.category_id, - position: model.position, channel_type: model.channel_type, name: model.name, created_at: model.created_at, @@ -24,7 +23,6 @@ pub fn create_request_to_am(req: CreateChannelRequest) -> channel::ActiveModel { id: Set(Uuid::new_v4()), server_id: Set(req.server_id), category_id: Set(req.category_id), - position: Set(req.position), channel_type: Set(req.channel_type), name: Set(req.name), ..Default::default() @@ -36,7 +34,6 @@ pub fn update_request_to_am(id: Uuid, req: UpdateChannelRequest) -> channel::Act id: Set(id), server_id: Set(req.server_id), category_id: Set(req.category_id), - position: Set(req.position), channel_type: Set(req.channel_type), name: Set(req.name), ..Default::default()