-- ================================================================ -- MIGRATION DE NETTOYAGE ET MISE À JOUR POUR PRODUCTION -- Version: 0.2.0 - Compatible avec structure existante -- ================================================================ -- ⚠️ ATTENTION: Cette migration est partiellement destructive -- 🔒 Assurez-vous d'avoir une sauvegarde complète avant exécution -- -- Utilisation: -- psql -h 10.5.191.47 -U veza -d veza_db -f migrations/999_cleanup_production_ready_fixed.sql -- ================================================================ -- Début de la migration de production -- Vérifier que nous sommes dans la bonne base SELECT current_database() as current_db, current_user as current_user_name; -- Créer l'extension UUID si pas présente -- Extension uuid-ossp non nécessaire, gen_random_uuid() est utilisé -- ================================================================ -- ÉTAPE 1: SAUVEGARDE DES DONNÉES EXISTANTES -- ================================================================ -- Sauvegarde des données existantes -- Sauvegarder les utilisateurs existants DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'users') THEN CREATE TEMP TABLE temp_old_users AS SELECT id, username, email, created_at, role FROM users; RAISE NOTICE 'Sauvegarde de % utilisateurs', (SELECT COUNT(*) FROM temp_old_users); ELSE RAISE NOTICE 'Table users non trouvée, création nécessaire'; END IF; END $$; -- Sauvegarder les messages existants DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'messages') THEN CREATE TEMP TABLE temp_old_messages AS SELECT id, sender_id, content, created_at, message_type FROM messages; RAISE NOTICE 'Sauvegarde de % messages', (SELECT COUNT(*) FROM temp_old_messages); ELSE RAISE NOTICE 'Table messages non trouvée, création nécessaire'; END IF; END $$; -- ================================================================ -- ÉTAPE 2: SUPPRESSION SÉCURISÉE DES TABLES REDONDANTES -- ================================================================ -- Suppression des tables redondantes -- Supprimer uniquement les tables qui existent et sont redondantes DROP TABLE IF EXISTS users_enhanced CASCADE; DROP TABLE IF EXISTS users_backup CASCADE; DROP TABLE IF EXISTS rooms_enhanced CASCADE; DROP TABLE IF EXISTS messages_enhanced CASCADE; DROP TABLE IF EXISTS message_mentions_enhanced CASCADE; DROP TABLE IF EXISTS message_mentions_secure CASCADE; DROP TABLE IF EXISTS message_reactions_enhanced CASCADE; DROP TABLE IF EXISTS room_members_enhanced CASCADE; DROP TABLE IF EXISTS user_sessions_enhanced CASCADE; DROP TABLE IF EXISTS user_sessions_secure CASCADE; DROP TABLE IF EXISTS user_blocks_enhanced CASCADE; DROP TABLE IF EXISTS user_blocks_secure CASCADE; DROP TABLE IF EXISTS security_events_enhanced CASCADE; DROP TABLE IF EXISTS security_events_secure CASCADE; -- Supprimer les tables métier obsolètes (si elles existent) DROP TABLE IF EXISTS listings CASCADE; DROP TABLE IF EXISTS categories CASCADE; DROP TABLE IF EXISTS user_products CASCADE; DROP TABLE IF EXISTS internal_documents CASCADE; DROP TABLE IF EXISTS shared_ressources CASCADE; DROP TABLE IF EXISTS shared_ressource_tags CASCADE; DROP TABLE IF EXISTS ressource_tags CASCADE; DROP TABLE IF EXISTS tracks CASCADE; -- ================================================================ -- ÉTAPE 3: CRÉATION DES TYPES ENUMS NÉCESSAIRES -- ================================================================ -- Création des types énumérés -- Type user_role supprimé car non nécessaire pour la migration de base -- Type message_status supprimé car non nécessaire pour la migration de base -- Type conversation_type supprimé car non nécessaire pour la migration de base -- ================================================================ -- ÉTAPE 4: MISE À JOUR DE LA TABLE USERS -- ================================================================ -- Mise à jour de la table users -- Colonnes uuid supprimées car les tables utilisent déjà id avec UUID -- Ajouter les nouvelles colonnes de sécurité DO $$ BEGIN -- Colonnes de sécurité 2FA supprimées car non nécessaires pour la migration de base -- Colonnes de profil (display_name et avatar_url existent déjà dans la migration de base) -- Colonne bio supprimée car non nécessaire pour la migration de base -- Colonnes de métadonnées -- Colonne last_login supprimée car non nécessaire pour la migration de base -- Colonne last_activity supprimée car non nécessaire pour la migration de base -- updated_at existe déjà dans la migration de base, pas besoin de l'ajouter -- Colonnes de permissions (is_active existe déjà dans la migration de base) -- Colonne is_verified supprimée car non nécessaire pour la migration de base END $$; -- Mise à jour du type de rôle DO $$ BEGIN -- Vérifier si la colonne role existe et la convertir IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'role') THEN -- Sauvegarder les valeurs existantes avant conversion UPDATE users SET role = 'user' WHERE role IS NULL OR role = ''; -- Convertir vers le nouveau type (si ce n'est pas déjà fait) BEGIN ALTER TABLE users ALTER COLUMN role TYPE user_role USING role::user_role; RAISE NOTICE 'Colonne role convertie vers user_role'; EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'Colonne role déjà au bon type ou erreur: %', SQLERRM; END; ELSE ALTER TABLE users ADD COLUMN role user_role DEFAULT 'user' NOT NULL; RAISE NOTICE 'Colonne role ajoutée avec type user_role'; END IF; END $$; -- ================================================================ -- ÉTAPE 5: CRÉATION DE LA TABLE CONVERSATIONS -- ================================================================ -- Création de la table conversations -- Table conversations existe déjà dans la migration 001, pas besoin de la redéfinir -- ================================================================ -- ÉTAPE 6: MISE À JOUR DE LA TABLE MESSAGES -- ================================================================ -- Mise à jour de la table messages -- Colonnes uuid supprimées car les tables utilisent déjà id avec UUID -- Colonnes déjà cohérentes avec la migration de base -- Colonne conversation_id existe déjà dans la migration de base -- Colonnes avancées existent déjà dans la migration de base -- ================================================================ -- ÉTAPE 7: CRÉATION DES TABLES COMPLÉMENTAIRES -- ================================================================ -- Création des tables complémentaires -- Table conversation_members existe déjà dans la migration 001, pas besoin de la redéfinir -- Table message_reactions existe déjà dans la migration 002, pas besoin de la redéfinir -- Table pour les mentions CREATE TABLE IF NOT EXISTS message_mentions ( id BIGSERIAL PRIMARY KEY, message_id UUID NOT NULL, mentioned_user_id UUID NOT NULL, is_read BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, CONSTRAINT message_mentions_message_fk FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE, CONSTRAINT message_mentions_user_fk FOREIGN KEY (mentioned_user_id) REFERENCES users(id) ON DELETE CASCADE, CONSTRAINT message_mentions_unique UNIQUE (message_id, mentioned_user_id) ); -- Table pour l'historique des messages CREATE TABLE IF NOT EXISTS message_history ( id BIGSERIAL PRIMARY KEY, message_id UUID NOT NULL, old_content TEXT NOT NULL, edited_by UUID NOT NULL, edited_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, CONSTRAINT message_history_message_fk FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE, CONSTRAINT message_history_user_fk FOREIGN KEY (edited_by) REFERENCES users(id) ON DELETE CASCADE ); -- Table pour les sessions utilisateur CREATE TABLE IF NOT EXISTS user_sessions ( id BIGSERIAL PRIMARY KEY, user_id UUID NOT NULL, session_token VARCHAR(255) UNIQUE NOT NULL, refresh_token VARCHAR(255) UNIQUE, device_info TEXT, ip_address INET, user_agent TEXT, is_active BOOLEAN DEFAULT TRUE, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, last_activity TIMESTAMPTZ DEFAULT NOW() NOT NULL, CONSTRAINT user_sessions_user_fk FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); -- ================================================================ -- ÉTAPE 8: CRÉATION DES INDEX DE PERFORMANCE -- ================================================================ -- Création des index de performance -- Index pour users CREATE INDEX IF NOT EXISTS idx_users_username_active ON users(username) WHERE is_active = TRUE; -- Index last_login supprimé car la colonne n'existe pas dans la migration de base -- Index email_verified supprimé car la colonne is_verified n'existe pas dans la migration de base -- Index last_activity supprimé car la colonne n'existe pas dans la migration de base -- Index pour conversations CREATE INDEX IF NOT EXISTS idx_conversations_type_public ON conversations(conversation_type) WHERE is_private = FALSE; CREATE INDEX IF NOT EXISTS idx_conversations_owner_active ON conversations(created_by); -- Index pour messages (performance critique) CREATE INDEX IF NOT EXISTS idx_messages_conversation_time ON messages(conversation_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_messages_sender_time ON messages(sender_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_messages_threads ON messages(parent_message_id, created_at) WHERE parent_message_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_messages_pinned ON messages(conversation_id) WHERE is_pinned = TRUE; -- Index pour réactions (utilise reaction_type au lieu d'emoji) CREATE INDEX IF NOT EXISTS idx_reactions_message ON message_reactions(message_id, reaction_type); CREATE INDEX IF NOT EXISTS idx_reactions_user ON message_reactions(user_id, created_at DESC); -- Index pour mentions CREATE INDEX IF NOT EXISTS idx_mentions_user_unread ON message_mentions(mentioned_user_id) WHERE is_read = FALSE; -- Index pour sessions CREATE INDEX IF NOT EXISTS idx_sessions_user_active ON user_sessions(user_id) WHERE is_active = TRUE; CREATE INDEX IF NOT EXISTS idx_sessions_token ON user_sessions(session_token); -- ================================================================ -- ÉTAPE 9: CRÉATION DES FONCTIONS UTILITAIRES -- ================================================================ -- Création des fonctions utilitaires -- Fonction pour obtenir l'ID utilisateur courant (à implémenter côté app) CREATE OR REPLACE FUNCTION current_user_id() RETURNS BIGINT AS $$ BEGIN -- Cette fonction doit être implémentée côté application -- Pour l'instant, elle retourne NULL RETURN NULL; END; $$ LANGUAGE plpgsql SECURITY DEFINER; -- Fonction pour nettoyer les sessions expirées CREATE OR REPLACE FUNCTION cleanup_expired_sessions() RETURNS INTEGER AS $$ DECLARE deleted_count INTEGER; BEGIN DELETE FROM user_sessions WHERE expires_at < NOW() OR (last_activity < NOW() - INTERVAL '30 days'); GET DIAGNOSTICS deleted_count = ROW_COUNT; RETURN deleted_count; END; $$ LANGUAGE plpgsql; -- Fonction trigger pour mettre à jour updated_at CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- ================================================================ -- ÉTAPE 10: CRÉATION DES TRIGGERS -- ================================================================ -- Création des triggers -- Triggers pour updated_at DROP TRIGGER IF EXISTS update_users_updated_at ON users; CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_conversations_updated_at ON conversations; CREATE TRIGGER update_conversations_updated_at BEFORE UPDATE ON conversations FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_messages_updated_at ON messages; CREATE TRIGGER update_messages_updated_at BEFORE UPDATE ON messages FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- ================================================================ -- ÉTAPE 11: VÉRIFICATION ET NETTOYAGE FINAL -- ================================================================ -- Vérification finale -- Nettoyer les fonctions obsolètes DROP FUNCTION IF EXISTS cleanup_expired_sessions_secure(); DROP FUNCTION IF EXISTS cleanup_old_audit_logs(); DROP FUNCTION IF EXISTS cleanup_old_data_secure(); DROP FUNCTION IF EXISTS handle_mentions_secure(); -- Mettre à jour les statistiques ANALYZE users; ANALYZE conversations; ANALYZE messages; ANALYZE message_reactions; ANALYZE message_mentions; ANALYZE user_sessions; -- Validation finale DO $$ DECLARE user_count INTEGER; message_count INTEGER; conversation_count INTEGER; BEGIN SELECT COUNT(*) INTO user_count FROM users; SELECT COUNT(*) INTO message_count FROM messages; SELECT COUNT(*) INTO conversation_count FROM conversations; RAISE NOTICE '✅ Migration terminée avec succès:'; RAISE NOTICE ' - Utilisateurs: %', user_count; RAISE NOTICE ' - Messages: %', message_count; RAISE NOTICE ' - Conversations: %', conversation_count; -- Warnings si problèmes détectés IF user_count = 0 THEN RAISE WARNING '⚠️ Aucun utilisateur trouvé après migration'; END IF; IF message_count > 0 AND conversation_count = 0 THEN RAISE WARNING '⚠️ Messages présents mais aucune conversation'; END IF; END $$; -- Nettoyer les tables temporaires DROP TABLE IF EXISTS temp_old_users; DROP TABLE IF EXISTS temp_old_messages; -- ================================================================ -- FINALISATION -- ================================================================ -- Migration de production terminée avec succès SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size FROM pg_tables WHERE schemaname = 'public' AND tablename IN ('users', 'conversations', 'messages', 'message_reactions', 'message_mentions', 'user_sessions') ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;