veza/veza-chat-server/migrations/archive/004_corrective_migration.sql
2025-12-03 20:33:26 +01:00

337 lines
No EOL
12 KiB
PL/PgSQL

-- Migration 004: Correction et compatibilité avec le schéma existant
-- Cette migration corrige les erreurs de la migration précédente
BEGIN;
-- ================================================
-- CORRECTIONS DE LA TABLE ROOMS
-- ================================================
-- Ajouter les colonnes manquantes à la table rooms existante
ALTER TABLE rooms ADD COLUMN IF NOT EXISTS creator_id INTEGER;
ALTER TABLE rooms ADD COLUMN IF NOT EXISTS max_members INTEGER DEFAULT 1000;
ALTER TABLE rooms ADD COLUMN IF NOT EXISTS description TEXT;
ALTER TABLE rooms ADD COLUMN IF NOT EXISTS is_archived BOOLEAN DEFAULT false;
-- Ajouter les contraintes de clés étrangères
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'rooms_creator_id_fkey'
) THEN
ALTER TABLE rooms ADD CONSTRAINT rooms_creator_id_fkey
FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL;
END IF;
END $$;
-- Créer l'index manquant
CREATE INDEX IF NOT EXISTS idx_rooms_creator ON rooms (creator_id);
-- ================================================
-- CORRECTIONS DE LA TABLE MESSAGES
-- ================================================
-- Renommer la colonne timestamp vers created_at pour cohérence
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'messages' AND column_name = 'timestamp')
AND NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'messages' AND column_name = 'created_at') THEN
ALTER TABLE messages RENAME COLUMN timestamp TO created_at;
END IF;
END $$;
-- Ajouter des colonnes manquantes pour les nouvelles fonctionnalités
ALTER TABLE messages ADD COLUMN IF NOT EXISTS is_pinned BOOLEAN DEFAULT false;
ALTER TABLE messages ADD COLUMN IF NOT EXISTS is_flagged BOOLEAN DEFAULT false;
ALTER TABLE messages ADD COLUMN IF NOT EXISTS moderation_notes TEXT;
ALTER TABLE messages ADD COLUMN IF NOT EXISTS thread_count INTEGER DEFAULT 0;
ALTER TABLE messages ADD COLUMN IF NOT EXISTS original_content TEXT;
-- Ajouter une colonne status si elle n'existe pas
ALTER TABLE messages ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'sent';
-- Ajouter des index pour les nouvelles colonnes
CREATE INDEX IF NOT EXISTS idx_messages_pinned ON messages (is_pinned) WHERE is_pinned = true;
CREATE INDEX IF NOT EXISTS idx_messages_flagged ON messages (is_flagged) WHERE is_flagged = true;
CREATE INDEX IF NOT EXISTS idx_messages_status ON messages (status);
CREATE INDEX IF NOT EXISTS idx_messages_thread ON messages (thread_count) WHERE thread_count > 0;
-- ================================================
-- AMÉLIORER LA TABLE USERS EXISTANTE
-- ================================================
-- Ajouter des colonnes pour la sécurité et la modération
ALTER TABLE users ADD COLUMN IF NOT EXISTS reputation_score INTEGER DEFAULT 100;
ALTER TABLE users ADD COLUMN IF NOT EXISTS is_banned BOOLEAN DEFAULT false;
ALTER TABLE users ADD COLUMN IF NOT EXISTS ban_expires_at TIMESTAMPTZ;
ALTER TABLE users ADD COLUMN IF NOT EXISTS ban_reason TEXT;
ALTER TABLE users ADD COLUMN IF NOT EXISTS warning_count INTEGER DEFAULT 0;
ALTER TABLE users ADD COLUMN IF NOT EXISTS mute_expires_at TIMESTAMPTZ;
ALTER TABLE users ADD COLUMN IF NOT EXISTS last_seen TIMESTAMPTZ DEFAULT NOW();
ALTER TABLE users ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'offline';
ALTER TABLE users ADD COLUMN IF NOT EXISTS status_message VARCHAR(100);
-- Ajouter des index pour les nouvelles colonnes users
CREATE INDEX IF NOT EXISTS idx_users_reputation ON users (reputation_score);
CREATE INDEX IF NOT EXISTS idx_users_banned ON users (is_banned) WHERE is_banned = true;
CREATE INDEX IF NOT EXISTS idx_users_status ON users (status);
CREATE INDEX IF NOT EXISTS idx_users_last_seen ON users (last_seen);
-- ================================================
-- CRÉER LES TABLES MANQUANTES
-- ================================================
-- Table des mentions (si elle n'existe pas)
CREATE TABLE IF NOT EXISTS message_mentions (
id BIGSERIAL PRIMARY KEY,
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
is_read BOOLEAN DEFAULT false,
UNIQUE (message_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_mentions_user ON message_mentions (user_id, is_read);
CREATE INDEX IF NOT EXISTS idx_mentions_message ON message_mentions (message_id);
-- Table des blocages utilisateurs (si elle n'existe pas)
CREATE TABLE IF NOT EXISTS user_blocks (
id BIGSERIAL PRIMARY KEY,
blocker_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
blocked_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
reason VARCHAR(500),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (blocker_id, blocked_id),
CONSTRAINT no_self_block CHECK (blocker_id != blocked_id)
);
CREATE INDEX IF NOT EXISTS idx_blocks_blocker ON user_blocks (blocker_id);
CREATE INDEX IF NOT EXISTS idx_blocks_blocked ON user_blocks (blocked_id);
-- Table des logs de modération (si elle n'existe pas)
CREATE TABLE IF NOT EXISTS moderation_log (
id BIGSERIAL PRIMARY KEY,
moderator_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
target_type VARCHAR(20) NOT NULL CHECK (target_type IN ('user', 'message', 'room')),
target_id TEXT NOT NULL,
action VARCHAR(50) NOT NULL,
reason TEXT,
details JSONB DEFAULT '{}'::jsonb,
duration INTERVAL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ip_address INET
);
CREATE INDEX IF NOT EXISTS idx_moderation_log_moderator ON moderation_log (moderator_id, created_at);
CREATE INDEX IF NOT EXISTS idx_moderation_log_target ON moderation_log (target_type, target_id);
CREATE INDEX IF NOT EXISTS idx_moderation_log_action ON moderation_log (action, created_at);
-- ================================================
-- VUES CORRIGÉES AVEC LES BONS NOMS DE COLONNES
-- ================================================
-- Vue des statistiques serveur (corrigée)
DROP VIEW IF EXISTS server_stats;
CREATE OR REPLACE VIEW server_stats AS
SELECT
'total_users'::text as metric,
COUNT(*)::bigint as value
FROM users
WHERE id IS NOT NULL
UNION ALL
SELECT
'active_users'::text as metric,
COUNT(*)::bigint as value
FROM users
WHERE last_seen > NOW() - INTERVAL '1 hour'
UNION ALL
SELECT
'total_rooms'::text as metric,
COUNT(*)::bigint as value
FROM rooms
UNION ALL
SELECT
'total_messages'::text as metric,
COUNT(*)::bigint as value
FROM messages
UNION ALL
SELECT
'messages_today'::text as metric,
COUNT(*)::bigint as value
FROM messages
WHERE created_at >= CURRENT_DATE;
-- ================================================
-- FONCTIONS UTILITAIRES CORRIGÉES
-- ================================================
-- Fonction pour calculer la réputation (corrigée)
CREATE OR REPLACE FUNCTION calculate_user_reputation(user_id_param INTEGER)
RETURNS INTEGER AS $$
DECLARE
base_score INTEGER := 100;
warnings INTEGER := 0;
bans INTEGER := 0;
recent_messages INTEGER := 0;
BEGIN
-- Compter les avertissements
SELECT COALESCE(warning_count, 0) INTO warnings
FROM users WHERE id = user_id_param;
-- Compter les messages récents (bonus)
SELECT COUNT(*) INTO recent_messages
FROM messages
WHERE from_user = user_id_param
AND created_at > NOW() - INTERVAL '30 days';
-- Calculer le score final
RETURN GREATEST(0, base_score - (warnings * 5) + (recent_messages / 10));
END;
$$ LANGUAGE plpgsql;
-- Fonction de nettoyage des données anciennes (corrigée)
CREATE OR REPLACE FUNCTION cleanup_old_data()
RETURNS void AS $$
BEGIN
-- Supprimer les sessions expirées anciennes
DELETE FROM user_sessions
WHERE created_at < NOW() - INTERVAL '30 days';
-- Marquer les anciens messages comme archivés (soft delete)
UPDATE messages
SET status = 'archived'
WHERE created_at < NOW() - INTERVAL '1 year'
AND status = 'sent';
-- Nettoyer les logs de modération anciens
DELETE FROM moderation_log
WHERE created_at < NOW() - INTERVAL '6 months';
RAISE NOTICE 'Nettoyage des données anciennes terminé';
END;
$$ LANGUAGE plpgsql;
-- ================================================
-- TRIGGERS POUR MAINTENIR LA COHÉRENCE
-- ================================================
-- Trigger pour mettre à jour last_seen automatiquement
CREATE OR REPLACE FUNCTION update_user_last_seen()
RETURNS TRIGGER AS $$
BEGIN
UPDATE users SET last_seen = NOW() WHERE id = NEW.from_user;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_update_last_seen ON messages;
CREATE TRIGGER trigger_update_last_seen
AFTER INSERT ON messages
FOR EACH ROW EXECUTE FUNCTION update_user_last_seen();
-- Trigger pour compter les threads
CREATE OR REPLACE FUNCTION update_thread_count()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.reply_to_id IS NOT NULL THEN
UPDATE messages
SET thread_count = thread_count + 1
WHERE id = NEW.reply_to_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_thread_count ON messages;
CREATE TRIGGER trigger_thread_count
AFTER INSERT ON messages
FOR EACH ROW EXECUTE FUNCTION update_thread_count();
-- ================================================
-- CONTRAINTES DE SÉCURITÉ SUPPLÉMENTAIRES
-- ================================================
-- Limiter la longueur des messages épinglés
ALTER TABLE messages ADD CONSTRAINT chk_pinned_content_reasonable
CHECK (NOT is_pinned OR LENGTH(content) <= 500);
-- Limiter le nombre de réactions par utilisateur et message
ALTER TABLE message_reactions ADD CONSTRAINT chk_emoji_reasonable
CHECK (LENGTH(emoji) <= 10);
-- Vérifier que les statuts utilisateur sont valides
ALTER TABLE users ADD CONSTRAINT chk_user_status_valid
CHECK (status IN ('online', 'away', 'busy', 'invisible', 'offline'));
-- ================================================
-- DONNÉES DE TEST ET INITIALISATION
-- ================================================
-- Mettre à jour les données existantes pour la compatibilité
UPDATE messages SET status = 'sent' WHERE status IS NULL;
UPDATE users SET reputation_score = 100 WHERE reputation_score IS NULL;
UPDATE users SET status = 'offline' WHERE status IS NULL;
-- Créer un salon général s'il n'existe pas
INSERT INTO rooms (name, is_private, description, creator_id)
SELECT 'général', false, 'Salon de discussion générale',
(SELECT id FROM users ORDER BY id LIMIT 1)
WHERE NOT EXISTS (SELECT 1 FROM rooms WHERE name = 'général');
-- ================================================
-- COMMENTAIRES POUR DOCUMENTATION
-- ================================================
COMMENT ON TABLE message_mentions IS 'Mentions d''utilisateurs dans les messages';
COMMENT ON TABLE user_blocks IS 'Blocages entre utilisateurs pour empêcher les DM';
COMMENT ON TABLE moderation_log IS 'Journal des actions de modération';
COMMENT ON VIEW server_stats IS 'Statistiques temps réel du serveur';
-- ================================================
-- PERMISSIONS ET SÉCURITÉ
-- ================================================
-- Accorder les permissions nécessaires à l'utilisateur veza
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO veza;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO veza;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO veza;
COMMIT;
-- ================================================
-- VÉRIFICATIONS POST-MIGRATION
-- ================================================
-- Vérifier que les colonnes essentielles existent
DO $$
BEGIN
-- Vérifier messages.created_at
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'messages' AND column_name = 'created_at') THEN
RAISE EXCEPTION 'Colonne messages.created_at manquante après migration';
END IF;
-- Vérifier rooms.creator_id
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'rooms' AND column_name = 'creator_id') THEN
RAISE EXCEPTION 'Colonne rooms.creator_id manquante après migration';
END IF;
RAISE NOTICE 'Vérifications post-migration réussies';
END $$;