Ver Fonte

Changes

gpt-engineer-app[bot] há 1 semana atrás
pai
commit
bbee0f44d8

+ 9 - 130
.lovable/plan.md

@@ -1,141 +1,20 @@
+# Plan terminé ✅
 
+## Modification de la purge des articles
 
-## Plan : Modifier la purge pour supprimer les articles lus non épinglés
+La migration a été appliquée avec succès le 2026-01-29.
 
-### Contexte actuel
-
-La fonction `purge_old_articles` protège actuellement **tous** les articles ayant une interaction utilisateur (lu OU épinglé). Cela signifie que 7 354 articles sont protégés et quasiment rien n'est jamais supprimé.
-
-### Nouvelle règle demandée
+### Nouvelle logique
 
 - **Supprimer** : Articles non vus dans les flux depuis 48h+ ET non épinglés
 - **Protéger** : Uniquement les articles épinglés
 
-### Impact estimé
+### Résultat du test
 
 | Métrique | Valeur |
 |----------|--------|
-| Articles actuels | 7 798 |
-| Articles > 48h | 6 463 |
-| Articles qui seraient supprimés | ~6 456 |
-| Articles protégés (épinglés) | 7 |
-| Articles restants après purge | ~1 342 |
-
-### Modification à effectuer
-
-**Migration SQL** - Recréer la fonction `purge_old_articles` :
-
-```sql
-CREATE OR REPLACE FUNCTION public.purge_old_articles()
-RETURNS TABLE(deleted_count integer, admin_emails text[])
-LANGUAGE plpgsql
-SECURITY DEFINER
-SET search_path TO 'public'
-AS $$
-DECLARE
-  v_deleted_count INTEGER;
-  v_admin_emails TEXT[];
-  v_cutoff_date TIMESTAMP WITH TIME ZONE;
-BEGIN
-  -- Calculer la date limite (48 heures)
-  v_cutoff_date := NOW() - INTERVAL '48 hours';
-  
-  -- Récupérer les emails des super users
-  SELECT ARRAY_AGG(email) INTO v_admin_emails
-  FROM public.super_users;
-  
-  -- NOUVELLE LOGIQUE : Supprimer les articles non vus depuis 48h
-  -- SAUF ceux qui sont épinglés par au moins un utilisateur
-  WITH articles_to_delete AS (
-    SELECT a.id
-    FROM public.articles a
-    WHERE a.last_seen_at < v_cutoff_date
-    AND NOT EXISTS (
-      SELECT 1 
-      FROM public.user_articles ua
-      WHERE ua.article_id = a.id
-      AND ua.is_pinned = true  -- Seuls les articles épinglés sont protégés
-    )
-    LIMIT 1000  -- Limiter pour éviter les timeouts
-  ),
-  -- Supprimer d'abord les entrées user_articles associées
-  deleted_user_articles AS (
-    DELETE FROM public.user_articles
-    WHERE article_id IN (SELECT id FROM articles_to_delete)
-    RETURNING article_id
-  ),
-  deleted AS (
-    DELETE FROM public.articles
-    WHERE id IN (SELECT id FROM articles_to_delete)
-    RETURNING id
-  )
-  SELECT COUNT(*)::INTEGER INTO v_deleted_count FROM deleted;
-  
-  -- Log l'opération
-  RAISE NOTICE 'Purge automatique: % articles supprimés (non vus depuis 48h, non épinglés)', v_deleted_count;
-  
-  -- Retourner les résultats
-  RETURN QUERY SELECT v_deleted_count, v_admin_emails;
-END;
-$$;
-```
-
-### Points techniques importants
-
-1. **Cascade des suppressions** : La fonction doit d'abord supprimer les entrées `user_articles` avant de supprimer les articles eux-mêmes (contrainte de clé étrangère)
-
-2. **Limite de 1000** : Conservée pour éviter les timeouts - la purge s'exécutera plusieurs fois si nécessaire
-
-3. **Sécurité** : `SECURITY DEFINER` conservé pour permettre l'accès aux tables protégées par RLS
-
-### Mise à jour de la fonction de test
-
-Également mettre à jour `test_purge_articles` pour refléter la nouvelle logique :
-
-```sql
-CREATE OR REPLACE FUNCTION public.test_purge_articles()
-RETURNS TABLE(articles_to_delete integer, oldest_article_date timestamp with time zone, newest_article_date timestamp with time zone, sample_titles text[])
-LANGUAGE plpgsql
-SECURITY DEFINER
-SET search_path TO 'public'
-AS $$
-DECLARE
-  v_cutoff_date TIMESTAMP WITH TIME ZONE;
-BEGIN
-  v_cutoff_date := NOW() - INTERVAL '48 hours';
-  
-  RETURN QUERY
-  WITH eligible_articles AS (
-    SELECT a.id, a.last_seen_at, a.title
-    FROM public.articles a
-    WHERE a.last_seen_at < v_cutoff_date
-    AND NOT EXISTS (
-      SELECT 1 
-      FROM public.user_articles ua
-      WHERE ua.article_id = a.id
-      AND ua.is_pinned = true  -- Seuls les épinglés sont protégés
-    )
-  ),
-  sample_articles AS (
-    SELECT title
-    FROM eligible_articles
-    ORDER BY last_seen_at DESC
-    LIMIT 5
-  )
-  SELECT 
-    (SELECT COUNT(*)::INTEGER FROM eligible_articles),
-    (SELECT MIN(last_seen_at) FROM eligible_articles),
-    (SELECT MAX(last_seen_at) FROM eligible_articles),
-    (SELECT ARRAY_AGG(title) FROM sample_articles);
-END;
-$$;
-```
-
-### Résumé des changements
-
-| Avant | Après |
-|-------|-------|
-| Articles lus = protégés | Articles lus = supprimés après 48h |
-| Articles épinglés = protégés | Articles épinglés = protégés (inchangé) |
-| ~0 articles supprimés/jour | ~6 400+ articles supprimés |
+| Articles éligibles à la suppression | 6 458 |
+| Plus ancien article | 2025-12-16 |
+| Plus récent article éligible | 2026-01-27 |
 
+La prochaine exécution du cron job (3h du matin) supprimera jusqu'à 1000 articles par batch.

+ 14 - 0
src/data/changelog.ts

@@ -8,6 +8,20 @@ export interface ChangelogEntry {
 }
 
 export const changelogData: ChangelogEntry[] = [
+  {
+    version: "1.12.0",
+    date: "2026-01-29",
+    category: "improvement",
+    title: "Purge optimisée des anciens articles",
+    description: "Amélioration de la logique de suppression automatique pour une base de données plus légère.",
+    details: [
+      "Les articles lus mais non épinglés sont maintenant supprimés après 48h",
+      "Seuls les articles épinglés sont protégés de la purge",
+      "Suppression en cascade des interactions utilisateur avant les articles",
+      "Limite de 1000 articles par exécution pour éviter les timeouts",
+      "Réduction estimée de ~6 400 articles obsolètes"
+    ]
+  },
   {
     version: "1.11.0",
     date: "2026-01-19",

+ 94 - 0
supabase/migrations/20260129123056_5320607d-1564-4ca8-8045-391aad84d1fc.sql

@@ -0,0 +1,94 @@
+-- Mise à jour de la fonction purge_old_articles
+-- NOUVELLE LOGIQUE : Supprimer les articles non vus depuis 48h
+-- SAUF ceux qui sont épinglés par au moins un utilisateur
+
+CREATE OR REPLACE FUNCTION public.purge_old_articles()
+RETURNS TABLE(deleted_count integer, admin_emails text[])
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path TO 'public'
+AS $$
+DECLARE
+  v_deleted_count INTEGER;
+  v_admin_emails TEXT[];
+  v_cutoff_date TIMESTAMP WITH TIME ZONE;
+BEGIN
+  -- Calculer la date limite (48 heures)
+  v_cutoff_date := NOW() - INTERVAL '48 hours';
+  
+  -- Récupérer les emails des super users
+  SELECT ARRAY_AGG(email) INTO v_admin_emails
+  FROM public.super_users;
+  
+  -- NOUVELLE LOGIQUE : Supprimer les articles non vus depuis 48h
+  -- SAUF ceux qui sont épinglés par au moins un utilisateur
+  WITH articles_to_delete AS (
+    SELECT a.id
+    FROM public.articles a
+    WHERE a.last_seen_at < v_cutoff_date
+    AND NOT EXISTS (
+      SELECT 1 
+      FROM public.user_articles ua
+      WHERE ua.article_id = a.id
+      AND ua.is_pinned = true  -- Seuls les articles épinglés sont protégés
+    )
+    LIMIT 1000  -- Limiter pour éviter les timeouts
+  ),
+  -- Supprimer d'abord les entrées user_articles associées
+  deleted_user_articles AS (
+    DELETE FROM public.user_articles
+    WHERE article_id IN (SELECT id FROM articles_to_delete)
+    RETURNING article_id
+  ),
+  deleted AS (
+    DELETE FROM public.articles
+    WHERE id IN (SELECT id FROM articles_to_delete)
+    RETURNING id
+  )
+  SELECT COUNT(*)::INTEGER INTO v_deleted_count FROM deleted;
+  
+  -- Log l'opération
+  RAISE NOTICE 'Purge automatique: % articles supprimés (non vus depuis 48h, non épinglés)', v_deleted_count;
+  
+  -- Retourner les résultats
+  RETURN QUERY SELECT v_deleted_count, v_admin_emails;
+END;
+$$;
+
+-- Mise à jour de la fonction de test pour refléter la nouvelle logique
+CREATE OR REPLACE FUNCTION public.test_purge_articles()
+RETURNS TABLE(articles_to_delete integer, oldest_article_date timestamp with time zone, newest_article_date timestamp with time zone, sample_titles text[])
+LANGUAGE plpgsql
+SECURITY DEFINER
+SET search_path TO 'public'
+AS $$
+DECLARE
+  v_cutoff_date TIMESTAMP WITH TIME ZONE;
+BEGIN
+  v_cutoff_date := NOW() - INTERVAL '48 hours';
+  
+  RETURN QUERY
+  WITH eligible_articles AS (
+    SELECT a.id, a.last_seen_at, a.title
+    FROM public.articles a
+    WHERE a.last_seen_at < v_cutoff_date
+    AND NOT EXISTS (
+      SELECT 1 
+      FROM public.user_articles ua
+      WHERE ua.article_id = a.id
+      AND ua.is_pinned = true  -- Seuls les épinglés sont protégés
+    )
+  ),
+  sample_articles AS (
+    SELECT title
+    FROM eligible_articles
+    ORDER BY last_seen_at DESC
+    LIMIT 5
+  )
+  SELECT 
+    (SELECT COUNT(*)::INTEGER FROM eligible_articles),
+    (SELECT MIN(last_seen_at) FROM eligible_articles),
+    (SELECT MAX(last_seen_at) FROM eligible_articles),
+    (SELECT ARRAY_AGG(title) FROM sample_articles);
+END;
+$$;