feat: add moderation center for reports and ratings with user cleanup cron job
This commit is contained in:
@@ -68,13 +68,65 @@ export async function getPendingRatings() {
|
||||
|
||||
export async function updateRatingStatus(id: string, status: 'APPROVED' | 'REJECTED') {
|
||||
try {
|
||||
await prisma.rating.update({
|
||||
const updated = await prisma.rating.update({
|
||||
where: { id },
|
||||
data: { status }
|
||||
});
|
||||
|
||||
if (updated.businessId) {
|
||||
const stats = await prisma.rating.aggregate({
|
||||
where: { businessId: updated.businessId, status: 'APPROVED' },
|
||||
_avg: { value: true },
|
||||
_count: { value: true }
|
||||
});
|
||||
|
||||
await prisma.business.update({
|
||||
where: { id: updated.businessId },
|
||||
data: {
|
||||
rating: stats._avg.value || 0,
|
||||
ratingCount: stats._count.value || 0
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
revalidatePath('/moderation');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: "Erreur lors de la mise à jour de l'avis" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteRating(id: string) {
|
||||
try {
|
||||
const rating = await prisma.rating.findUnique({
|
||||
where: { id },
|
||||
select: { businessId: true }
|
||||
});
|
||||
|
||||
await prisma.rating.delete({
|
||||
where: { id }
|
||||
});
|
||||
|
||||
if (rating?.businessId) {
|
||||
const stats = await prisma.rating.aggregate({
|
||||
where: { businessId: rating.businessId, status: 'APPROVED' },
|
||||
_avg: { value: true },
|
||||
_count: { value: true }
|
||||
});
|
||||
|
||||
await prisma.business.update({
|
||||
where: { id: rating.businessId },
|
||||
data: {
|
||||
rating: stats._avg.value || 0,
|
||||
ratingCount: stats._count.value || 0
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
revalidatePath('/moderation');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: "Erreur lors de la suppression de l'avis" };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,12 +30,23 @@ export async function GET(request: Request) {
|
||||
return NextResponse.json({ message: "Aucun compte à supprimer" });
|
||||
}
|
||||
|
||||
// 2. Suppression définitive
|
||||
// Prisma supprimera les données liées si onDelete: Cascade est configuré dans le schéma
|
||||
const userIds = usersToDelete.map(u => u.id);
|
||||
|
||||
// 2. Préserver les avis rédigés en les dissociant du compte (deviennent anonymes / "Utilisateur supprimé")
|
||||
await prisma.rating.updateMany({
|
||||
where: {
|
||||
userId: { in: userIds }
|
||||
},
|
||||
data: {
|
||||
userId: null
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Suppression définitive des comptes utilisateurs
|
||||
const deleteResult = await prisma.user.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
in: usersToDelete.map(u => u.id)
|
||||
in: userIds
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -131,10 +131,16 @@ export default async function ModerationPage({
|
||||
<div>
|
||||
<div className="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-1.5">Auteur</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-9 h-9 rounded-full bg-slate-800 flex items-center justify-center text-brand-400 font-bold text-sm uppercase">{r.user.name.charAt(0)}</div>
|
||||
<div className="w-9 h-9 rounded-full bg-slate-800 flex items-center justify-center text-brand-400 font-bold text-sm uppercase">
|
||||
{r.user?.name ? r.user.name.charAt(0) : '?'}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-bold text-white">{r.user.name}</div>
|
||||
<div className="text-[10px] text-slate-500">{r.user.email}</div>
|
||||
<div className="text-sm font-bold text-white">
|
||||
{r.user?.name || "Utilisateur supprimé"}
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-500">
|
||||
{r.user?.email || "Compte clôturé"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,7 +148,9 @@ export default async function ModerationPage({
|
||||
<div>
|
||||
<div className="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-1.5">Entrepreneur Concerné</div>
|
||||
<div className="bg-slate-900/80 p-2.5 rounded-lg border border-slate-800">
|
||||
<div className="text-sm font-bold text-indigo-400">{r.business.name}</div>
|
||||
<div className="text-sm font-bold text-indigo-400">
|
||||
{r.business?.name || "Entreprise introuvable ou supprimée"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user