From 2965ef8a5af128f379527c7d55c7d20b5a3391db Mon Sep 17 00:00:00 2001 From: Nell Date: Sun, 12 Jul 2026 00:22:28 +0200 Subject: [PATCH] init --- .junie/perm.md | 533 ----------------------------------- src/routes/channel/dto.rs | 6 - src/routes/channel/mapper.rs | 6 - src/routes/server/dto.rs | 6 - src/routes/server/mapper.rs | 6 - 5 files changed, 557 deletions(-) delete mode 100644 .junie/perm.md diff --git a/.junie/perm.md b/.junie/perm.md deleted file mode 100644 index 4678e73..0000000 --- a/.junie/perm.md +++ /dev/null @@ -1,533 +0,0 @@ -Oui. Si tu gardes les bitflags, je simplifierais tes modèles autour de cette règle : - -> Les permissions appartiennent aux **groupes** ou sont accordées directement à un **membre**, et chaque attribution -> possède un périmètre. - -Je ne mettrais plus les permissions par défaut directement sur `server` et `channel`. - -## 1. `user` - -Je garderais quasiment ton modèle actuel : - -```plain text -user ----- -id -username -password -pub_key -created_at -updated_at -is_superuser -``` - -`is_superuser` reste un bypass global, indépendant des permissions d’un serveur. - -Je ne mettrais pas de permissions directement dans `user`, car les permissions doivent être contextuelles à un serveur. - -## 2. `server` - -Je supprimerais : - -```plain text -default_server_permissions -default_channel_permissions -default_voice_permissions -``` - -Le serveur ne devrait pas porter directement les permissions. Il possède plutôt des groupes. - -```plain text -server ------- -id -name -password -created_at -updated_at -is_default -``` - -Les permissions par défaut seraient celles du groupe système `Everyone`. - -## 3. `server_user` - -Je garderais cette table pour l’appartenance d’un utilisateur à un serveur, mais je supprimerais probablement : - -```plain text -is_admin -is_owner -server_permissions -channel_permissions -voice_permissions -``` - -Je remplacerais les statuts par des groupes : - -```plain text -server_user ------------ -id -server_id -user_id -username -joined_at -updated_at -``` - -Puis : - -```plain text -group_member ------------- -group_id -user_id -``` - -Par exemple : - -```plain text -Everyone -Membre -Modérateur -Administrateur -``` - -L’utilisateur propriétaire pourrait rester une exception structurelle : - -```plain text -server.owner_id -``` - -ou être représenté par un groupe système très privilégié. Personnellement, je conserverais `owner_id` pour éviter qu’un -propriétaire perde accidentellement ses droits. - -## 4. `group` - -Ta table actuelle est déjà proche de ce qu’il faut : - -```plain text -group ------ -id -server_id -name -is_default -created_at -updated_at -``` - -Je garderais les trois bitmasks : - -```plain text -server_permissions -channel_permissions -voice_permissions -``` - -Donc : - -```rust -pub struct Model { - pub id: Uuid, - pub server_id: Uuid, - pub name: String, - pub is_default: bool, - pub server_permissions: i64, - pub channel_permissions: i64, - pub voice_permissions: i64, - pub created_at: DateTimeUtc, -} -``` - -Chaque groupe est alors un rôle contenant un ensemble de permissions. - -Exemples : - -```plain text -Everyone : - READ_CHANNEL - SEND_MESSAGE - JOIN_CHANNEL - SPEAK - -Modérateur : - DELETE_OTHERS_MESSAGES - MANAGE_MESSAGES - MUTE_OTHERS -``` - -## 5. `group_member` - -C’est ici que j’ajouterais le périmètre. - -Actuellement, un groupe est uniquement attribué à un utilisateur de manière globale au serveur : - -```plain text -group_id -user_id -``` - -Pour permettre à un modérateur d’agir seulement dans une catégorie ou un canal, il faut ajouter un scope. - -### Option que je recommande - -```plain text -group_member ------------- -group_id -user_id -scope_type -scope_id -``` - -Exemples : - -```plain text -Modérateur -> Alice -> server -> serveur A -Modérateur -> Bob -> category -> catégorie Support -Modérateur -> Claire -> channel -> canal Général -``` - -Avec un enum : - -```rust -pub enum PermissionScopeType { - Server, - Category, - Channel, -} -``` - -Le problème est que `scope_id` peut pointer vers plusieurs tables. Il faudra donc valider la cohérence côté application, -ou utiliser trois colonnes nullable. - -### Variante plus propre SQL - -```plain text -group_member ------------- -group_id -user_id -server_id -category_id -channel_id -``` - -Avec la règle : - -```plain text -exactement une portée est définie -``` - -Mais cette variante est plus lourde à manipuler. - -Pour un projet avec SeaORM, je choisirais probablement `scope_type + scope_id`, avec validation dans le service de -permissions. - -## 6. Permissions individuelles - -Tu as deux possibilités. - -### Option simple : les stocker dans `server_user` - -Pour une permission individuelle valable sur tout le serveur : - -```plain text -server_user ------------ -server_permissions -channel_permissions -voice_permissions -``` - -Cela permet : - -```plain text -Alice possède individuellement SEND_MESSAGE sur le serveur -``` - -Mais cette solution ne permet pas facilement une permission individuelle limitée à une catégorie ou un canal. - -### Option plus flexible : créer `user_permission_scope` - -Je recommande cette table : - -```plain text -user_permission ---------------- -id -user_id -server_id -category_id nullable -channel_id nullable -server_permissions -channel_permissions -voice_permissions -created_at -``` - -Exemples : - -```plain text -Alice -> SEND_MESSAGE -> serveur A -Bob -> DELETE_OTHERS_MESSAGES -> catégorie Support -Claire -> SEND_MESSAGE -> canal Général -``` - -La colonne `server_id` est utile pour garantir que toutes les ressources appartiennent au bon serveur. - -## 7. Que faire de `channel_user` ? - -Ta table actuelle contient : - -```plain text -channel_user ------------- -channel_id -user_id -role -permissions -``` - -Je ne garderais pas `role` et `permissions` sous cette forme si tu as déjà `group` et `group_member`. - -Je l’utiliserais uniquement pour représenter une relation particulière au canal, par exemple : - -```plain text -channel_user ------------- -id -channel_id -user_id -joined_at -``` - -Ou je la supprimerais complètement si l’accès au canal est déduit des permissions et de l’appartenance au serveur. - -Pour les permissions, je préfère `user_permission`, qui couvre déjà : - -- serveur ; -- catégorie ; -- canal. - -Sinon tu risques d’avoir trois systèmes concurrents : - -```plain text -server_user.permissions -channel_user.permissions -group.permissions -``` - -et le calcul deviendra difficile à maintenir. - -## 8. Que faire des permissions de `channel` ? - -Je supprimerais : - -```plain text -default_channel_permissions -default_voice_permissions -``` - -Le rôle par défaut `Everyone` du serveur les remplace. - -Si tu veux qu’un canal accorde des permissions supplémentaires, tu peux utiliser une table dédiée : - -```plain text -channel_permission_grant ------------------------- -channel_id -group_id nullable -user_id nullable -server_permissions -channel_permissions -voice_permissions -``` - -Mais je ne l’ajouterais pas immédiatement. Commence avec : - -```plain text -permissions serveur -permissions de groupe -permissions individuelles -``` - -Puis ajoute les overrides de canal uniquement si le besoin apparaît réellement. - -## Structure que je choisirais maintenant - -```plain text -user -server -category -channel - -server_user -group -group_member -user_permission -``` - -### `server_user` - -Appartenance au serveur : - -```plain text -user_id -server_id -joined_at -``` - -### `group` - -Permissions d’un rôle : - -```plain text -id -server_id -name -is_default -server_permissions -channel_permissions -voice_permissions -``` - -### `group_member` - -Attribution d’un rôle avec périmètre : - -```plain text -group_id -user_id -scope_type -scope_id -``` - -### `user_permission` - -Permission individuelle avec périmètre : - -```plain text -id -user_id -server_id -scope_type -scope_id -server_permissions -channel_permissions -voice_permissions -``` - -## Calcul des permissions - -Pour un utilisateur et un canal donné : - -```plain text -permissions effectives = - permissions des groupes applicables - | permissions individuelles applicables -``` - -Les groupes applicables sont ceux dont le scope est : - -```plain text -serveur parent -catégorie parente -canal courant -``` - -Exemple : - -```plain text -Alice - Everyone sur le serveur - Modérateur sur la catégorie Support - permission individuelle sur le canal Général -``` - -Dans un canal de `Support`, elle reçoit : - -```plain text -permissions Everyone -| permissions Modérateur -| permissions individuelles du canal -``` - -## Problème important avec `is_admin` - -Je supprimerais `is_admin` de `server_user`. - -Un administrateur devient simplement un groupe : - -```plain text -Administrateur -``` - -avec les permissions correspondantes. - -En revanche, je garderais probablement : - -```plain text -server.owner_id -``` - -car le propriétaire est une relation métier particulière, pas seulement un ensemble de permissions. - -## À propos de `i64` - -Puisque tu stockes des valeurs `u64` dans des colonnes `i64`, je ferais au minimum une abstraction claire côté Rust : - -```rust -fn permissions_from_db(value: i64) -> u64 { - value as u64 -} -``` - -Et j’éviterais de disperser les conversions dans les modèles. - -Si tu ne comptes pas utiliser le bit de poids fort, tu peux aussi décider officiellement que seuls les 63 premiers bits -sont valides. - -## Recommandation finale - -Je modifierais ton système comme ceci : - -```plain text -Supprimer : - permissions de server - permissions de channel - is_admin de server_user - permissions de channel_user - role texte de channel_user - -Garder : - is_superuser sur user - owner_id sur server - trois bitflags sur group - -Ajouter : - scope aux attributions de groupes - table user_permission pour les permissions individuelles -``` - -Le modèle deviendrait : - -```plain text -User - ├── appartient à des serveurs - ├── reçoit des groupes - └── peut recevoir des permissions individuelles - -Group - └── possède trois bitflags - -GroupMember - └── associe un groupe à un utilisateur dans un scope - -UserPermission - └── associe directement des bitflags à un utilisateur dans un scope -``` - -C’est le compromis que je choisirais : **bitflags conservés, groupes réutilisables, permissions individuelles possibles, -et modération limitée par scope**. \ No newline at end of file diff --git a/src/routes/channel/dto.rs b/src/routes/channel/dto.rs index d6ab425..90a656b 100644 --- a/src/routes/channel/dto.rs +++ b/src/routes/channel/dto.rs @@ -13,8 +13,6 @@ pub struct CreateChannelRequest { pub channel_type: ChannelType, #[schema(example = "général")] pub name: Option, - pub default_channel_permissions: Option, - pub default_voice_permissions: Option, } #[derive(Debug, Serialize, Deserialize, ToSchema)] @@ -24,8 +22,6 @@ pub struct UpdateChannelRequest { pub position: i32, pub channel_type: ChannelType, pub name: Option, - pub default_channel_permissions: Option, - pub default_voice_permissions: Option, } #[derive(Debug, Serialize, Deserialize, ToSchema)] @@ -38,6 +34,4 @@ pub struct ChannelResponse { pub name: Option, pub created_at: DateTime, pub updated_at: DateTime, - pub default_channel_permissions: Option, - pub default_voice_permissions: Option, } diff --git a/src/routes/channel/mapper.rs b/src/routes/channel/mapper.rs index 566dcdd..b5cf4bb 100644 --- a/src/routes/channel/mapper.rs +++ b/src/routes/channel/mapper.rs @@ -13,8 +13,6 @@ pub fn channel_model_to_channel_response(model: channel::Model) -> ChannelRespon name: model.name, created_at: model.created_at, updated_at: model.updated_at, - default_channel_permissions: model.default_channel_permissions.map(|p| p as u64), - default_voice_permissions: model.default_voice_permissions.map(|p| p as u64), } } @@ -26,8 +24,6 @@ pub fn create_request_to_am(req: CreateChannelRequest) -> channel::ActiveModel { position: Set(req.position), channel_type: Set(req.channel_type), name: Set(req.name), - default_channel_permissions: Set(req.default_channel_permissions.map(|p| p as i64)), - default_voice_permissions: Set(req.default_voice_permissions.map(|p| p as i64)), ..Default::default() } } @@ -40,8 +36,6 @@ pub fn update_request_to_am(id: Uuid, req: UpdateChannelRequest) -> channel::Act position: Set(req.position), channel_type: Set(req.channel_type), name: Set(req.name), - default_channel_permissions: Set(req.default_channel_permissions.map(|p| p as i64)), - default_voice_permissions: Set(req.default_voice_permissions.map(|p| p as i64)), ..Default::default() } } diff --git a/src/routes/server/dto.rs b/src/routes/server/dto.rs index 571001c..2e08471 100644 --- a/src/routes/server/dto.rs +++ b/src/routes/server/dto.rs @@ -17,9 +17,6 @@ pub struct UpdateServerRequest { pub name: String, pub password: Option, pub is_default: bool, - pub default_server_permissions: i64, - pub default_channel_permissions: i64, - pub default_voice_permissions: i64, } #[derive(Debug, Serialize, Deserialize, ToSchema)] @@ -29,7 +26,4 @@ pub struct ServerResponse { pub is_default: bool, pub created_at: DateTime, pub updated_at: DateTime, - pub default_server_permissions: i64, - pub default_channel_permissions: i64, - pub default_voice_permissions: i64, } diff --git a/src/routes/server/mapper.rs b/src/routes/server/mapper.rs index 8ae8c70..04e947e 100644 --- a/src/routes/server/mapper.rs +++ b/src/routes/server/mapper.rs @@ -10,9 +10,6 @@ pub fn server_model_to_server_response(model: server::Model) -> ServerResponse { is_default: model.is_default, created_at: model.created_at, updated_at: model.updated_at, - default_server_permissions: model.default_server_permissions, - default_channel_permissions: model.default_channel_permissions, - default_voice_permissions: model.default_voice_permissions, } } @@ -32,9 +29,6 @@ pub fn update_request_to_am(id: Uuid, req: UpdateServerRequest) -> server::Activ name: Set(req.name), password: Set(req.password), is_default: Set(req.is_default), - default_server_permissions: Set(req.default_server_permissions), - default_channel_permissions: Set(req.default_channel_permissions), - default_voice_permissions: Set(req.default_voice_permissions), ..Default::default() } }