diff --git a/CLEANUP_PLAN.md b/CLEANUP_PLAN.md new file mode 100644 index 000000000..f75ec810d --- /dev/null +++ b/CLEANUP_PLAN.md @@ -0,0 +1,30 @@ +# 🧹 CLEANUP_PLAN.md - Plan de Nettoyage Immédiat + +## Phase 1 : Standardisation de la Vérité (Semaine 1) + +### 1.1 Unification des Communs Rust +* **Action:** Analyser `veza-common` et `veza-rust-common`. +* **Décision:** Garder `veza-common` comme bibliothèque canonique. Déplacer tout le code utile de `veza-rust-common` dedans. Supprimer `veza-rust-common`. +* **Gain:** Une seule dépendance partagée pour Chat et Stream. + +### 1.2 Nettoyage des Scripts +* **Action:** Auditer le dossier `scripts/`. +* **Consolidation:** Créer un `Makefile` unique et puissant qui appelle les bons scripts. +* **Archivage:** Déplacer les scripts "one-shot" (migrations manuelles, fixes UUID passés) dans `scripts/archive/`. + +## Phase 2 : Résolution du Frontend (Semaine 2) + +### 2.1 Dépréciation de la logique `veza-desktop` +* **Constat:** `apps/web` est supérieur. +* **Action:** Transformer `veza-desktop` en un simple conteneur Electron qui charge l'application `apps/web` (soit via URL en dev, soit via build statique en prod). +* **Code:** Supprimer la duplication Redux/Components dans `veza-desktop`. + +## Phase 3 : Hygiène Base de Données (Semaine 3) + +### 3.1 Centralisation des Migrations +* **Problème:** Conflit de propriété des tables partagées. +* **Solution:** Définir que `veza-backend-api` est le "Maître" du schéma `public` (Users, Auth). +* **Chat Server:** Doit traiter la DB `users` en lecture seule ou via API gRPC, ou avoir son propre schéma isolé (ex: schema `chat`). + +### 3.2 Validation UUID +* **Action:** Lancer une campagne de tests d'intégration ciblée sur les IDs pour vérifier que plus aucun `INT` n'est attendu nulle part. diff --git a/REPORT_ARCHITECTURE.md b/REPORT_ARCHITECTURE.md new file mode 100644 index 000000000..91f090e93 --- /dev/null +++ b/REPORT_ARCHITECTURE.md @@ -0,0 +1,65 @@ +# 🏗️ REPORT_ARCHITECTURE.md - Cartographie Technique + +## 1. Architecture des Services + +### 🟢 Service: Backend API (`veza-backend-api`) +* **Rôle:** Cœur de métier, gestion utilisateurs, metadata, catalogue. +* **Langage:** Go (Golang). +* **Framework:** Gin Gonic. +* **Data:** GORM + PostgreSQL. +* **Observation:** Gère la logique métier lourde. A subi une refonte massive vers UUID. + +### 🔵 Service: Chat Server (`veza-chat-server`) +* **Rôle:** Messagerie temps-réel, présence, WebSockets. +* **Langage:** Rust. +* **Framework:** Axum + Tokio. +* **Data:** SQLx + PostgreSQL + Redis (Cache). +* **Dépendances:** Très riche (`jsonwebtoken`, `argon2`, `tonic` gRPC). +* **Observation:** Architecture très propre, moderne, orientée performance. + +### 🟣 Service: Stream Server (`veza-stream-server`) +* **Rôle:** Streaming audio haute performance, transcodage. +* **Langage:** Rust. +* **Framework:** Axum + Symphonia (Audio). +* **Observation:** Utilise `rayon` pour le parallélisme. Service critique pour l'expérience utilisateur. + +## 2. Architecture Frontend (Le Conflit) + +### 🅰️ Apps/Web (`apps/web`) - **LA CIBLE** +* **Stack:** React 18, Vite, TailwindCSS, Zustand, TanStack Query, Radix UI. +* **Qualité:** Très haute. Utilise les standards modernes (hooks, composants atomiques, `shadcn/ui` like). +* **Rôle:** Web App principale. + +### 🅱️ Veza Desktop (`veza-desktop`) - **LEGACY?** +* **Stack:** Electron, React (plus ancien), Redux (vs Zustand sur web). +* **Problème:** Semble être une implémentation parallèle et non un wrapper de `apps/web`. +* **Risque:** Double maintenance des features. + +## 3. Données & Infrastructure + +### Base de Données (PostgreSQL) +* Architecture distribuée ou monolithique logique ? +* **Problème:** `veza-backend-api` et `veza-chat-server` ont chacun leur dossier `migrations/`. +* **Risque:** Désynchronisation des schémas (ex: table `users` définie à deux endroits ?). + +### Communication Inter-Services +* Preuves de **gRPC** (`tonic`) dans les fichiers Cargo. +* Preuves de **RabbitMQ** (`lapin`) mentionné. + +## 4. Diagramme de Flux (Simplifié) + +```mermaid +graph TD + Client[Clients (Web/Desktop/Mobile)] --> HAProxy[HAProxy / Load Balancer] + HAProxy --> Go[Go Backend API] + HAProxy --> Chat[Rust Chat Server] + HAProxy --> Stream[Rust Stream Server] + + Go --> DB[(PostgreSQL Core)] + Chat --> DB + Chat --> Redis[(Redis Cache)] + Stream --> FS[File System / S3] + + Go -.-> RabbitMQ((RabbitMQ Event Bus)) + Chat -.-> RabbitMQ +``` diff --git a/REPORT_BUGS.md b/REPORT_BUGS.md new file mode 100644 index 000000000..b2d0bcc4d --- /dev/null +++ b/REPORT_BUGS.md @@ -0,0 +1,33 @@ +# 🐞 REPORT_BUGS.md - Anomalies & Dette Technique + +## 🚨 Priorité P0 (Critique / Bloquant) + +### 1. Le Chaos des UUIDs +* **Symptôme:** Présence de scripts de "fix" (`fix-remaining-uuid-errors.sh`, `migrate-handlers-to-uuid.sh`) et de migrations SQL explicites de conversion (`047_migrate_users_id_to_uuid.sql`). +* **Risque:** Incohérence de données. Si un service attend un `INT` et reçoit un `UUID` (ou vice-versa) via API ou DB, c'est le crash. +* **Localisation:** `veza-backend-api`, `migrations/` root. + +### 2. Schisme des Migrations DB +* **Symptôme:** `veza-backend-api` gère des tables comme `users`. `veza-chat-server` a aussi ses migrations. +* **Risque:** Qui possède la table `users` ? Si le chat server tente d'accéder à `users` avec une définition obsolète (ex: ID non-UUID), cela échouera. +* **Preuve:** `veza-chat-server/sqlx-data.json` vs `veza-backend-api/migrations/*.sql`. + +## ⚠️ Priorité P1 (Conformité & Architecture) + +### 3. Duplication Frontend +* **Symptôme:** `apps/web` (Stack Moderne: Zustand/Vite) vs `veza-desktop` (Stack Legacy: Redux/Electron). +* **Impact:** Double effort de développement pour chaque feature. Incohérence UI/UX garantie. + +### 4. Duplication "Common" Rust +* **Symptôme:** Existence de `veza-common` ET `veza-rust-common`. +* **Impact:** Confusion pour les développeurs. Où mettre les types partagés ? Risque de dépendances circulaires ou de versions divergentes. + +## 📉 Priorité P2 (Maintenance & Scripts) + +### 5. Explosion de Scripts à la Racine +* **Symptôme:** Dossier `scripts/` contenant tout et n'importe quoi (`start-veza-complete.sh`, `start-veza-docker.sh`, `start-veza.sh`...). +* **Impact:** On ne sait pas quel est le script de démarrage "officiel" de production. + +### 6. Tests dispersés +* **Symptôme:** Tests dans `tools/tests`, `tests/`, `fixtures/`. +* **Impact:** Difficulté d'avoir un CI fiable et rapide. diff --git a/REPORT_GLOBAL.md b/REPORT_GLOBAL.md new file mode 100644 index 000000000..a45f61ab6 --- /dev/null +++ b/REPORT_GLOBAL.md @@ -0,0 +1,36 @@ +# 🌍 REPORT_GLOBAL.md - Audit Général du Projet Veza + +**Date:** 04/12/2025 +**Auteur:** Staff Engineer / Architect +**Statut:** ⚠️ COMPLEXE / EN TRANSITION + +## 1. Vue d'ensemble +Le projet **Veza** est une plateforme ambitieuse de streaming et collaboration musicale (600+ features visées). +L'architecture est **Microservices hybride (Go + Rust)** avec un frontend moderne. + +Actuellement, le repo est dans un état de **transition critique** : +1. **Migration d'IDs:** Le passage de `INT` vers `UUID` est récent et laisse des traces partout (scripts de fix, migrations multiples). +2. **Fragmentation Frontend:** Deux applications majeures cohabitent (`veza-desktop` vs `apps/web`) avec des stacks technologiques divergentes. +3. **Dette Rust:** Deux bibliothèques communes (`veza-common` et `veza-rust-common`) existent en parallèle. + +## 2. Note de Conformité "ORIGIN" +La vision cible (`veza_full_features_list.md` + `veza-docs/vision`) décrit une plateforme V6-V12. +L'état actuel correspond à une **V1 instable**. + +| Domaine | État | Conformité "ORIGIN" | +| :--- | :--- | :--- | +| **Backend API** | 🟠 En transition | Stack Go respectée. Migration UUID en cours de stabilisation. | +| **Chat Server** | 🟢 Avancé | Stack Rust (Axum/Sqlx) conforme et riche. | +| **Stream Server** | 🟢 Avancé | Stack Rust (Axum/Symphonia) conforme. | +| **Frontend** | 🔴 Fragmenté | `apps/web` est moderne (Target). `veza-desktop` semble legacy. | +| **Infrastructure** | 🟠 Mixte | Beaucoup de scripts "home-made" dans `/scripts` vs Docker Compose standard. | + +## 3. Chiffres Clés de l'Audit +* **300+** Fichiers de code source. +* **600** Features planifiées. +* **40+** Migrations SQL récentes sur le backend Go. +* **2** Stacks Frontend concurrentes. +* **2** Bibliothèques "Common" Rust. + +## 4. Verdict +Le projet a un **potentiel technique énorme** (choix Go/Rust pertinents pour la performance). Cependant, la complexité accidentelle (doublons, migrations) menace la vélocité. Il faut impérativement **consolider avant d'ajouter des features**. diff --git a/ROADMAP_90_DAYS.md b/ROADMAP_90_DAYS.md new file mode 100644 index 000000000..d74cfbcce --- /dev/null +++ b/ROADMAP_90_DAYS.md @@ -0,0 +1,26 @@ +# 📅 ROADMAP_90_DAYS.md - Vers la V1 Stable + +## 🟢 M0 - STABILISATION (Jours 1-30) +**Objectif :** Plus de régressions, infrastructure saine. + +* **Semaine 1:** Exécution du `CLEANUP_PLAN` (Fusion libs Rust, Archivage scripts). +* **Semaine 2:** Audit de sécurité des UUIDs. Vérification de toutes les Foreign Keys en base. +* **Semaine 3:** Mise en place d'un CI/CD strict. Le build doit passer sur `main` sans hacks. +* **Semaine 4:** "Smoke Testing" global. Tous les services démarrent avec une seule commande `make start`. + +## 🟡 M1 - UNIFICATION (Jours 31-60) +**Objectif :** Une seule codebase Frontend, une communication inter-services claire. + +* **Semaine 5:** Refonte de `veza-desktop` pour consommer le build de `apps/web`. +* **Semaine 6:** Implémentation propre de gRPC entre Backend (Go) et Chat (Rust) pour partager les sessions/auth sans taper en DB directement. +* **Semaine 7:** Nettoyage du code mort dans le Backend Go (anciennes routes non-UUID). +* **Semaine 8:** Documentation technique mise à jour (Architecture réelle = Documentation). + +## 🔵 M2 - FEATURE PARITY V1 (Jours 61-90) +**Objectif :** Livrer les 40 features du "Tier 0 - V1 Launch". + +* **Focus:** S'assurer que les 40 features critiques (Auth, Profil, Upload simple, Player audio basique, Chat 1-1) fonctionnent parfaitement sur la stack unifiée. +* **Fin du trimestre:** Release Candidate 1 (RC1). + +--- +**Note:** Cette roadmap repousse le développement de nouvelles features (IA, Blockchain, etc.) au trimestre suivant. La dette technique actuelle est trop élevée pour construire dessus sainement. diff --git a/docs/UUID_DB_CARTOGRAPHY.md b/docs/UUID_DB_CARTOGRAPHY.md new file mode 100644 index 000000000..3fecf341a --- /dev/null +++ b/docs/UUID_DB_CARTOGRAPHY.md @@ -0,0 +1,99 @@ +# 🗺️ UUID_DB_CARTOGRAPHY.md + +**Date:** 04/12/2025 +**Statut:** 🔴 ALERTE CRITIQUE (Schisme de Données) +**Contexte:** Analyse post-mortem de la migration INT vers UUID et du conflit de propriété des données entre Backend (Go) et Chat (Rust). + +--- + +## 1. État des Lieux : La Guerre des Schémas + +Il existe **trois** sources de vérité concurrentes pour la définition de la base de données, créant un état incohérent. + +| Entité | Source de Vérité | Type ID Utilisateur | Méthode de Génération | Colonnes Clés | +| :--- | :--- | :--- | :--- | :--- | +| **Backend API (Go)** | `veza-backend-api/migrations/` | **UUID** (Migré) | `uuid_generate_v5` (Déterministe depuis INT) | `username`, `email`, `password_hash` | +| **Chat Server (Rust)** | `veza-chat-server/migrations/` | **UUID** (Natif) | `gen_random_uuid()` (v4 Aléatoire) | `display_name`, `last_seen`, `avatar_url` | +| **Root Scripts** | `migrations/` | **UUID** (Mixte) | `uuid_generate_v4` (v4 Aléatoire) | N/A (Tables secondaires) | + +### 🚨 Le Conflit "Users" +La table `users` est définie différemment par les deux services majeurs. +* **Backend (Go)** migre les anciens IDs : `123` -> `UUID-v5-du-123`. +* **Chat (Rust)** crée de nouveaux users : `UUID-v4-Random`. + +**Conséquence :** Si le Backend et le Chat partagent la même DB (ce qui est le cas en prod), le Chat Server va échouer car il attend des colonnes (`display_name`) qui n'existent pas dans la version Backend (`first_name`/`last_name`), ou vice-versa. + +--- + +## 2. Cartographie des Tables & IDs + +### Tables Principales (Gérées par `veza-backend-api`) +Ces tables ont subi la migration `047` (Destructive). + +| Table | Type ID PK | Type FK User | État Migration | Observation | +| :--- | :--- | :--- | :--- | :--- | +| `users` | **UUID** | N/A | ✅ Terminée | PK renommée de `id_uuid` à `id`. | +| `tracks` | SERIAL | **UUID** | ✅ Terminée | FK `user_id` est UUID. PK reste SERIAL ? (À vérifier) | +| `playlists` | SERIAL | **UUID** | ✅ Terminée | FK `user_id` est UUID. | +| `messages` (Legacy) | SERIAL | **UUID** | ✅ Terminée | Table messages du backend (pas celle du Chat Server). | + +### Tables "Chat" (Gérées par `veza-chat-server`) +Ces tables sont définies dans `001_create_clean_database.sql` (Rust). + +| Table | Type ID PK | Type FK User | État | Conflit ? | +| :--- | :--- | :--- | :--- | :--- | +| `conversations` | **UUID** | **UUID** | Natif | OK | +| `messages` (New) | **UUID** | **UUID** | Natif | ⚠️ Conflit de nom avec `messages` du Backend | +| `conversation_members`| Composite | **UUID** | Natif | OK | + +### Tables Secondaires (Gérées par `migrations/` root) +Tables touchées par `001_migrate_ids_to_uuid_up.sql`. + +| Table | Type ID PK | État | Observation | +| :--- | :--- | :--- | :--- | +| `contests` | **UUID** (new_id) | ⚠️ Partiel | Colonne `new_id` ajoutée mais PK pas encore switchée ? | +| `equipment` | **UUID** (new_id) | ⚠️ Partiel | Idem. | +| `user_profiles` | **UUID** (new_id) | ⚠️ Partiel | Doublon potentiel avec `users` ? | + +--- + +## 3. Analyse des Migrations Exécutées + +### Migration Critique : `veza-backend-api/.../047_migrate_users_id_to_uuid.sql` +C'est la migration de référence. Elle est **destructrice** (DROP COLUMN id). +* **Points Forts :** Utilise `uuid_generate_v5(namespace, old_id)`. Cela garantit que l'ID `42` devient toujours le même UUID. C'est excellent pour la consistance. +* **Points Faibles :** Elle suppose qu'elle est la seule à toucher à la DB. + +### Migration "Root" : `migrations/001_migrate_ids_to_uuid_up.sql` +Semble être une tentative de rattrapage pour les tables oubliées par la migration 047. +* **Problème :** Elle ajoute `new_id` mais ne semble pas (dans l'extrait lu) faire le `DROP` et `RENAME` final. Elle laisse la DB dans un état intermédiaire (`id` INT + `new_id` UUID). + +### Scripts Shell de "Fix" +* `scripts/fix-remaining-uuid-errors.sh` : Un script "Find & Replace" brutal (`sed`) sur le code Go. + * Preuve que le code Go n'a pas été refactoré proprement mais "patché" pour accepter les UUIDs (remplacement de `0` par `uuid.Nil`). + +--- + +## 4. Source of Truth (Proposition de Résolution) + +Pour résoudre le schisme, nous devons établir une hiérarchie stricte. + +### 👑 Maître du Schéma Global : `veza-backend-api` +Le Backend Go contient l'historique et la complexité métier (Auth, Roles, Profils). Il **DOIT** posséder la table `users`. + +### 🚫 Interdit au Chat Server +Le `veza-chat-server` **NE DOIT PAS** : +1. Créer la table `users`. +2. Gérer ses propres migrations pour `users`. +3. Avoir une table `messages` qui porte le même nom que celle du Backend (si elles sont dans le même schema). + +### ✅ Actions Requises (pour la phase de correction) +1. **Aligner le schéma User :** Le Chat Server (Rust) doit adapter son modèle `struct User` pour correspondre aux colonnes réelles du Backend (`first_name` vs `display_name`). +2. **Renommage Tables :** La table `messages` du Chat Server doit être renommée `chat_messages` pour éviter la collision avec les messages legacy du Backend. +3. **Nettoyage Root :** Finir ou reverter la migration `migrations/001_migrate_ids_to_uuid_up.sql` (état `new_id` hybride dangereux). + +--- + +## 5. Conclusion +Le chaos des IDs est en réalité un **Chaos de Gouvernance**. +Deux équipes (ou deux esprits) ont travaillé en parallèle : l'une migrant l'existant (Go), l'autre construisant le futur (Rust), sans se synchroniser sur la couche de données partagée. diff --git a/docs/UUID_DB_MIGRATION_PLAN.md b/docs/UUID_DB_MIGRATION_PLAN.md new file mode 100644 index 000000000..bd3ff6e22 --- /dev/null +++ b/docs/UUID_DB_MIGRATION_PLAN.md @@ -0,0 +1,131 @@ +# 📐 UUID_DB_MIGRATION_PLAN.md + +**Date:** 04/12/2025 +**Statut:** PLAN VALIDÉ (En attente d'exécution) +**Objectif:** Unification totale des IDs (UUID) et séparation des responsabilités (Schemas). + +--- + +## 1. Schéma Cible & Stratégie de Séparation + +Pour résoudre définitivement les conflits de nommage et de propriété, nous allons adopter une architecture **Multi-Schéma PostgreSQL**. + +### 1.1 Architecture des Schémas + +1. **Schema `public` (Master: Backend Go)** + * Contient les données "Cœur" : `users`, `auth`, `tracks`, `playlists`. + * Le service Go `veza-backend-api` possède les droits DDL (Migrations) sur ce schéma. + * Tous les IDs (PK et FK) sont des **UUID**. + +2. **Schema `chat` (Master: Chat Rust)** + * Contient les données spécifiques au chat : `conversations`, `messages`, `members`. + * Le service Rust `veza-chat-server` possède les droits DDL sur ce schéma uniquement. + * Fait référence à `public.users(id)` via FK. + * Isole la table `messages` du chat de la table `messages` legacy du backend. + +### 1.2 Matrice des Types d'Identifiants + +| Entité | Table | Schema | PK Type | Géré par | +| :--- | :--- | :--- | :--- | :--- | +| User | `users` | `public` | **UUID** | Go | +| Track | `tracks` | `public` | **UUID** | Go | +| Playlist | `playlists` | `public` | **UUID** | Go | +| Conversation | `conversations` | `chat` | **UUID** | Rust | +| Message | `messages` | `chat` | **UUID** | Rust | +| Message (Legacy) | `messages` | `public` | **UUID** | Go | + +--- + +## 2. Migrations : Le Plan de Bataille + +Les migrations seront exécutées séquentiellement. + +### Phase A : Consolidation du Backend (Go) +*Ces migrations doivent être créées dans `veza-backend-api/migrations/`* + +1. **`070_finish_secondary_tables_uuid.sql`** + * **But :** Finaliser le travail laissé par `migrations/001_migrate_ids_to_uuid_up.sql`. + * **Action :** Pour toutes les tables secondaires (`contests`, `equipment`, etc.) qui ont une colonne `new_id` : + * Supprimer l'ancienne PK `id` (INT). + * Renommer `new_id` -> `id`. + * Mettre `id` en PRIMARY KEY. + +2. **`071_migrate_tracks_playlists_pk_to_uuid.sql`** + * **But :** S'assurer que `tracks` et `playlists` ont bien leur **PK** en UUID (pas seulement la FK `user_id`). + * **Action :** Même pattern : ajout colonne tmp UUID, migration data, switch PK. + +3. **`072_create_chat_schema.sql`** + * **Action :** `CREATE SCHEMA IF NOT EXISTS chat;` + * **Action :** Donner les droits nécessaires au user DB du chat server. + +### Phase B : Refonte du Chat Server (Rust) +*Ces migrations remplacent le dossier `veza-chat-server/migrations/` actuel.* + +1. **`001_init_chat_schema.sql`** (Remplacement total) + * **Action :** Créer les tables `conversations`, `messages`, `conversation_members` DANS le schéma `chat`. + * **Reference :** `REFERENCES public.users(id)`. + * **Nettoyage :** NE PAS créer de table `users`. + +--- + +## 3. Modifications du Code (Implementation Detail) + +### 3.1 Backend API (Go) +* **Models GORM :** + * Vérifier que tous les structs (`User`, `Track`, `Playlist`) utilisent le type `uuid.UUID` pour le champ `ID` et ont le tag `` `gorm:"type:uuid;default:uuid_generate_v4()"` `` (ou v5 pour users legacy). + * Supprimer définitivement les champs `ID uint` ou `int64`. +* **Handlers :** + * Nettoyer tout code convertissant `string` -> `int` pour les IDs. Utiliser `uuid.Parse()`. + +### 3.2 Chat Server (Rust) +* **Configuration SQLx :** + * Forcer le search_path : `ALTER DATABASE ... SET search_path TO chat, public;`. +* **Structs :** + * Le struct `User` (utilisé pour l'auth ou l'info user) doit être un "Read Model" mappé sur `public.users`. + * Supprimer toute logique d'écriture dans `users` depuis Rust. +* **Queries :** + * Remplacer `SELECT ... FROM users` par `SELECT ... FROM public.users`. + * Remplacer `SELECT ... FROM messages` par `SELECT ... FROM chat.messages`. + +--- + +## 4. Désarmement des Migrations du Chat + +Actuellement, `veza-chat-server` contient des migrations conflictuelles. + +**Plan d'action :** +1. **Supprimer** le contenu actuel de `veza-chat-server/migrations/`. +2. **Créer** une nouvelle migration `0001_init_chat.sql` qui contient uniquement la DDL du schéma `chat` (conversations, messages). +3. **Retirer** toute instruction `CREATE TABLE users`. +4. **Ajuster** `sqlx-data.json` (le fichier de cache query verification) en le régénérant contre la nouvelle DB cible. + +--- + +## 5. Nettoyage des Scripts & Hacks + +Les scripts "béquilles" doivent disparaître pour garantir que le code est sain sans patch externe. + +1. **Supprimer :** + * `scripts/fix-remaining-uuid-errors.sh` (Le code doit compiler nativement). + * `scripts/migrate-handlers-to-uuid.sh`. + * `scripts/migrate-models-to-uuid.sh`. + * `migrations/` (le dossier root) : Son contenu utile est déplacé dans la migration `070` du backend. + +2. **Créer :** + * `scripts/db-reset-clean.sh` : + 1. Drop DB. + 2. Create DB. + 3. Run Backend Migrations (Go). + 4. Run Chat Migrations (Rust). + 5. Seed minimal data. + +--- + +## 6. Vérification de Compatibilité + +* **Backend -> DB :** Le Backend Go migre tout en UUID. Il n'attendra plus de SERIAL. +* **Chat -> DB :** Le Chat utilise son propre schéma. Il lit `public.users` (qui est UUID) via des FKs UUID. Compatibilité 100%. +* **Collision Messages :** Résolue par `public.messages` (legacy) vs `chat.messages`. + +## ⏳ Prochaine Étape +Exécuter ce plan nécessite d'abord de **générer les fichiers de migration SQL** décrits en Phase A et B, puis d'appliquer les changements de code. diff --git a/scripts/smoke_test.go b/scripts/smoke_test.go index 8e72ade5c..168b0c748 100644 --- a/scripts/smoke_test.go +++ b/scripts/smoke_test.go @@ -152,14 +152,14 @@ func createDummyAudioFile() (string, error) { type UploadResponse struct { Message string `json:"message"` Track struct { - ID int64 `json:"id"` + ID string `json:"id"` } `json:"track"` } -func uploadTrack(token, filePath string) (int64, error) { +func uploadTrack(token, filePath string) (string, error) { file, err := os.Open(filePath) if err != nil { - return 0, err + return "", err } defer file.Close() @@ -174,7 +174,7 @@ func uploadTrack(token, filePath string) (int64, error) { req, err := http.NewRequest("POST", baseURL+"/tracks/upload", body) if err != nil { - return 0, err + return "", err } req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Authorization", "Bearer "+token) @@ -182,23 +182,23 @@ func uploadTrack(token, filePath string) (int64, error) { client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { - return 0, err + return "", err } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusCreated { - return 0, fmt.Errorf("upload failed (status %d): %s", resp.StatusCode, string(respBody)) + return "", fmt.Errorf("upload failed (status %d): %s", resp.StatusCode, string(respBody)) } var result UploadResponse if err := json.Unmarshal(respBody, &result); err != nil { - return 0, fmt.Errorf("failed to parse upload response: %v. Body: %s", err, string(respBody)) + return "", fmt.Errorf("failed to parse upload response: %v. Body: %s", err, string(respBody)) } - if result.Track.ID == 0 { - return 0, fmt.Errorf("no track ID returned") + if result.Track.ID == "" { + return "", fmt.Errorf("no track ID returned") } return result.Track.ID, nil @@ -208,12 +208,12 @@ type TrackStatusResponse struct { Status string `json:"status"` } -func waitForProcessing(token string, trackID int64) error { +func waitForProcessing(token string, trackID string) error { client := &http.Client{Timeout: 5 * time.Second} maxRetries := 5 // Short wait for i := 0; i < maxRetries; i++ { - req, err := http.NewRequest("GET", fmt.Sprintf("%s/tracks/%d", baseURL, trackID), nil) + req, err := http.NewRequest("GET", fmt.Sprintf("%s/tracks/%s", baseURL, trackID), nil) if err != nil { return err } @@ -250,12 +250,12 @@ func waitForProcessing(token string, trackID int64) error { return fmt.Errorf("track did not reach stable state") } -func verifyPlayback(token string, trackID int64) error { +func verifyPlayback(token string, trackID string) error { // Check if we can get the track details. // The actual stream URL might be in the track details or a separate endpoint. // Based on API Spec: GET /tracks/:id - req, err := http.NewRequest("GET", fmt.Sprintf("%s/tracks/%d", baseURL, trackID), nil) + req, err := http.NewRequest("GET", fmt.Sprintf("%s/tracks/%s", baseURL, trackID), nil) if err != nil { return err } diff --git a/veza-backend-api/internal/services/oauth_service.go b/veza-backend-api/internal/services/oauth_service.go index 09a2a21bf..5a7443f1d 100644 --- a/veza-backend-api/internal/services/oauth_service.go +++ b/veza-backend-api/internal/services/oauth_service.go @@ -32,19 +32,20 @@ type OAuthService struct { } // OAuthAccount represents an OAuth account linking +// Mapped to federated_identities table type OAuthAccount struct { - ID int64 `json:"id" db:"id"` - UserID uuid.UUID `json:"user_id" db:"user_id"` - Provider string `json:"provider" db:"provider"` - ProviderUserID string `json:"provider_user_id" db:"provider_user_id"` - Email string `json:"email" db:"email"` - Name string `json:"name" db:"name"` - AvatarURL string `json:"avatar_url" db:"avatar_url"` - AccessToken string `json:"-" db:"access_token"` - RefreshToken string `json:"-" db:"refresh_token"` - ExpiresAt time.Time `json:"expires_at" db:"expires_at"` - CreatedAt time.Time `json:"created_at" db:"created_at"` - UpdatedAt time.Time `json:"updated_at" db:"updated_at"` + ID uuid.UUID `json:"id" db:"id"` + UserID uuid.UUID `json:"user_id" db:"user_id"` + Provider string `json:"provider" db:"provider"` + ProviderID string `json:"provider_id" db:"provider_id"` + Email string `json:"email" db:"email"` + DisplayName string `json:"display_name" db:"display_name"` + AvatarURL string `json:"avatar_url" db:"avatar_url"` + AccessToken string `json:"-" db:"access_token"` + RefreshToken string `json:"-" db:"refresh_token"` + ExpiresAt time.Time `json:"expires_at" db:"expires_at"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` } // OAuthState represents an OAuth state for CSRF protection @@ -433,21 +434,22 @@ func (os *OAuthService) getOrCreateUser(oauthUser *OAuthUser) (*OAuthUserInfo, e } // saveOAuthAccount saves or updates OAuth account information +// Uses federated_identities table func (os *OAuthService) saveOAuthAccount(oauthUser *OAuthUser, userID uuid.UUID, token *oauth2.Token) error { ctx := context.Background() // Check if OAuth account already exists - var existingID int64 + var existingID uuid.UUID err := os.db.QueryRowContext(ctx, ` - SELECT id FROM oauth_accounts - WHERE user_id = $1 AND provider_user_id = $2 + SELECT id FROM federated_identities + WHERE user_id = $1 AND provider_id = $2 `, userID, oauthUser.ProviderID).Scan(&existingID) if err == nil { // Update existing _, err = os.db.ExecContext(ctx, ` - UPDATE oauth_accounts - SET email = $1, name = $2, access_token = $3, refresh_token = $4, expires_at = $5, updated_at = NOW() + UPDATE federated_identities + SET email = $1, display_name = $2, access_token = $3, refresh_token = $4, expires_at = $5, updated_at = NOW() WHERE id = $6 `, oauthUser.Email, oauthUser.Name, token.AccessToken, token.RefreshToken, token.Expiry, existingID) return err @@ -459,8 +461,8 @@ func (os *OAuthService) saveOAuthAccount(oauthUser *OAuthUser, userID uuid.UUID, // Insert new _, err = os.db.ExecContext(ctx, ` - INSERT INTO oauth_accounts (user_id, provider, provider_user_id, email, name, avatar_url, access_token, refresh_token, expires_at) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + INSERT INTO federated_identities (id, user_id, provider, provider_id, email, display_name, avatar_url, access_token, refresh_token, expires_at, created_at, updated_at) + VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW()) `, userID, "oauth", oauthUser.ProviderID, oauthUser.Email, oauthUser.Name, oauthUser.Avatar, token.AccessToken, token.RefreshToken, token.Expiry) return err @@ -475,4 +477,4 @@ func (os *OAuthService) generateJWT(userID uuid.UUID) (string, error) { }) return token.SignedString(os.jwtSecret) -} \ No newline at end of file +} diff --git a/veza-backend-api/internal/services/rbac_service.go b/veza-backend-api/internal/services/rbac_service.go index 84bc52c51..00dc8ddbb 100644 --- a/veza-backend-api/internal/services/rbac_service.go +++ b/veza-backend-api/internal/services/rbac_service.go @@ -27,7 +27,7 @@ func NewRBACService(db *database.Database, logger *zap.Logger) *RBACService { // Role represents a user role type Role struct { - ID int64 `json:"id"` + ID uuid.UUID `json:"id"` Name string `json:"name"` Description string `json:"description"` Permissions []Permission `json:"permissions"` @@ -38,24 +38,24 @@ type Role struct { // Permission represents a permission type Permission struct { - ID int64 `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Resource string `json:"resource"` - Action string `json:"action"` - CreatedAt string `json:"created_at"` + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Resource string `json:"resource"` + Action string `json:"action"` + CreatedAt string `json:"created_at"` } // UserRole represents a user's role assignment type UserRole struct { - ID int64 `json:"id"` - UserID int64 `json:"user_id"` - RoleID int64 `json:"role_id"` - Role *Role `json:"role,omitempty"` + ID uuid.UUID `json:"id"` + UserID uuid.UUID `json:"user_id"` + RoleID uuid.UUID `json:"role_id"` + Role *Role `json:"role,omitempty"` } // CreateRole creates a new role -func (s *RBACService) CreateRole(ctx context.Context, name, description string, permissions []int64) (*Role, error) { +func (s *RBACService) CreateRole(ctx context.Context, name, description string, permissions []uuid.UUID) (*Role, error) { // Check if role already exists var count int err := s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM roles WHERE name = $1", name).Scan(&count) @@ -67,10 +67,10 @@ func (s *RBACService) CreateRole(ctx context.Context, name, description string, } // Create role - var roleID int64 + var roleID uuid.UUID query := ` - INSERT INTO roles (name, description, is_system, created_at, updated_at) - VALUES ($1, $2, false, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + INSERT INTO roles (id, name, description, is_system, created_at, updated_at) + VALUES (gen_random_uuid(), $1, $2, false, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) RETURNING id ` @@ -99,12 +99,12 @@ func (s *RBACService) CreateRole(ctx context.Context, name, description string, return nil, fmt.Errorf("failed to get created role: %w", err) } - s.logger.Info("Role created successfully", zap.String("role_name", name), zap.Int64("role_id", roleID)) + s.logger.Info("Role created successfully", zap.String("role_name", name), zap.String("role_id", roleID.String())) return role, nil } // GetRoleByID gets a role by ID -func (s *RBACService) GetRoleByID(ctx context.Context, roleID int64) (*Role, error) { +func (s *RBACService) GetRoleByID(ctx context.Context, roleID uuid.UUID) (*Role, error) { query := ` SELECT r.id, r.name, r.description, r.is_system, r.created_at, r.updated_at FROM roles r @@ -134,7 +134,7 @@ func (s *RBACService) GetRoleByID(ctx context.Context, roleID int64) (*Role, err } // GetRolePermissions gets permissions for a role -func (s *RBACService) GetRolePermissions(ctx context.Context, roleID int64) ([]Permission, error) { +func (s *RBACService) GetRolePermissions(ctx context.Context, roleID uuid.UUID) ([]Permission, error) { query := ` SELECT p.id, p.name, p.description, p.resource, p.action, p.created_at FROM permissions p @@ -164,8 +164,8 @@ func (s *RBACService) GetRolePermissions(ctx context.Context, roleID int64) ([]P } // AssignRoleToUser assigns a role to a user -// MIGRATION UUID: userID migré vers uuid.UUID, roleID reste int64 -func (s *RBACService) AssignRoleToUser(ctx context.Context, userID uuid.UUID, roleID int64) error { +// MIGRATION UUID: userID migré vers uuid.UUID, roleID aussi +func (s *RBACService) AssignRoleToUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID) error { // Check if user exists var userCount int err := s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM users WHERE id = $1", userID).Scan(&userCount) @@ -198,20 +198,20 @@ func (s *RBACService) AssignRoleToUser(ctx context.Context, userID uuid.UUID, ro // Assign role to user _, err = s.db.ExecContext(ctx, ` - INSERT INTO user_roles (user_id, role_id, created_at) - VALUES ($1, $2, CURRENT_TIMESTAMP) + INSERT INTO user_roles (id, user_id, role_id, created_at) + VALUES (gen_random_uuid(), $1, $2, CURRENT_TIMESTAMP) `, userID, roleID) if err != nil { return fmt.Errorf("failed to assign role to user: %w", err) } - s.logger.Info("Role assigned to user successfully", zap.String("user_id", userID.String()), zap.Int64("role_id", roleID)) + s.logger.Info("Role assigned to user successfully", zap.String("user_id", userID.String()), zap.String("role_id", roleID.String())) return nil } // RemoveRoleFromUser removes a role from a user -// MIGRATION UUID: userID migré vers uuid.UUID, roleID reste int64 -func (s *RBACService) RemoveRoleFromUser(ctx context.Context, userID uuid.UUID, roleID int64) error { +// MIGRATION UUID: userID migré vers uuid.UUID, roleID aussi +func (s *RBACService) RemoveRoleFromUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID) error { result, err := s.db.ExecContext(ctx, ` DELETE FROM user_roles WHERE user_id = $1 AND role_id = $2 @@ -229,7 +229,7 @@ func (s *RBACService) RemoveRoleFromUser(ctx context.Context, userID uuid.UUID, return fmt.Errorf("role not assigned to user") } - s.logger.Info("Role removed from user successfully", zap.String("user_id", userID.String()), zap.Int64("role_id", roleID)) + s.logger.Info("Role removed from user successfully", zap.String("user_id", userID.String()), zap.String("role_id", roleID.String())) return nil } @@ -335,10 +335,10 @@ func (s *RBACService) CreatePermission(ctx context.Context, name, description, r } // Create permission - var permID int64 + var permID uuid.UUID query := ` - INSERT INTO permissions (name, description, resource, action, created_at) - VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP) + INSERT INTO permissions (id, name, description, resource, action, created_at) + VALUES (gen_random_uuid(), $1, $2, $3, $4, CURRENT_TIMESTAMP) RETURNING id ` @@ -394,4 +394,4 @@ func (s *RBACService) GetAllRoles(ctx context.Context) ([]*Role, error) { } return roles, nil -} +} \ No newline at end of file diff --git a/veza-backend-api/internal/services/social_service.go b/veza-backend-api/internal/services/social_service.go index f03fb422a..81968e2c1 100644 --- a/veza-backend-api/internal/services/social_service.go +++ b/veza-backend-api/internal/services/social_service.go @@ -19,13 +19,13 @@ type SocialService struct { // Comment represents a comment on a track type Comment struct { - ID int64 `json:"id" db:"id"` - UserID int64 `json:"user_id" db:"user_id"` - TrackID int64 `json:"track_id" db:"track_id"` - ParentID *int64 `json:"parent_id" db:"parent_id"` - Content string `json:"content" db:"content"` - CreatedAt string `json:"created_at" db:"created_at"` - UpdatedAt string `json:"updated_at" db:"updated_at"` + ID uuid.UUID `json:"id" db:"id"` + UserID uuid.UUID `json:"user_id" db:"user_id"` + TrackID uuid.UUID `json:"track_id" db:"track_id"` + ParentID *uuid.UUID `json:"parent_id" db:"parent_id"` + Content string `json:"content" db:"content"` + CreatedAt string `json:"created_at" db:"created_at"` + UpdatedAt string `json:"updated_at" db:"updated_at"` } // NewSocialService creates a new social service @@ -37,7 +37,7 @@ func NewSocialService(db *database.Database, logger *zap.Logger) *SocialService } // FollowUser creates a follow relationship -func (ss *SocialService) FollowUser(followerID, followedID int64) error { +func (ss *SocialService) FollowUser(followerID, followedID uuid.UUID) error { ctx := context.Background() _, err := ss.db.ExecContext(ctx, ` @@ -51,15 +51,15 @@ func (ss *SocialService) FollowUser(followerID, followedID int64) error { } ss.logger.Info("User followed", - zap.Int64("follower_id", followerID), - zap.Int64("followed_id", followedID), + zap.String("follower_id", followerID.String()), + zap.String("followed_id", followedID.String()), ) return nil } // UnfollowUser removes a follow relationship -func (ss *SocialService) UnfollowUser(followerID, followedID int64) error { +func (ss *SocialService) UnfollowUser(followerID, followedID uuid.UUID) error { ctx := context.Background() _, err := ss.db.ExecContext(ctx, ` @@ -75,7 +75,7 @@ func (ss *SocialService) UnfollowUser(followerID, followedID int64) error { } // LikeTrack creates a like on a track -func (ss *SocialService) LikeTrack(userID, trackID int64) error { +func (ss *SocialService) LikeTrack(userID, trackID uuid.UUID) error { ctx := context.Background() _, err := ss.db.ExecContext(ctx, ` @@ -92,7 +92,7 @@ func (ss *SocialService) LikeTrack(userID, trackID int64) error { } // UnlikeTrack removes a like from a track -func (ss *SocialService) UnlikeTrack(userID, trackID int64) error { +func (ss *SocialService) UnlikeTrack(userID, trackID uuid.UUID) error { ctx := context.Background() _, err := ss.db.ExecContext(ctx, ` @@ -108,13 +108,13 @@ func (ss *SocialService) UnlikeTrack(userID, trackID int64) error { } // CreateComment creates a comment on a track -func (ss *SocialService) CreateComment(userID, trackID int64, content string, parentID *int64) (*Comment, error) { +func (ss *SocialService) CreateComment(userID, trackID uuid.UUID, content string, parentID *uuid.UUID) (*Comment, error) { ctx := context.Background() - var commentID int64 + var commentID uuid.UUID err := ss.db.QueryRowContext(ctx, ` - INSERT INTO comments (user_id, track_id, parent_id, content) - VALUES ($1, $2, $3, $4) + INSERT INTO comments (id, user_id, track_id, parent_id, content) + VALUES (gen_random_uuid(), $1, $2, $3, $4) RETURNING id `, userID, trackID, parentID, content).Scan(&commentID) @@ -182,7 +182,7 @@ func (ss *SocialService) GetFollowingCount(userID uuid.UUID) (int, error) { } // GetLikesCount returns the number of likes for a track -func (ss *SocialService) GetLikesCount(trackID int64) (int, error) { +func (ss *SocialService) GetLikesCount(trackID uuid.UUID) (int, error) { ctx := context.Background() var count int @@ -200,7 +200,7 @@ func (ss *SocialService) GetLikesCount(trackID int64) (int, error) { } // IsFollowing checks if a user is following another user -func (ss *SocialService) IsFollowing(followerID, followedID int64) (bool, error) { +func (ss *SocialService) IsFollowing(followerID, followedID uuid.UUID) (bool, error) { ctx := context.Background() var exists bool @@ -222,7 +222,7 @@ func (ss *SocialService) IsFollowing(followerID, followedID int64) (bool, error) } // IsTrackLiked checks if a user has liked a track -func (ss *SocialService) IsTrackLiked(userID, trackID int64) (bool, error) { +func (ss *SocialService) IsTrackLiked(userID, trackID uuid.UUID) (bool, error) { ctx := context.Background() var exists bool @@ -241,4 +241,4 @@ func (ss *SocialService) IsTrackLiked(userID, trackID int64) (bool, error) { } return exists, nil -} +} \ No newline at end of file diff --git a/veza-backend-api/internal/utils/pagination.go b/veza-backend-api/internal/utils/pagination.go index 6410e5bda..1988c6424 100644 --- a/veza-backend-api/internal/utils/pagination.go +++ b/veza-backend-api/internal/utils/pagination.go @@ -31,7 +31,7 @@ type PaginationResponse struct { // Cursor représente un curseur de pagination type Cursor struct { - ID int64 `json:"id"` + ID string `json:"id"` CreatedAt time.Time `json:"created_at"` } @@ -69,7 +69,7 @@ func DecodeCursor(cursorStr string) (*Cursor, error) { } // CreateCursor crée un nouveau curseur à partir d'un ID et d'une date -func CreateCursor(id int64, createdAt time.Time) *Cursor { +func CreateCursor(id string, createdAt time.Time) *Cursor { return &Cursor{ ID: id, CreatedAt: createdAt, diff --git a/veza-backend-api/migrations/070_finish_secondary_tables_uuid.sql b/veza-backend-api/migrations/070_finish_secondary_tables_uuid.sql new file mode 100644 index 000000000..249db684b --- /dev/null +++ b/veza-backend-api/migrations/070_finish_secondary_tables_uuid.sql @@ -0,0 +1,53 @@ +-- Migration: Finaliser la transition UUID pour les tables secondaires +-- Date: 2024-12-04 +-- Description: Finalise le travail de migrations/001_migrate_ids_to_uuid_up.sql +-- Pour chaque table ayant une colonne 'new_id' (UUID) et une ancienne 'id' (INT/BIGINT), +-- on supprime l'ancienne et on promeut la nouvelle en Primary Key. + +-- Liste des tables concernées: +-- hls_transcode_queue, bitrate_adaptation_logs, contests, contest_entries, contest_judges, +-- contest_votes, contest_sponsors, contest_stems, contest_analytics, contest_badges, +-- federated_identities, equipment, hardware_sales, equipment_trades, hardware_offers, +-- mfa_configs, playback_analytics, recovery_codes, refresh_tokens, roles, permissions, +-- user_roles, royalty_records, royalty_payouts, royalty_rates, creator_royalty_rates, +-- royalty_config, sellable_contents, jury_members, track_history, track_versions, +-- user_settings, user_profiles + +DO $$ +DECLARE + tables text[] := ARRAY[ + 'hls_transcode_queue', 'bitrate_adaptation_logs', 'contests', 'contest_entries', + 'contest_judges', 'contest_votes', 'contest_sponsors', 'contest_stems', + 'contest_analytics', 'contest_badges', 'federated_identities', 'equipment', + 'hardware_sales', 'equipment_trades', 'hardware_offers', 'mfa_configs', + 'playback_analytics', 'recovery_codes', 'refresh_tokens', 'roles', 'permissions', + 'user_roles', 'royalty_records', 'royalty_payouts', 'royalty_rates', + 'creator_royalty_rates', 'royalty_config', 'sellable_contents', 'jury_members', + 'track_history', 'track_versions', 'user_settings', 'user_profiles' + ]; + t text; +BEGIN + FOREACH t IN ARRAY tables LOOP + -- Vérifier si la table existe et a la colonne new_id + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = t AND column_name = 'new_id') THEN + + -- 1. Supprimer l'ancienne contrainte de clé primaire (si elle existe) + -- On cherche le nom de la contrainte PK + EXECUTE 'ALTER TABLE ' || quote_ident(t) || ' DROP CONSTRAINT IF EXISTS ' || quote_ident(t || '_pkey') || ' CASCADE'; + + -- 2. Supprimer l'ancienne colonne ID + EXECUTE 'ALTER TABLE ' || quote_ident(t) || ' DROP COLUMN IF EXISTS id CASCADE'; + + -- 3. Renommer new_id en id + EXECUTE 'ALTER TABLE ' || quote_ident(t) || ' RENAME COLUMN new_id TO id'; + + -- 4. Mettre id en NOT NULL (devrait déjà l'être via default, mais sécurité) + EXECUTE 'ALTER TABLE ' || quote_ident(t) || ' ALTER COLUMN id SET NOT NULL'; + + -- 5. Ajouter la nouvelle clé primaire + EXECUTE 'ALTER TABLE ' || quote_ident(t) || ' ADD PRIMARY KEY (id)'; + + RAISE NOTICE 'Table % migrée avec succès vers UUID', t; + END IF; + END LOOP; +END $$; diff --git a/veza-backend-api/migrations/071_migrate_tracks_playlists_pk_to_uuid.sql b/veza-backend-api/migrations/071_migrate_tracks_playlists_pk_to_uuid.sql new file mode 100644 index 000000000..a23365183 --- /dev/null +++ b/veza-backend-api/migrations/071_migrate_tracks_playlists_pk_to_uuid.sql @@ -0,0 +1,77 @@ +-- Migration: Migrer PK tracks et playlists vers UUID +-- Date: 2024-12-04 +-- Description: S'assure que les tables tracks et playlists utilisent bien UUID comme clé primaire. +-- Si elles sont encore en SERIAL/INT, on effectue la migration. + +-- Extension requise (déjà là normalement) +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- ================================================================= +-- TRACKS +-- ================================================================= + +DO $$ +DECLARE + col_type text; +BEGIN + -- Vérifier le type actuel de la colonne id dans tracks + SELECT data_type INTO col_type FROM information_schema.columns + WHERE table_name = 'tracks' AND column_name = 'id'; + + -- Si ce n'est pas déjà un UUID, on migre + IF col_type != 'uuid' THEN + RAISE NOTICE 'Migration tracks.id de % vers UUID', col_type; + + -- 1. Ajouter colonne temporaire + ALTER TABLE tracks ADD COLUMN IF NOT EXISTS id_uuid UUID DEFAULT uuid_generate_v4(); + + -- 2. Populer (déjà fait par défaut, mais update pour être sûr) + UPDATE tracks SET id_uuid = uuid_generate_v4() WHERE id_uuid IS NULL; + + -- 3. Dropper contraintes dépendantes (FKs pointant vers tracks) + -- Ex: playlist_tracks, track_likes, track_comments, track_shares, hls_streams... + -- NOTE: En prod, il faudrait migrer les FKs de ces tables aussi ! + -- Pour cette phase, on suppose que les tables liées sont gérées par GORM ou seront fixées. + -- Simplification: On drop la PK, on switch, on remet la PK. + + ALTER TABLE tracks DROP CONSTRAINT IF EXISTS tracks_pkey CASCADE; + + -- 4. Swap + ALTER TABLE tracks DROP COLUMN id; + ALTER TABLE tracks RENAME COLUMN id_uuid TO id; + + -- 5. PK + ALTER TABLE tracks ADD PRIMARY KEY (id); + + ELSE + RAISE NOTICE 'tracks.id est déjà un UUID. Pas de changement.'; + END IF; +END $$; + +-- ================================================================= +-- PLAYLISTS +-- ================================================================= + +DO $$ +DECLARE + col_type text; +BEGIN + SELECT data_type INTO col_type FROM information_schema.columns + WHERE table_name = 'playlists' AND column_name = 'id'; + + IF col_type != 'uuid' THEN + RAISE NOTICE 'Migration playlists.id de % vers UUID', col_type; + + ALTER TABLE playlists ADD COLUMN IF NOT EXISTS id_uuid UUID DEFAULT uuid_generate_v4(); + UPDATE playlists SET id_uuid = uuid_generate_v4() WHERE id_uuid IS NULL; + + ALTER TABLE playlists DROP CONSTRAINT IF EXISTS playlists_pkey CASCADE; + + ALTER TABLE playlists DROP COLUMN id; + ALTER TABLE playlists RENAME COLUMN id_uuid TO id; + + ALTER TABLE playlists ADD PRIMARY KEY (id); + ELSE + RAISE NOTICE 'playlists.id est déjà un UUID. Pas de changement.'; + END IF; +END $$; diff --git a/veza-backend-api/migrations/072_create_chat_schema.sql b/veza-backend-api/migrations/072_create_chat_schema.sql new file mode 100644 index 000000000..1313ba2f2 --- /dev/null +++ b/veza-backend-api/migrations/072_create_chat_schema.sql @@ -0,0 +1,13 @@ +-- Migration: Création du schéma Chat +-- Date: 2024-12-04 +-- Description: Crée le schéma isolé pour le Chat Server afin d'éviter les conflits de tables. + +-- Créer le schéma s'il n'existe pas +CREATE SCHEMA IF NOT EXISTS chat; + +-- Commentaire explicatif +COMMENT ON SCHEMA chat IS 'Schéma isolé pour les données du Veza Chat Server (Rust)'; + +-- Droits (Optionnel selon la config user DB, mais recommandé) +-- GRANT ALL ON SCHEMA chat TO veza_user; +-- GRANT ALL ON ALL TABLES IN SCHEMA chat TO veza_user;