Browse Source

Updated article purge logic

Applied SQL migration to modify the article purge function. Read articles older than 48 hours that are not pinned are now deleted. Pinned articles remain protected. Updated test function and changelog.

X-Lovable-Edit-ID: edt-9a6e3d77-98ed-474b-8099-7f8aec190136
gpt-engineer-app[bot] 1 tuần trước cách đây
mục cha
commit
fd2177a229

+ 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;
+$$;