From 6fb8ab19aab709b3f98addf538d8a7d910889f03 Mon Sep 17 00:00:00 2001 From: Nell Date: Sat, 25 Jul 2026 08:55:36 +0200 Subject: [PATCH] init --- .../plans/add-permission-policy-to-views.md | 49 ++++++++++ .../formalize-permission-event-payloads.md | 90 +++++++++++++++++++ src/repositories/mod.rs | 6 ++ src/repositories/server_item_order.rs | 46 ++++++++++ 4 files changed, 191 insertions(+) create mode 100644 .junie/plans/add-permission-policy-to-views.md create mode 100644 .junie/plans/formalize-permission-event-payloads.md create mode 100644 src/repositories/server_item_order.rs diff --git a/.junie/plans/add-permission-policy-to-views.md b/.junie/plans/add-permission-policy-to-views.md new file mode 100644 index 0000000..010f721 --- /dev/null +++ b/.junie/plans/add-permission-policy-to-views.md @@ -0,0 +1,49 @@ +--- +sessionId: session-260725-084752-m7a6 +--- + +# Requirements + +### Overview & Goals +The goal is to design `RequireServerPermission` and `RequireChannelPermission` Axum extractors using const generics with bitflags, and provide clear documentation and usage examples for developers adding permissions to view handlers. + +### Scope +- **In Scope:** + - Designing `RequireServerPermission` and `RequireChannelPermission` using const generics with `ServerPermission` and `ChannelPermission` bitflags. + - Designing the path parameter extraction strategy for scope (extracting `server_id` or `channel_id` from request extensions / path parameters). + - Handling superuser bypass (`is_superuser`) automatically. + - Adding detailed documentation and usage examples (`src/http/permissions.rs` doc comments / guide). +- **Out of Scope:** + - Modifying existing view handlers or database/repository schemas. + +### Functional Requirements +- **FR1:** The extractor must support const generic bitflags. +- **FR2:** The extractor must automatically extract `CurrentUser`, check `is_superuser` for bypass, and fetch the required scope (`server_id` or `channel_id`). +- **FR3:** Unauthorized requests are rejected with `403 Forbidden`, unauthenticated with `401 Unauthorized`. + +# Technical Design + +### Current Implementation +- `CurrentUser` and `Superuser` extractors in `src/http/context.rs` implement `FromRequestParts`. +- `ServerPermission` and `ChannelPermission` are defined as `bitflags!` in `src/permissions.rs`. + +### Key Decisions +- **Decision 1: Const Generics for Permission Extractors** + - *Choice:* Use `RequireServerPermission` and `RequireChannelPermission`. + - *Rationale:* Allows clean, declarative handler annotations. +- **Decision 2: Providing the Scope (`server_id` / `channel_id`)** + - *Choice:* Extract path parameters (`server_id` / `channel_id` / `id`) dynamically via Axum path parameters / extensions. + +### Proposed Changes +1. **Implement `src/http/permissions.rs`:** + - Define `RequireServerPermission` and `RequireChannelPermission`. + - Implement `FromRequestParts`. + - Add extensive inline documentation and code examples showing how to annotate route handlers with `RequireServerPermission::<{ ServerPermission::MANAGE_SERVER.bits() }>` and `RequireChannelPermission::<{ ChannelPermission::READ_CHANNEL.bits() }>`. + +### File Structure Changes +- **New File:** `src/http/permissions.rs` + +# Testing + +### Validation Approach +- Write unit/mock tests for the permission extractors. \ No newline at end of file diff --git a/.junie/plans/formalize-permission-event-payloads.md b/.junie/plans/formalize-permission-event-payloads.md new file mode 100644 index 0000000..b5de9ac --- /dev/null +++ b/.junie/plans/formalize-permission-event-payloads.md @@ -0,0 +1,90 @@ +--- +sessionId: session-260713-161459-1v9e +--- + +# Requirements + +### Objectif +Créer dans `src/domain/` les payloads explicites correspondant à **tous les événements actuellement émis par `src/repositories/`**, afin qu’ils soient réutilisables par plusieurs endpoints et consommateurs sans dépendre des modèles SeaORM, des repositories ou des DTO Gateway. + +### Inclus +- Remplacer les payloads implicites des 20 émissions recensées dans `category.rs`, `channel.rs`, `group.rs`, `message.rs`, `server.rs` et `user.rs` par des contrats de domaine dédiés. +- Couvrir les événements de création, mise à jour, suppression et changement utilisateur/serveur existants. +- Prévoir des payloads dédiés pour les données complètes des créations/mises à jour et pour les identifiants nécessaires aux suppressions ; ne pas utiliser de tuple anonyme. +- Conserver les contrats de permissions déjà demandés : `ServerUserChanged`, `RoleUserChanged`, `ServerRolePermissionChanged`, `ServerUserPermissionChanged`, `ChannelRolePermissionChanged` et `ChannelUserPermissionChanged`. +- Ajouter les contrats CRUD pour les ressources `Category`, `Channel`, `Group`, `Message`, `Server` et `User`, ainsi que les payloads d’identifiants de suppression et le payload `UserChanged` si nécessaire pour `user_changed`. +- Utiliser `Uuid` et des noms explicites (`category_id`, `channel_id`, `group_id`/`role_id`, `message_id`, `server_id`, `user_id`) plutôt que `id` lorsque le domaine est connu. +- Dériver `Clone` et `Debug` sur chaque struct, avec une composition compatible avec `Send + Sync + 'static`. +- Exposer les contrats depuis `crate::domain` pour une utilisation multi-endpoint. + +### Exclus +- Aucun branchement des nouveaux types dans les repositories et aucune modification de `Repositories`. +- Aucun changement de nom de topic, de logique CRUD, d’ordre mutation puis émission ou de comportement de suppression. +- Aucun abonnement ou traitement dans `src/core/permission_sync.rs`. +- Aucun pont avec les événements ou DTO de `src/routes/gateway/mod.rs`. +- Aucun partage direct des modèles SeaORM comme contrat de domaine. + +# Technical Design + +### Contexte actuel +- Les 20 appels `EventBus::emit` sont répartis entre six repositories ; `computed_permission.rs` ne produit actuellement aucun événement. +- Les créations et mises à jour transmettent des modèles SeaORM, tandis que les suppressions transmettent généralement un `Uuid`; `server_user_created` transmet un tuple `(server_id, user_id)`. +- `message_deleted` et `user_deleted` vérifient déjà `rows_affected` avant émission, alors que d’autres suppressions devront conserver leur comportement actuel dans cette étape. +- `src/routes/gateway/mod.rs` consomme certains événements de canal avec ses propres modèles ; les contrats de domaine resteront indépendants. + +### Organisation proposée +- Ajouter `src/domain/mod.rs` et un sous-module par famille émettrice : + - `category.rs`, `channel.rs`, `group.rs`, `message.rs`, `server.rs` et `user.rs` pour les événements CRUD et les changements de relation. + - `server_role_permission.rs`, `server_user_permission.rs`, `channel_role_permission.rs` et `channel_user_permission.rs` pour les contrats de permissions. +- Définir dans chaque module les payloads spécifiques nécessaires aux topics de sa famille, par exemple `CategoryChanged`/`CategoryDeleted`, `ChannelChanged`/`ChannelDeleted`, `MessageChanged`/`MessageDeleted`, `ServerChanged`/`ServerDeleted` et `UserChanged`/`UserDeleted`. +- Utiliser des structs distinctes lorsque les événements de création/mise à jour transportent plusieurs propriétés et lorsqu’une suppression ne nécessite que l’identifiant ; le contenu exact doit refléter les informations actuellement véhiculées par les modèles, sans importer SeaORM dans `domain`. +- Conserver `ServerUserChanged` et `RoleUserChanged` comme contrats relationnels identifiés, avec les champs `server_id`, `role_id` et `user_id`. +- Réexporter sélectivement tous les contrats depuis `crate::domain`; ajouter `pub mod domain;` dans `src/lib.rs`. +- Ne modifier ni `src/repositories/mod.rs`, ni `src/core/permission_sync.rs`, ni les routes Gateway. + +### Contrats de données +Tous les contrats suivent ce style : + +```rust +use uuid::Uuid; + +#[derive(Clone, Debug)] +pub struct ServerUserChanged { + pub server_id: Uuid, + pub user_id: Uuid, +} + +#[derive(Clone, Debug)] +pub struct ChannelDeleted { + pub channel_id: Uuid, +} +``` +Les payloads de ressource complète reprennent explicitement les champs utiles du modèle courant ; les payloads de suppression reprennent l’identifiant nommé de la ressource. Les six contrats de permissions utilisent respectivement `server_id`, `role_id`, `channel_id` et `user_id` selon leur responsabilité, sans bitmask implicite ni modèle persisté. + +### Risques et garde-fous +- Les contrats CRUD devront être suffisamment complets pour ne pas perdre d’information lors du futur branchement des repositories ; la liste de champs sera vérifiée contre `src/models/*.rs`. +- Le changement ultérieur des payloads de topics de canal pourra nécessiter une adaptation du Gateway ; cette intégration est explicitement exclue. +- Les payloads ne doivent pas être centralisés dans `repositories` ni dépendre de SeaORM, afin de rester utilisables par plusieurs endpoints. + +# Testing + +### Validation +- Ajouter des assertions ou tests de compilation pour instancier chaque contrat public et accéder à tous ses champs. +- Vérifier les familles couvrant chaque émission : `category_*`, `channel_*`, `group_*`, `message_*`, `server_*` et `user_*`, y compris les suppressions et `server_user_created`. +- Vérifier `Clone`, `Debug`, `Send`, `Sync` et `'static` pour tous les payloads. +- Vérifier les imports via `crate::domain::{...}` et exécuter `cargo check` ainsi que les tests ciblés. + +### Limites de validation +Les émissions réelles, les changements de topics, la consommation par `PermissionSyncService`, les recalculs de permissions et l’intégration Gateway restent hors périmètre ; le demandeur réalisera leur branchement ultérieurement. + +### ✓ Step 1: Recenser les émissions et les modèles +- Vérifier les topics et les champs actuellement transmis par chaque repository. +- Vérifier les modèles correspondants pour définir les contrats complets. + +### ✓ Step 2: Créer les modules et payloads de domaine +- Ajouter les modules `src/domain/` et les structs CRUD, relationnelles et de permissions. +- Exposer les contrats depuis `crate::domain` sans modifier les repositories. + +### ✓ Step 3: Ajouter la validation des contrats +- Ajouter des assertions de compilation couvrant les structs et leurs champs. +- Exécuter `cargo check` et les tests ciblés. \ No newline at end of file diff --git a/src/repositories/mod.rs b/src/repositories/mod.rs index a07b251..b0d72ab 100644 --- a/src/repositories/mod.rs +++ b/src/repositories/mod.rs @@ -6,6 +6,7 @@ use crate::repositories::computed_permission::ComputedPermissionRepository; use crate::repositories::group::GroupRepository; use crate::repositories::message::MessageRepository; use crate::repositories::server::ServerRepository; +use crate::repositories::server_item_order::ServerItemOrderRepository; use crate::repositories::user::UserRepository; use event_bus::EventBus; use sea_orm::DatabaseConnection; @@ -17,6 +18,7 @@ mod computed_permission; mod group; mod message; mod server; +mod server_item_order; pub mod types; mod user; @@ -35,6 +37,7 @@ pub struct Repositories { pub message: MessageRepository, pub user: UserRepository, pub computed_permission: ComputedPermissionRepository, + pub server_item_order: ServerItemOrderRepository, } impl Repositories { @@ -63,6 +66,9 @@ impl Repositories { computed_permission: ComputedPermissionRepository { context: context.clone(), }, + server_item_order: ServerItemOrderRepository { + context: context.clone(), + }, } } } diff --git a/src/repositories/server_item_order.rs b/src/repositories/server_item_order.rs new file mode 100644 index 0000000..884b144 --- /dev/null +++ b/src/repositories/server_item_order.rs @@ -0,0 +1,46 @@ +use crate::models::server_item_order; +use crate::models::server_item_order::OrderedResourceType; +use crate::repositories::{AnyResult, RepositoryContext}; +use sea_orm::{ + ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, TransactionTrait, +}; +use std::sync::Arc; +use uuid::Uuid; + +#[derive(Clone, Debug)] +pub struct ServerItemOrderRepository { + pub context: Arc, +} + +impl ServerItemOrderRepository { + /// Récupère la liste ordonnée pour un serveur (racine ou spécifique à une catégorie) + pub async fn get_by_server(&self, server_id: Uuid) -> AnyResult> { + Ok(server_item_order::Entity::find() + .filter(server_item_order::Column::ServerId.eq(server_id)) + .order_by_asc(server_item_order::Column::OrderKey) + .all(&self.context.db) + .await?) + } + + /// Réorganise en bloc (reorder) une liste d'éléments dans un serveur ou une catégorie + pub async fn update_orders( + &self, + server_id: Uuid, + items: Vec<(Uuid, OrderedResourceType, Option, i64)>, // (resource_id, resource_type, parent_category_id, order_key) + ) -> AnyResult<()> { + self.context + .db + .transaction::<_, (), anyhow::Error>(|txn| { + Box::pin(async move { + for (resource_id, res_type, parent_cat_id, order_key) in items { + // Logique de mise à jour / upsert des order_key + } + Ok(()) + }) + }) + .await?; + + self.context.events.emit("server_order_updated", server_id); + Ok(()) + } +}