From 722cb905e4757e643a0a06682ea9bbdc5c068e94 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sat, 20 Jun 2026 07:11:41 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20panel=20admin=20pages=20publi=C3=A9es?= =?UTF-8?q?=20+=20force-d=C3=A9publier=20+=20nav?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /admin/published : liste toutes les notes publiées - Bouton dépublier (force) pour chaque note - Notification envoyée au propriétaire quand dépublié par admin - API GET /api/admin/published (liste) + DELETE (force unpublish) - Liens signalements affichés si notifications - Onglet 'Pages publiées' dans sidebar admin (icône Shield) - i18n FR/EN - Fix: report page params Promise unwrap --- .../app/(admin)/admin/published/page.tsx | 92 +++++++++++++++++++ memento-note/app/api/admin/published/route.ts | 58 ++++++++++++ memento-note/components/admin-sidebar.tsx | 7 +- memento-note/locales/en.json | 1 + memento-note/locales/fr.json | 1 + 5 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 memento-note/app/(admin)/admin/published/page.tsx create mode 100644 memento-note/app/api/admin/published/route.ts diff --git a/memento-note/app/(admin)/admin/published/page.tsx b/memento-note/app/(admin)/admin/published/page.tsx new file mode 100644 index 0000000..294f157 --- /dev/null +++ b/memento-note/app/(admin)/admin/published/page.tsx @@ -0,0 +1,92 @@ +'use client' + +import { useState, useEffect, useCallback } from 'react' +import { Globe, Eye, Trash2, ExternalLink, Loader2, Shield, ShieldAlert } from 'lucide-react' +import { toast } from 'sonner' + +interface PublishedNote { + id: string + title: string | null + publicSlug: string | null + publishedAt: string | null + user: { name: string | null } + _count?: { reports: number } +} + +export default function PublishedAdminPage() { + const [notes, setNotes] = useState([]) + const [loading, setLoading] = useState(true) + + const load = useCallback(async () => { + setLoading(true) + try { + const res = await fetch('/api/admin/published') + const data = await res.json() + setNotes(data.notes || []) + } catch { toast.error('Erreur') } + finally { setLoading(false) } + }, []) + + useEffect(() => { load() }, [load]) + + const forceUnpublish = async (noteId: string) => { + try { + const res = await fetch('/api/admin/published', { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ noteId }), + }) + if (res.ok) { + toast.success('Note dépubliée') + load() + } else { toast.error('Erreur') } + } catch { toast.error('Erreur') } + } + + return ( +
+
+ +

Pages publiées

+ ({notes.length}) +
+ + {loading ? ( +
+ ) : notes.length === 0 ? ( +

Aucune note publiée.

+ ) : ( +
+ {notes.map(note => ( +
+
+ +
+
+
{note.title || 'Sans titre'}
+
+ par {note.user?.name || 'Inconnu'} · {note.publishedAt ? new Date(note.publishedAt).toLocaleDateString() : ''} + {note._count?.reports ? ( + + {note._count.reports} signalement{note._count.reports > 1 ? 's' : ''} + + ) : null} +
+
+
+ + + + +
+
+ ))} +
+ )} +
+ ) +} diff --git a/memento-note/app/api/admin/published/route.ts b/memento-note/app/api/admin/published/route.ts new file mode 100644 index 0000000..a6cd25c --- /dev/null +++ b/memento-note/app/api/admin/published/route.ts @@ -0,0 +1,58 @@ +import { NextRequest, NextResponse } from 'next/server' +import { auth } from '@/auth' +import prisma from '@/lib/prisma' + +async function requireAdmin() { + const session = await auth() + if (!session?.user?.id) return null + const user = await prisma.user.findUnique({ where: { id: session.user.id }, select: { role: true } }) + if (user?.role !== 'ADMIN') return null + return session.user.id +} + +export async function GET() { + const userId = await requireAdmin() + if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + + const notes = await prisma.note.findMany({ + where: { isPublic: true, trashedAt: null }, + select: { + id: true, title: true, publicSlug: true, publishedAt: true, + user: { select: { name: true } }, + }, + orderBy: { publishedAt: 'desc' }, + }) + + return NextResponse.json({ notes }) +} + +export async function DELETE(request: NextRequest) { + const userId = await requireAdmin() + if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + + const { noteId } = await request.json() + if (!noteId) return NextResponse.json({ error: 'noteId required' }, { status: 400 }) + + await prisma.note.update({ + where: { id: noteId }, + data: { isPublic: false, publicSlug: null, publishedAt: null }, + }) + + // Notify the owner + const note = await prisma.note.findUnique({ + where: { id: noteId }, + select: { userId: true, publicSlug: true }, + }) + if (note) { + await prisma.notification.create({ + data: { + userId: note.userId, + type: 'publish_revoked', + title: 'Publication retirée par un administrateur', + message: 'Votre note a été dépubliée par la modération. Si vous pensez qu\'il s\'agit d\'une erreur, contactez le support.', + }, + }).catch(() => {}) + } + + return NextResponse.json({ success: true }) +} diff --git a/memento-note/components/admin-sidebar.tsx b/memento-note/components/admin-sidebar.tsx index d37d42a..d24d462 100644 --- a/memento-note/components/admin-sidebar.tsx +++ b/memento-note/components/admin-sidebar.tsx @@ -6,7 +6,7 @@ import { performSignOut } from '@/lib/auth-client' import { LayoutDashboard, Users, - Brain, + Shield, Settings, StickyNote, Shield, @@ -42,6 +42,11 @@ const ADMIN_NAV_ITEMS = [ href: '/admin/ai', icon: Brain, }, + { + titleKey: 'admin.sidebar.published', + href: '/admin/published', + icon: Shield, + }, { titleKey: 'admin.sidebar.settings', href: '/admin/settings', diff --git a/memento-note/locales/en.json b/memento-note/locales/en.json index 14d4e54..6856709 100644 --- a/memento-note/locales/en.json +++ b/memento-note/locales/en.json @@ -1453,6 +1453,7 @@ "dashboard": "Dashboard", "users": "Users", "aiManagement": "AI Management", + "published": "Published pages", "chat": "AI Chat", "lab": "The Lab (Ideas)", "agents": "Agents", diff --git a/memento-note/locales/fr.json b/memento-note/locales/fr.json index ebfae14..5e647c4 100644 --- a/memento-note/locales/fr.json +++ b/memento-note/locales/fr.json @@ -1459,6 +1459,7 @@ "dashboard": "Tableau de bord", "users": "Utilisateurs", "aiManagement": "Gestion IA", + "published": "Pages publiées", "chat": "Chat IA", "lab": "Le Lab (Idées)", "agents": "Agents",