This commit is contained in:
2026-07-25 08:55:36 +02:00
parent b54b60c988
commit 6fb8ab19aa
4 changed files with 191 additions and 0 deletions
@@ -0,0 +1,49 @@
---
sessionId: session-260725-084752-m7a6
---
# Requirements
### Overview & Goals
The goal is to design `RequireServerPermission<const P: u64>` and `RequireChannelPermission<const P: u64>` 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<const PERM: u64>` and `RequireChannelPermission<const PERM: u64>` 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<const PERM: u64>` and `RequireChannelPermission<const PERM: u64>`.
- *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<const PERM: u64>` and `RequireChannelPermission<const PERM: u64>`.
- 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.
@@ -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 quils 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 didentifiants 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, dordre 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 dautres 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 lorsquune suppression ne nécessite que lidentifiant ; 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 lidentifiant 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 dinformation 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 linté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.
+6
View File
@@ -6,6 +6,7 @@ use crate::repositories::computed_permission::ComputedPermissionRepository;
use crate::repositories::group::GroupRepository; use crate::repositories::group::GroupRepository;
use crate::repositories::message::MessageRepository; use crate::repositories::message::MessageRepository;
use crate::repositories::server::ServerRepository; use crate::repositories::server::ServerRepository;
use crate::repositories::server_item_order::ServerItemOrderRepository;
use crate::repositories::user::UserRepository; use crate::repositories::user::UserRepository;
use event_bus::EventBus; use event_bus::EventBus;
use sea_orm::DatabaseConnection; use sea_orm::DatabaseConnection;
@@ -17,6 +18,7 @@ mod computed_permission;
mod group; mod group;
mod message; mod message;
mod server; mod server;
mod server_item_order;
pub mod types; pub mod types;
mod user; mod user;
@@ -35,6 +37,7 @@ pub struct Repositories {
pub message: MessageRepository, pub message: MessageRepository,
pub user: UserRepository, pub user: UserRepository,
pub computed_permission: ComputedPermissionRepository, pub computed_permission: ComputedPermissionRepository,
pub server_item_order: ServerItemOrderRepository,
} }
impl Repositories { impl Repositories {
@@ -63,6 +66,9 @@ impl Repositories {
computed_permission: ComputedPermissionRepository { computed_permission: ComputedPermissionRepository {
context: context.clone(), context: context.clone(),
}, },
server_item_order: ServerItemOrderRepository {
context: context.clone(),
},
} }
} }
} }
+46
View File
@@ -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<RepositoryContext>,
}
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<Vec<server_item_order::Model>> {
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<Uuid>, 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(())
}
}