Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
795 lines
29 KiB
Markdown
795 lines
29 KiB
Markdown
# Migration Chat-Server Rust : i64 → UUID — Rapport complet
|
|
|
|
**Date** : 2025-01-27
|
|
**Service** : `veza-chat-server` (Rust/Axum)
|
|
**Objectif** : Migrer tous les IDs de `i64` vers `Uuid` pour cohérence avec le schéma DB et le backend Go
|
|
|
|
---
|
|
|
|
## Résumé exécutif
|
|
|
|
- **Fichiers à modifier** : ~25 fichiers
|
|
- **Structs à migrer** : 8 structures principales
|
|
- **Requêtes SQL à mettre à jour** : ~50+ requêtes SQLx
|
|
- **Messages WebSocket à migrer** : 5+ types de messages
|
|
- **Estimation temps** : 4-6 heures
|
|
- **Risque** : Moyen (nécessite tests exhaustifs)
|
|
|
|
**État actuel** :
|
|
- ✅ **Schéma DB** : Utilise `UUID` (colonnes `uuid`) mais aussi `BIGSERIAL` (colonnes `id`)
|
|
- ❌ **Code Rust** : Utilise `i64` pour la plupart des IDs
|
|
- ✅ **Frontend** : Envoie déjà des UUID strings
|
|
- ⚠️ **Backend Go** : Mixte (certains handlers utilisent encore `int64`)
|
|
|
|
**Problème identifié** : Le schéma DB a une **cohabitation BIGSERIAL/UUID** :
|
|
- Colonnes `id` : `BIGSERIAL` (i64)
|
|
- Colonnes `uuid` : `UUID` (Uuid)
|
|
- Le code Rust utilise les colonnes `id` (i64) alors qu'il devrait utiliser `uuid`
|
|
|
|
---
|
|
|
|
## 1. Cartographie complète
|
|
|
|
### 1.1 Structures avec IDs à migrer
|
|
|
|
| Struct | Fichier | Champs i64 | Champs déjà Uuid | Action | Priorité |
|
|
|--------|---------|------------|------------------|--------|----------|
|
|
| `Room` | `src/hub/channels.rs` | `id: i64`, `owner_id: i64` | `uuid: Uuid` | Supprimer `id`, renommer `uuid→id`, migrer `owner_id` | 🔴 Haute |
|
|
| `RoomMember` | `src/hub/channels.rs` | `id: i64`, `conversation_id: i64`, `user_id: i64` | - | Migrer tous vers `Uuid` | 🔴 Haute |
|
|
| `RoomMessage` | `src/hub/channels.rs` | `id: i64`, `author_id: i64`, `conversation_id: i64`, `parent_message_id: Option<i64>` | `uuid: Uuid` | Supprimer `id`, renommer `uuid→id`, migrer autres | 🔴 Haute |
|
|
| `RoomStats` | `src/hub/channels.rs` | `room_id: i64` | - | Migrer vers `Uuid` | 🟡 Moyenne |
|
|
| `EnhancedRoomMessage` | `src/hub/channels.rs` | `id: i64`, `author_id: i32`, `room_id: Option<i32>` | - | Migrer vers `Uuid` | 🟡 Moyenne |
|
|
| `AuditLog` | `src/hub/audit.rs` | `id: i64`, `user_id: Option<i64>` | - | Migrer vers `Uuid` | 🟡 Moyenne |
|
|
| `SecurityEvent` | `src/hub/audit.rs` | `id: i64`, `user_id: Option<i64>` | - | Migrer vers `Uuid` | 🟡 Moyenne |
|
|
| `UserActivity` | `src/hub/audit.rs` | `user_id: i64` | - | Migrer vers `Uuid` | 🟡 Moyenne |
|
|
| `RoomAuditSummary` | `src/hub/audit.rs` | `room_id: i64` | - | Migrer vers `Uuid` | 🟡 Moyenne |
|
|
| `Message` | `src/models/message.rs` | - | `id: Uuid`, `conversation_id: Uuid`, `sender_id: Uuid` | ✅ Déjà migré | ✅ OK |
|
|
| `WsInbound` | `src/messages.rs` | `to_user_id: i32`, `with: i32` | - | Migrer vers `Uuid` (string) | 🔴 Haute |
|
|
|
|
**Total** : 10 structures à migrer (8 avec i64, 2 déjà OK)
|
|
|
|
### 1.2 Requêtes SQLx à mettre à jour
|
|
|
|
#### Fichier : `src/hub/channels.rs`
|
|
|
|
| Fonction | Ligne | Requête | Champs i64 concernés | Modification |
|
|
|----------|-------|---------|---------------------|--------------|
|
|
| `create_room` | 139-152 | `INSERT INTO conversations ... RETURNING id, uuid, ...` | `id`, `owner_id` | Utiliser `uuid` au lieu de `id`, migrer `owner_id` |
|
|
| `join_room` | 198-220 | `SELECT id, uuid, ... FROM conversations WHERE id = $1` | `room_id`, `user_id` | Utiliser `uuid` au lieu de `id` |
|
|
| `leave_room` | 254-290 | `SELECT id, ... FROM conversations WHERE id = $1` | `room_id`, `user_id` | Utiliser `uuid` |
|
|
| `send_room_message` | 347-412 | `INSERT INTO messages ... RETURNING id` | `room_id`, `author_id`, `message_id`, `parent_message_id` | Utiliser `uuid` pour tous |
|
|
| `pin_message` | 416-450 | `UPDATE messages ... WHERE id = $2` | `room_id`, `message_id`, `user_id` | Utiliser `uuid` |
|
|
| `fetch_room_history` | 462-546 | `SELECT id, uuid, ... FROM messages WHERE conversation_id = $1` | `room_id`, `user_id`, `message_id` | Utiliser `uuid` |
|
|
| `fetch_pinned_messages` | 548-593 | `SELECT ... FROM messages WHERE conversation_id = $1` | `room_id`, `user_id` | Utiliser `uuid` |
|
|
| `get_room_stats` | 594-623 | `SELECT c.id as room_id, ...` | `room_id` | Utiliser `uuid` |
|
|
| `list_room_members` | 625-670 | `SELECT ... FROM conversation_members WHERE conversation_id = $1` | `room_id`, `user_id` | Utiliser `uuid` |
|
|
|
|
**Total dans channels.rs** : ~20 requêtes à modifier
|
|
|
|
#### Fichier : `src/hub/audit.rs`
|
|
|
|
| Fonction | Ligne | Requête | Champs i64 concernés | Modification |
|
|
|----------|-------|---------|---------------------|--------------|
|
|
| `log_action` | 81-100 | `INSERT INTO audit_logs ... RETURNING id` | `user_id: Option<i64>` | Migrer vers `Option<Uuid>` |
|
|
| `log_security_event` | 112-137 | `INSERT INTO security_events ... RETURNING id` | `user_id: Option<i64>` | Migrer vers `Option<Uuid>` |
|
|
| `log_room_created` | 150-173 | `log_action(..., room_id: i64, owner_id: i64)` | `room_id`, `owner_id` | Migrer vers `Uuid` |
|
|
| `log_member_change` | 174-207 | `log_action(..., room_id: i64, target_user_id: i64, ...)` | `room_id`, `user_ids` | Migrer vers `Uuid` |
|
|
| `log_message_modified` | 207-244 | `log_action(..., message_id: i64, room_id: i64, ...)` | Tous les IDs | Migrer vers `Uuid` |
|
|
| `log_moderation_action` | 244-297 | `log_action(..., room_id: i64, ...)` | Tous les IDs | Migrer vers `Uuid` |
|
|
| `get_room_audit_logs` | 297-346 | `SELECT ... FROM audit_logs WHERE ...` | `room_id`, `requesting_user_id` | Migrer vers `Uuid` |
|
|
| `get_room_security_events` | 347-398 | `SELECT ... FROM security_events WHERE ...` | `room_id`, `requesting_user_id` | Migrer vers `Uuid` |
|
|
| `generate_room_activity_report` | 399-515 | `SELECT ... WHERE room_id = $1` | `room_id`, `requesting_user_id` | Migrer vers `Uuid` |
|
|
| `get_room_audit_summary` | 516-551 | `SELECT c.id as room_id, ...` | `room_id`, `requesting_user_id` | Migrer vers `Uuid` |
|
|
| `detect_suspicious_patterns` | 552-590 | `SELECT ... WHERE room_id = $1` | `room_id` | Migrer vers `Uuid` |
|
|
|
|
**Total dans audit.rs** : ~15 requêtes à modifier
|
|
|
|
#### Autres fichiers
|
|
|
|
| Fichier | Fonctions impactées | Requêtes | Priorité |
|
|
|---------|---------------------|----------|----------|
|
|
| `src/hub/direct_messages.rs` | Toutes fonctions DM | ~10 requêtes | 🔴 Haute |
|
|
| `src/repository/room_repository.rs` | Toutes méthodes | ~8 requêtes | 🔴 Haute |
|
|
| `src/repository/message_repository.rs` | Toutes méthodes | ~8 requêtes | 🔴 Haute |
|
|
| `src/message_store.rs` | Store/retrieve | ~5 requêtes | 🟡 Moyenne |
|
|
| `src/services/room_service.rs` | Service layer | ~5 requêtes | 🟡 Moyenne |
|
|
|
|
**Total estimé** : ~60 requêtes SQLx à modifier
|
|
|
|
### 1.3 Conversions/parsing d'ID à migrer
|
|
|
|
| Fichier | Ligne | Code actuel | Code cible | Contexte |
|
|
|---------|-------|-------------|------------|----------|
|
|
| `src/messages.rs` | 21 | `to_user_id: i32` | `to_user_id: String` (UUID string) | WebSocket inbound |
|
|
| `src/messages.rs` | 33 | `with: i32` | `with: String` (UUID string) | WebSocket inbound |
|
|
| `src/hub/channels.rs` | 122 | `owner_id: i64` | `owner_id: Uuid` | Paramètre fonction |
|
|
| `src/hub/channels.rs` | 189 | `room_id: i64, user_id: i64` | `room_id: Uuid, user_id: Uuid` | Paramètres fonction |
|
|
| `src/hub/channels.rs` | 326 | `author_id: i64` | `author_id: Uuid` | Paramètre fonction |
|
|
| `src/hub/channels.rs` | 339 | `author_id as i32` | Supprimer conversion | Rate limiting |
|
|
| `src/hub/channels.rs` | 383 | `message.get("id")` → `i64` | `message.get("uuid")` → `Uuid` | Récupération ID |
|
|
| `src/hub/audit.rs` | 81 | `user_id: Option<i64>` | `user_id: Option<Uuid>` | Paramètre fonction |
|
|
| `src/hub/audit.rs` | 150 | `room_id: i64, owner_id: i64` | `room_id: Uuid, owner_id: Uuid` | Paramètres fonction |
|
|
|
|
**Patterns de conversion à chercher** :
|
|
- `as i64` / `as i32` : Conversions explicites
|
|
- `.parse::<i64>()` : Parsing depuis string
|
|
- `get::<i64, _>("id")` : Récupération depuis SQLx Row
|
|
- `validate_user_id(user_id as i32)` : Validation avec conversion
|
|
|
|
### 1.4 Messages/DTOs WebSocket à migrer
|
|
|
|
| Struct | Fichier | Champs i64 | Sérialisé en JSON | Impact client | Action |
|
|
|--------|---------|------------|-------------------|---------------|--------|
|
|
| `WsInbound::DirectMessage` | `src/messages.rs` | `to_user_id: i32` | Oui | ❌ Frontend envoie UUID string | Migrer vers `String` (UUID) |
|
|
| `WsInbound::DmHistory` | `src/messages.rs` | `with: i32` | Oui | ❌ Frontend envoie UUID string | Migrer vers `String` (UUID) |
|
|
| `RoomMessage` | `src/hub/channels.rs` | `id: i64`, `author_id: i64`, `conversation_id: i64` | Oui | ⚠️ Frontend attend UUID string | Migrer vers `Uuid` (sérialisé en string) |
|
|
| `Room` | `src/hub/channels.rs` | `id: i64`, `owner_id: i64` | Oui | ⚠️ Frontend attend UUID string | Migrer vers `Uuid` |
|
|
| `RoomMember` | `src/hub/channels.rs` | `id: i64`, `user_id: i64` | Oui | ⚠️ Frontend attend UUID string | Migrer vers `Uuid` |
|
|
|
|
**Note importante** : Le frontend envoie déjà des UUID strings (voir `apps/web/src/features/chat/types/index.ts`). Le problème est que le Rust attend des `i32`/`i64`.
|
|
|
|
### 1.5 Schéma DB (source de vérité)
|
|
|
|
**Analyse du schéma** : `migrations/001_create_clean_database.sql`
|
|
|
|
| Table | Colonne ID | Type DB | Colonne UUID | Type DB | Type Rust actuel | Conforme | Action |
|
|
|-------|------------|---------|--------------|---------|------------------|----------|--------|
|
|
| `users` | `id` | `BIGSERIAL` | `uuid` | `UUID` | `i64` | ❌ | Utiliser `uuid` |
|
|
| `conversations` | `id` | `BIGSERIAL` | `uuid` | `UUID` | `i64` | ❌ | Utiliser `uuid` |
|
|
| `conversation_members` | `id` | `BIGSERIAL` | - | - | `i64` | ❌ | **PROBLÈME** : Pas de colonne UUID |
|
|
| `messages` | `id` | `BIGSERIAL` | `uuid` | `UUID` | `i64` | ❌ | Utiliser `uuid` |
|
|
| `audit_logs` | `id` | `BIGSERIAL` | - | - | `i64` | ❌ | **PROBLÈME** : Pas de colonne UUID |
|
|
| `security_events` | `id` | `BIGSERIAL` | - | - | `i64` | ❌ | **PROBLÈME** : Pas de colonne UUID |
|
|
|
|
**Problème majeur identifié** :
|
|
- Les tables `conversation_members`, `audit_logs`, `security_events` n'ont **PAS de colonne UUID**
|
|
- Elles utilisent uniquement `BIGSERIAL` pour les IDs
|
|
- **Solution** : Soit ajouter des colonnes UUID (migration DB), soit utiliser les IDs BIGSERIAL mais les convertir en UUID côté application
|
|
|
|
**Recommandation** : Utiliser les colonnes `uuid` existantes et ajouter des migrations pour les tables sans UUID.
|
|
|
|
---
|
|
|
|
## 2. Impacts et dépendances
|
|
|
|
### 2.1 Communication avec le backend Go
|
|
|
|
| Direction | Endpoint/Event | Format ID actuel (Rust) | Format attendu (Go) | Action |
|
|
|-----------|---------------|------------------------|---------------------|--------|
|
|
| Go → Rust | WebSocket token (JWT) | `user_id` dans JWT : `int64` | `user_id` : `uuid.UUID` | ⚠️ **PROBLÈME** : JWT contient int64 |
|
|
| Go → Rust | HTTP webhook (si existe) | `user_id: i64` | `user_id: string (UUID)` | Vérifier si webhooks existent |
|
|
| Rust → Go | Webhook callback (si existe) | `user_id: i64` | `user_id: string (UUID)` | Migrer vers UUID |
|
|
|
|
**Problème identifié** : Le backend Go génère des tokens JWT avec `user_id` en `uuid.UUID`, mais le chat-server Rust pourrait s'attendre à un `int64`. À vérifier dans `src/auth.rs` et `src/jwt_manager.rs`.
|
|
|
|
### 2.2 Communication avec le Frontend
|
|
|
|
| Message WS | Direction | Champ | Type actuel (Rust) | Type Frontend | Compatible | Action |
|
|
|------------|-----------|-------|-------------------|---------------|------------|--------|
|
|
| `NewMessage` | Server→Client | `message_id` | `i64` (number) | `string` (UUID) | ❌ | Migrer vers `Uuid` (sérialisé en string) |
|
|
| `NewMessage` | Server→Client | `sender_id` | `i64` (number) | `string` (UUID) | ❌ | Migrer vers `Uuid` |
|
|
| `NewMessage` | Server→Client | `conversation_id` | `i64` (number) | `string` (UUID) | ❌ | Migrer vers `Uuid` |
|
|
| `join_room` | Client→Server | `room` | `String` (nom) | `string` (nom ou UUID) | ✅ | OK (utilise nom, pas ID) |
|
|
| `direct_message` | Client→Server | `to_user_id` | `i32` (number) | `string` (UUID) | ❌ | Migrer vers `String` (UUID) |
|
|
| `dm_history` | Client→Server | `with` | `i32` (number) | `string` (UUID) | ❌ | Migrer vers `String` (UUID) |
|
|
|
|
**Résultat** : ❌ **Incompatible** - Le frontend envoie/reçoit des UUID strings, mais le Rust attend/envoie des `i64`.
|
|
|
|
### 2.3 Tests existants
|
|
|
|
| Fichier test | Test | Utilise i64 | Modification |
|
|
|--------------|------|-------------|--------------|
|
|
| `src/hub/channels.rs` (tests inline) | `test_room_creation` | Probable | Changer en `Uuid::new_v4()` |
|
|
| `tests/integration_test.rs` (si existe) | Tests d'intégration | Probable | Migrer vers UUID |
|
|
| Tests unitaires | Tous | Probable | Migrer vers UUID |
|
|
|
|
**Action** : Vérifier avec `grep -r "#\[test\]" veza-chat-server/src/` et mettre à jour tous les tests.
|
|
|
|
---
|
|
|
|
## 3. Plan de migration détaillé
|
|
|
|
### 3.1 Ordre des modifications (bottom-up)
|
|
|
|
#### Étape 1 : Préparation (sans changement fonctionnel)
|
|
|
|
1. [ ] Vérifier `Cargo.toml` : `uuid` avec features `["v4", "serde"]` ✅ (déjà présent)
|
|
2. [ ] Vérifier `Cargo.toml` : `sqlx` avec feature `uuid` ✅ (déjà présent)
|
|
3. [ ] Créer branche : `git checkout -b fix/chat-server-uuid-migration`
|
|
4. [ ] Tag de sauvegarde : `git tag pre-uuid-migration-chat-server`
|
|
|
|
#### Étape 2 : Migration des structs (du plus simple au plus complexe)
|
|
|
|
**Ordre recommandé** :
|
|
|
|
1. [ ] `src/models/message.rs` - ✅ Déjà migré, vérifier seulement
|
|
2. [ ] `src/messages.rs` - Migrer `WsInbound` (simple, pas de DB)
|
|
3. [ ] `src/hub/channels.rs` - Migrer `Room`, `RoomMember`, `RoomMessage` (complexe)
|
|
4. [ ] `src/hub/audit.rs` - Migrer structs d'audit
|
|
5. [ ] Autres structs dans autres fichiers
|
|
|
|
#### Étape 3 : Migration des requêtes SQLx
|
|
|
|
**Ordre recommandé** :
|
|
|
|
1. [ ] `src/hub/channels.rs` - Toutes les requêtes (fonctions principales)
|
|
2. [ ] `src/hub/audit.rs` - Toutes les requêtes d'audit
|
|
3. [ ] `src/hub/direct_messages.rs` - Requêtes DM
|
|
4. [ ] `src/repository/*.rs` - Repositories
|
|
5. [ ] Autres fichiers avec requêtes SQL
|
|
|
|
#### Étape 4 : Migration handlers/WebSocket
|
|
|
|
1. [ ] `src/websocket/handler.rs` - Handlers WebSocket
|
|
2. [ ] `src/websocket/broadcast.rs` - Broadcast messages
|
|
3. [ ] `src/message_handler.rs` - Message handlers
|
|
4. [ ] Autres handlers
|
|
|
|
#### Étape 5 : Tests
|
|
|
|
1. [ ] Mettre à jour tous les tests unitaires
|
|
2. [ ] Mettre à jour les tests d'intégration
|
|
3. [ ] Ajouter des tests de conversion UUID
|
|
|
|
### 3.2 Modifications fichier par fichier
|
|
|
|
#### Fichier : `src/messages.rs`
|
|
|
|
**Modification** : Migrer `WsInbound` pour accepter des UUID strings
|
|
|
|
```rust
|
|
// AVANT
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(tag = "type")]
|
|
pub enum WsInbound {
|
|
#[serde(rename = "direct_message")]
|
|
DirectMessage {
|
|
to_user_id: i32, // ❌
|
|
content: String,
|
|
},
|
|
#[serde(rename = "dm_history")]
|
|
DmHistory {
|
|
with: i32, // ❌
|
|
limit: i64,
|
|
}
|
|
}
|
|
|
|
// APRÈS
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(tag = "type")]
|
|
pub enum WsInbound {
|
|
#[serde(rename = "direct_message")]
|
|
DirectMessage {
|
|
to_user_id: String, // ✅ UUID string depuis frontend
|
|
content: String,
|
|
},
|
|
#[serde(rename = "dm_history")]
|
|
DmHistory {
|
|
with: String, // ✅ UUID string depuis frontend
|
|
limit: i64,
|
|
}
|
|
}
|
|
```
|
|
|
|
**Fonctions impactées** : Aucune (juste parsing)
|
|
|
|
---
|
|
|
|
#### Fichier : `src/hub/channels.rs`
|
|
|
|
**Modification 1** : Struct `Room`
|
|
|
|
```rust
|
|
// AVANT
|
|
#[derive(Debug, FromRow, Serialize, Deserialize)]
|
|
pub struct Room {
|
|
pub id: i64, // ❌
|
|
pub uuid: Uuid, // ✅ Existe déjà
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub owner_id: i64, // ❌
|
|
pub is_public: bool,
|
|
pub is_archived: bool,
|
|
pub max_members: Option<i32>,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
// APRÈS
|
|
#[derive(Debug, FromRow, Serialize, Deserialize)]
|
|
pub struct Room {
|
|
pub id: Uuid, // ✅ Renommé depuis uuid
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub owner_id: Uuid, // ✅ Migré
|
|
pub is_public: bool,
|
|
pub is_archived: bool,
|
|
pub max_members: Option<i32>,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
```
|
|
|
|
**Modification 2** : Struct `RoomMember`
|
|
|
|
```rust
|
|
// AVANT
|
|
#[derive(Debug, FromRow, Serialize, Deserialize)]
|
|
pub struct RoomMember {
|
|
pub id: i64, // ❌
|
|
pub conversation_id: i64, // ❌
|
|
pub user_id: i64, // ❌
|
|
pub role: String,
|
|
pub joined_at: DateTime<Utc>,
|
|
pub left_at: Option<DateTime<Utc>>,
|
|
pub is_muted: bool,
|
|
}
|
|
|
|
// APRÈS
|
|
#[derive(Debug, FromRow, Serialize, Deserialize)]
|
|
pub struct RoomMember {
|
|
pub id: Uuid, // ✅
|
|
pub conversation_id: Uuid, // ✅
|
|
pub user_id: Uuid, // ✅
|
|
pub role: String,
|
|
pub joined_at: DateTime<Utc>,
|
|
pub left_at: Option<DateTime<Utc>>,
|
|
pub is_muted: bool,
|
|
}
|
|
```
|
|
|
|
**Modification 3** : Struct `RoomMessage`
|
|
|
|
```rust
|
|
// AVANT
|
|
#[derive(Debug, FromRow, Serialize)]
|
|
pub struct RoomMessage {
|
|
pub id: i64, // ❌
|
|
pub uuid: Uuid, // ✅ Existe déjà
|
|
pub author_id: i64, // ❌
|
|
pub author_username: String,
|
|
pub conversation_id: i64, // ❌
|
|
pub content: String,
|
|
pub parent_message_id: Option<i64>, // ❌
|
|
// ...
|
|
}
|
|
|
|
// APRÈS
|
|
#[derive(Debug, FromRow, Serialize)]
|
|
pub struct RoomMessage {
|
|
pub id: Uuid, // ✅ Renommé depuis uuid
|
|
pub author_id: Uuid, // ✅
|
|
pub author_username: String,
|
|
pub conversation_id: Uuid, // ✅
|
|
pub content: String,
|
|
pub parent_message_id: Option<Uuid>, // ✅
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Modification 4** : Fonction `create_room`
|
|
|
|
```rust
|
|
// AVANT
|
|
pub async fn create_room(
|
|
hub: &ChatHub,
|
|
owner_id: i64, // ❌
|
|
name: &str,
|
|
// ...
|
|
) -> Result<Room> {
|
|
let room_uuid = Uuid::new_v4();
|
|
|
|
let conversation = query_as::<_, Room>("
|
|
INSERT INTO conversations (uuid, type, name, description, owner_id, is_public, max_members)
|
|
VALUES ($1, 'public_room', $2, $3, $4, $5, $6)
|
|
RETURNING id, uuid, name, description, owner_id, is_public, is_archived, max_members, created_at, updated_at
|
|
")
|
|
.bind(room_uuid)
|
|
.bind(owner_id) // ❌ i64
|
|
// ...
|
|
}
|
|
|
|
// APRÈS
|
|
pub async fn create_room(
|
|
hub: &ChatHub,
|
|
owner_id: Uuid, // ✅
|
|
name: &str,
|
|
// ...
|
|
) -> Result<Room> {
|
|
let room_uuid = Uuid::new_v4();
|
|
|
|
let conversation = query_as::<_, Room>("
|
|
INSERT INTO conversations (uuid, type, name, description, owner_id, is_public, max_members)
|
|
VALUES ($1, 'public_room', $2, $3, $4, $5, $6)
|
|
RETURNING uuid as id, name, description, owner_id, is_public, is_archived, max_members, created_at, updated_at
|
|
")
|
|
.bind(room_uuid)
|
|
.bind(owner_id) // ✅ Uuid
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Note** : La requête SQL doit utiliser `uuid as id` pour mapper la colonne `uuid` vers le champ `id` de la struct.
|
|
|
|
**Modification 5** : Fonction `send_room_message`
|
|
|
|
```rust
|
|
// AVANT
|
|
pub async fn send_room_message(
|
|
hub: &ChatHub,
|
|
room_id: i64, // ❌
|
|
author_id: i64, // ❌
|
|
username: &str,
|
|
content: &str,
|
|
parent_message_id: Option<i64>, // ❌
|
|
metadata: Option<Value>
|
|
) -> Result<i64> { // ❌ Retourne i64
|
|
// ...
|
|
let message = query("
|
|
INSERT INTO messages (uuid, author_id, conversation_id, content, parent_message_id, metadata, status)
|
|
VALUES ($1, $2, $3, $4, $5, $6, 'sent')
|
|
RETURNING id, created_at
|
|
")
|
|
.bind(message_uuid)
|
|
.bind(author_id) // ❌ i64
|
|
.bind(room_id) // ❌ i64
|
|
.bind(parent_message_id) // ❌ Option<i64>
|
|
// ...
|
|
let message_id: i64 = message.get("id"); // ❌
|
|
// ...
|
|
Ok(message_id) // ❌
|
|
}
|
|
|
|
// APRÈS
|
|
pub async fn send_room_message(
|
|
hub: &ChatHub,
|
|
room_id: Uuid, // ✅
|
|
author_id: Uuid, // ✅
|
|
username: &str,
|
|
content: &str,
|
|
parent_message_id: Option<Uuid>, // ✅
|
|
metadata: Option<Value>
|
|
) -> Result<Uuid> { // ✅ Retourne Uuid
|
|
// ...
|
|
let message = query("
|
|
INSERT INTO messages (uuid, author_id, conversation_id, content, parent_message_id, metadata, status)
|
|
VALUES ($1, $2, $3, $4, $5, $6, 'sent')
|
|
RETURNING uuid as id, created_at
|
|
")
|
|
.bind(message_uuid)
|
|
.bind(author_id) // ✅ Uuid
|
|
.bind(room_id) // ✅ Uuid
|
|
.bind(parent_message_id) // ✅ Option<Uuid>
|
|
// ...
|
|
let message_id: Uuid = message.get("id"); // ✅ (depuis uuid as id)
|
|
// ...
|
|
Ok(message_id) // ✅
|
|
}
|
|
```
|
|
|
|
**Toutes les autres fonctions** : Même pattern - remplacer `i64` par `Uuid` dans les paramètres et utiliser `uuid as id` dans les requêtes SQL.
|
|
|
|
---
|
|
|
|
#### Fichier : `src/hub/audit.rs`
|
|
|
|
**Modification** : Toutes les fonctions utilisent `i64` pour les IDs. Migrer vers `Uuid`.
|
|
|
|
```rust
|
|
// AVANT
|
|
pub async fn log_action(
|
|
hub: &ChatHub,
|
|
action: &str,
|
|
details: Value,
|
|
user_id: Option<i64>, // ❌
|
|
// ...
|
|
) -> Result<i64> { // ❌
|
|
// ...
|
|
}
|
|
|
|
// APRÈS
|
|
pub async fn log_action(
|
|
hub: &ChatHub,
|
|
action: &str,
|
|
details: Value,
|
|
user_id: Option<Uuid>, // ✅
|
|
// ...
|
|
) -> Result<Uuid> { // ✅
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Note** : Les tables `audit_logs` et `security_events` n'ont pas de colonne `uuid`. Deux options :
|
|
1. **Option A (recommandée)** : Ajouter une migration DB pour ajouter des colonnes `uuid`
|
|
2. **Option B** : Garder `BIGSERIAL` pour ces tables (moins idéal)
|
|
|
|
---
|
|
|
|
### 3.3 Gestion de la sérialisation JSON
|
|
|
|
**Configuration Serde** : Avec `uuid = { version = "1.6", features = ["v4", "serde"] }`, les `Uuid` se sérialisent automatiquement en strings.
|
|
|
|
**Vérification** : Le JSON produit sera :
|
|
```json
|
|
{
|
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"name": "General"
|
|
}
|
|
```
|
|
|
|
**Pas besoin de configuration spéciale** - Serde gère automatiquement.
|
|
|
|
### 3.4 Gestion des requêtes SQLx
|
|
|
|
**Pattern de migration** :
|
|
|
|
```rust
|
|
// AVANT (i64)
|
|
let room = query_as::<_, Room>("
|
|
SELECT id, uuid, name, description, owner_id, is_public, is_archived, max_members, created_at, updated_at
|
|
FROM conversations
|
|
WHERE id = $1
|
|
")
|
|
.bind(room_id) // i64
|
|
.fetch_one(&pool)
|
|
.await?;
|
|
|
|
// APRÈS (Uuid)
|
|
let room = query_as::<_, Room>("
|
|
SELECT uuid as id, name, description, owner_id, is_public, is_archived, max_members, created_at, updated_at
|
|
FROM conversations
|
|
WHERE uuid = $1
|
|
")
|
|
.bind(room_id) // Uuid
|
|
.fetch_one(&pool)
|
|
.await?;
|
|
```
|
|
|
|
**Points d'attention** :
|
|
1. Utiliser `uuid as id` dans les SELECT pour mapper vers le champ `id` de la struct
|
|
2. Utiliser `WHERE uuid = $1` au lieu de `WHERE id = $1`
|
|
3. Les paramètres `$1, $2, ...` doivent être de type `Uuid`
|
|
4. SQLx vérifie les types au compile-time - les erreurs seront explicites
|
|
|
|
---
|
|
|
|
## 4. Gestion des erreurs et rollback
|
|
|
|
### 4.1 Points de rollback
|
|
|
|
**Stratégie de commits** :
|
|
|
|
#### Commit 1 : Préparation
|
|
```bash
|
|
git commit -m "chore(chat-server): prepare UUID migration dependencies"
|
|
```
|
|
- Vérifier/ajouter dépendances Cargo.toml ✅ (déjà présentes)
|
|
- Créer types/ids.rs si nécessaire (optionnel)
|
|
|
|
#### Commit 2 : Migration des structs
|
|
```bash
|
|
git commit -m "refactor(chat-server): migrate structs from i64 to Uuid"
|
|
```
|
|
- Modifier toutes les structs
|
|
- **Le code NE COMPILE PAS encore** (c'est normal)
|
|
|
|
#### Commit 3 : Migration des requêtes DB
|
|
```bash
|
|
git commit -m "refactor(chat-server): migrate SQLx queries to Uuid"
|
|
```
|
|
- Modifier toutes les requêtes SQLx
|
|
- **Le code devrait compiler maintenant**
|
|
|
|
#### Commit 4 : Migration handlers/WebSocket
|
|
```bash
|
|
git commit -m "refactor(chat-server): migrate handlers and WS to Uuid"
|
|
```
|
|
- Modifier les handlers
|
|
- Modifier les messages WS
|
|
|
|
#### Commit 5 : Tests
|
|
```bash
|
|
git commit -m "test(chat-server): update tests for UUID migration"
|
|
```
|
|
- Mettre à jour tous les tests
|
|
- Tous les tests passent
|
|
|
|
#### Tag final
|
|
```bash
|
|
git tag chat-server-uuid-migration-complete
|
|
```
|
|
|
|
### 4.2 Erreurs attendues et solutions
|
|
|
|
#### Erreur 1 : Type mismatch dans query_as!
|
|
|
|
```
|
|
error: type mismatch: expected `i64`, found `Uuid`
|
|
```
|
|
|
|
**Solution** : Vérifier que la struct ET la requête utilisent le même type. Utiliser `uuid as id` dans le SELECT.
|
|
|
|
#### Erreur 2 : Cannot convert i64 to Uuid
|
|
|
|
```
|
|
error: the trait `From<i64>` is not implemented for `Uuid`
|
|
```
|
|
|
|
**Solution** : Il reste du code qui utilise i64 — chercher avec `grep -r "i64" src/ | grep -v test`
|
|
|
|
#### Erreur 3 : Serde désérialisation échoue
|
|
|
|
```
|
|
error: invalid type: integer, expected a string
|
|
```
|
|
|
|
**Solution** : Le client envoie un number au lieu d'un string UUID. Vérifier le frontend ou accepter les deux formats temporairement.
|
|
|
|
#### Erreur 4 : SQLx compile-time check échoue
|
|
|
|
```
|
|
error: column "id" is of type uuid but expression is of type bigint
|
|
```
|
|
|
|
**Solution** : La requête SQL utilise encore un paramètre i64. Migrer vers Uuid.
|
|
|
|
---
|
|
|
|
## 5. Validation et tests
|
|
|
|
### 5.1 Tests de non-régression
|
|
|
|
#### Tests unitaires Rust
|
|
```bash
|
|
cd veza-chat-server
|
|
cargo test
|
|
```
|
|
|
|
#### Test d'intégration DB
|
|
```bash
|
|
# Vérifier que les requêtes fonctionnent avec la vraie DB
|
|
DATABASE_URL="postgres://..." cargo test --features integration
|
|
```
|
|
|
|
#### Test WebSocket manuel
|
|
```bash
|
|
# Avec websocat ou wscat
|
|
wscat -c ws://localhost:8080/ws
|
|
|
|
# Envoyer un message avec UUID
|
|
{"type": "join_room", "room": "general"}
|
|
{"type": "direct_message", "to_user_id": "550e8400-e29b-41d4-a716-446655440000", "content": "test"}
|
|
|
|
# Vérifier la réponse (doit contenir des UUID strings, pas des numbers)
|
|
```
|
|
|
|
#### Test intégration Backend Go ↔ Chat Server
|
|
```bash
|
|
# Depuis le backend Go, obtenir un token
|
|
curl -X GET http://localhost:8080/api/v1/chat/token \
|
|
-H "Authorization: Bearer <jwt_token>"
|
|
|
|
# Vérifier que le token contient un UUID (pas un int64)
|
|
```
|
|
|
|
#### Test Frontend
|
|
1. Ouvrir l'app web
|
|
2. Rejoindre un chat room
|
|
3. Envoyer un message
|
|
4. Vérifier dans la console réseau que les IDs sont des strings UUID
|
|
|
|
### 5.2 Checklist finale
|
|
|
|
#### Compilation
|
|
- [ ] `cargo build --release` passe sans warning
|
|
- [ ] `cargo clippy` passe sans erreur
|
|
- [ ] `cargo test` — tous les tests passent
|
|
|
|
#### Cohérence des types
|
|
- [ ] Aucun `i64` pour des IDs dans src/ (vérifier avec `grep -r "i64" src/ | grep -v test | grep -v limit | grep -v count`)
|
|
- [ ] Tous les champs ID sont de type `Uuid`
|
|
- [ ] Toutes les requêtes SQLx utilisent `Uuid`
|
|
|
|
#### Sérialisation JSON
|
|
- [ ] Les réponses JSON contiennent des UUID strings (pas des numbers)
|
|
- [ ] Les requêtes JSON acceptent des UUID strings
|
|
|
|
#### Intégration
|
|
- [ ] Le backend Go peut communiquer avec le chat-server
|
|
- [ ] Le frontend peut se connecter et envoyer/recevoir des messages
|
|
- [ ] Les IDs dans les messages WebSocket sont des strings
|
|
|
|
#### Documentation
|
|
- [ ] README mis à jour si nécessaire
|
|
- [ ] Commentaires de code à jour
|
|
|
|
---
|
|
|
|
## 6. Commandes d'exécution
|
|
|
|
```bash
|
|
# Étape 1 : Créer branche
|
|
git checkout -b fix/chat-server-uuid-migration
|
|
|
|
# Étape 2 : Tag de sauvegarde
|
|
git tag pre-uuid-migration-chat-server
|
|
|
|
# Étape 3 : Appliquer les modifications (voir sections 3.2)
|
|
|
|
# Étape 4 : Tester
|
|
cd veza-chat-server
|
|
cargo build --release
|
|
cargo test
|
|
|
|
# Étape 5 : Commit
|
|
git add .
|
|
git commit -m "refactor(chat-server): migrate all IDs from i64 to Uuid"
|
|
|
|
# Étape 6 : Tag final
|
|
git tag chat-server-uuid-migration-complete
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Questions à clarifier
|
|
|
|
### 7.1 Schéma DB - Tables sans UUID
|
|
|
|
**Problème** : Les tables `conversation_members`, `audit_logs`, `security_events` n'ont pas de colonne `uuid`.
|
|
|
|
**Options** :
|
|
1. **Ajouter des colonnes UUID** (migration DB) - Recommandé
|
|
2. **Garder BIGSERIAL** et convertir en UUID côté application - Moins idéal
|
|
|
|
**Recommandation** : Créer une migration pour ajouter des colonnes `uuid` à ces tables.
|
|
|
|
### 7.2 Backend Go - Handlers avec int64
|
|
|
|
**Problème** : `veza-backend-api/internal/api/handlers/chat_handlers.go` utilise encore `strconv.ParseInt` pour les room_id.
|
|
|
|
**Action** : Migrer aussi le backend Go (hors scope de ce rapport, mais à noter).
|
|
|
|
### 7.3 JWT Tokens - Format user_id
|
|
|
|
**Question** : Le JWT généré par le backend Go contient-il `user_id` en UUID ou int64 ?
|
|
|
|
**Action** : Vérifier dans `src/auth.rs` et `src/jwt_manager.rs` comment le JWT est parsé.
|
|
|
|
---
|
|
|
|
## 8. Résumé des modifications
|
|
|
|
### Fichiers à modifier (ordre de priorité)
|
|
|
|
1. 🔴 **Haute priorité** :
|
|
- `src/messages.rs` - WebSocket inbound messages
|
|
- `src/hub/channels.rs` - Structures et fonctions principales
|
|
- `src/hub/direct_messages.rs` - Direct messages
|
|
- `src/repository/room_repository.rs` - Repository layer
|
|
- `src/repository/message_repository.rs` - Repository layer
|
|
|
|
2. 🟡 **Moyenne priorité** :
|
|
- `src/hub/audit.rs` - Audit logs
|
|
- `src/services/room_service.rs` - Service layer
|
|
- `src/message_store.rs` - Message storage
|
|
- `src/websocket/handler.rs` - WebSocket handlers
|
|
- `src/websocket/broadcast.rs` - Broadcast messages
|
|
|
|
3. 🟢 **Basse priorité** :
|
|
- Tests unitaires
|
|
- Documentation
|
|
- Autres fichiers avec IDs
|
|
|
|
### Statistiques
|
|
|
|
- **Structs à migrer** : 10
|
|
- **Fonctions à modifier** : ~40
|
|
- **Requêtes SQL à mettre à jour** : ~60
|
|
- **Lignes de code à modifier** : ~500-800
|
|
- **Temps estimé** : 4-6 heures
|
|
|
|
---
|
|
|
|
**Document généré le** : 2025-01-27
|
|
**Prochaine étape** : Commencer la migration avec l'étape 1 (préparation)
|
|
|