gestion supp user admin
This commit is contained in:
@@ -41,3 +41,95 @@ export async function verifyUserEmail(userId: string) {
|
||||
return { success: false, error: "Erreur lors de la vérification de l'email" };
|
||||
}
|
||||
}
|
||||
export async function scheduleUserDeletion(userId: string, reason: string, delayDays: number) {
|
||||
try {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId }
|
||||
});
|
||||
|
||||
if (!user) return { success: false, error: "Utilisateur non trouvé" };
|
||||
|
||||
const settings = await prisma.siteSetting.findUnique({
|
||||
where: { id: "singleton" }
|
||||
});
|
||||
|
||||
const deletionDate = new Date();
|
||||
deletionDate.setDate(deletionDate.getDate() + delayDays);
|
||||
|
||||
if (delayDays === 0) {
|
||||
// Immediate deletion
|
||||
await prisma.user.delete({
|
||||
where: { id: userId }
|
||||
});
|
||||
return { success: true, message: "Supprimé immédiatement" };
|
||||
}
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
deletedAt: new Date(),
|
||||
deletionScheduledAt: deletionDate,
|
||||
deletionReason: reason
|
||||
}
|
||||
});
|
||||
|
||||
// Send notification email
|
||||
if (settings?.deleteAccountTemplate) {
|
||||
const { sendEmail } = await import("@/../../lib/mail");
|
||||
const html = settings.deleteAccountTemplate
|
||||
.replace(/{name}/g, user.name)
|
||||
.replace(/{siteName}/g, settings.siteName)
|
||||
.replace(/{deletionDate}/g, delayDays === 0 ? "immédiatement" : deletionDate.toLocaleDateString("fr-FR"))
|
||||
.replace(/{reason}/g, reason)
|
||||
.replace(/{reactivateUrl}/g, `${process.env.NEXT_PUBLIC_APP_URL}/auth/reactivate?id=${user.id}`);
|
||||
|
||||
await sendEmail({
|
||||
to: user.email,
|
||||
subject: settings.deleteAccountSubject,
|
||||
html
|
||||
});
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Failed to schedule user deletion:", error);
|
||||
return { success: false, error: "Erreur lors de la programmation de la suppression" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function reactivateUser(userId: string, reason: string) {
|
||||
try {
|
||||
const user = await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
deletedAt: null,
|
||||
deletionScheduledAt: null,
|
||||
reactivationReason: reason
|
||||
}
|
||||
});
|
||||
|
||||
const settings = await prisma.siteSetting.findUnique({
|
||||
where: { id: "singleton" }
|
||||
});
|
||||
|
||||
// Send confirmation email
|
||||
if (settings?.reactivateAccountTemplate) {
|
||||
const { sendEmail } = await import("@/../../lib/mail");
|
||||
const html = settings.reactivateAccountTemplate
|
||||
.replace(/{name}/g, user.name)
|
||||
.replace(/{siteName}/g, settings.siteName)
|
||||
.replace(/{reason}/g, reason);
|
||||
|
||||
await sendEmail({
|
||||
to: user.email,
|
||||
subject: settings.reactivateAccountSubject,
|
||||
html
|
||||
});
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Failed to reactivate user:", error);
|
||||
return { success: false, error: "Erreur lors de la réactivation de l'utilisateur" };
|
||||
}
|
||||
}
|
||||
|
||||
51
admin/src/app/api/cron/cleanup-users/route.ts
Normal file
51
admin/src/app/api/cron/cleanup-users/route.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
/**
|
||||
* Route pour le nettoyage automatique des comptes
|
||||
* À appeler via une tâche cron (ex: chaque nuit à minuit)
|
||||
* GET /api/cron/cleanup-users?key=VOTRE_CLE_SECRET
|
||||
*/
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const key = searchParams.get("key");
|
||||
|
||||
if (key !== process.env.CRON_SECRET) {
|
||||
return NextResponse.json({ error: "Non autorisé" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const now = new Date();
|
||||
|
||||
// 1. Trouver les utilisateurs dont le délai est passé
|
||||
const usersToDelete = await prisma.user.findMany({
|
||||
where: {
|
||||
deletionScheduledAt: {
|
||||
lte: now
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (usersToDelete.length === 0) {
|
||||
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 deleteResult = await prisma.user.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
in: usersToDelete.map(u => u.id)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
message: `${deleteResult.count} compte(s) supprimé(s) définitivement`,
|
||||
deletedIds: usersToDelete.map(u => u.id)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Cron Cleanup Error:", error);
|
||||
return NextResponse.json({ error: "Erreur lors du nettoyage" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import EntrepreneurActions from '@/components/EntrepreneurActions';
|
||||
import PlanSelector from '@/components/PlanSelector';
|
||||
import ClientActions from "@/components/ClientActions";
|
||||
import ManualVerifyButton from "@/components/ManualVerifyButton";
|
||||
import { ShieldAlert, ShieldX, ShieldCheck, Users, Briefcase, User, Mail, Phone, MapPin, MailCheck, MailX } from 'lucide-react';
|
||||
import { Trash2, ShieldAlert, ShieldX, ShieldCheck, Users, Briefcase, User, Mail, Phone, MapPin, MailCheck, MailX } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
async function getBusinesses() {
|
||||
@@ -109,7 +109,7 @@ export default async function UsersPage({
|
||||
</tr>
|
||||
) : (
|
||||
businesses.map((business: any) => (
|
||||
<tr key={business.id} className={(business.isSuspended || business.owner?.isSuspended) ? 'opacity-60 grayscale-[0.5]' : ''}>
|
||||
<tr key={business.id} className={`transition-colors ${business.owner?.deletedAt ? 'bg-red-500/5 opacity-80' : (business.isSuspended || business.owner?.isSuspended) ? 'opacity-60 grayscale-[0.5]' : ''}`}>
|
||||
<td>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-lg bg-slate-800 flex items-center justify-center overflow-hidden">
|
||||
@@ -148,7 +148,11 @@ export default async function UsersPage({
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex flex-col gap-1">
|
||||
{business.owner?.isSuspended ? (
|
||||
{business.owner?.deletedAt ? (
|
||||
<span className="badge bg-red-500/10 text-red-500 border border-red-500/20 flex items-center gap-1">
|
||||
<Trash2 className="w-3 h-3" /> Suppression en cours
|
||||
</span>
|
||||
) : business.owner?.isSuspended ? (
|
||||
<span className="badge badge-error flex items-center gap-1">
|
||||
<ShieldX className="w-3 h-3" /> Compte Suspendu
|
||||
</span>
|
||||
@@ -218,7 +222,7 @@ export default async function UsersPage({
|
||||
</tr>
|
||||
) : (
|
||||
clients.map((client: any) => (
|
||||
<tr key={client.id} className={`hover:bg-slate-800/30 transition-colors ${client.isSuspended ? 'opacity-60 grayscale-[0.5]' : ''}`}>
|
||||
<tr key={client.id} className={`hover:bg-slate-800/30 transition-colors ${client.deletedAt ? 'bg-red-500/5 opacity-80' : client.isSuspended ? 'opacity-60 grayscale-[0.5]' : ''}`}>
|
||||
<td className="py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-slate-800 flex items-center justify-center text-slate-400 font-bold text-sm overflow-hidden border border-slate-700">
|
||||
@@ -270,7 +274,11 @@ export default async function UsersPage({
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex flex-col gap-1">
|
||||
{client.isSuspended ? (
|
||||
{client.deletedAt ? (
|
||||
<span className="badge bg-red-500/10 text-red-500 border border-red-500/20 flex items-center gap-1">
|
||||
<Trash2 className="w-3 h-3" /> Suppression en cours
|
||||
</span>
|
||||
) : client.isSuspended ? (
|
||||
<>
|
||||
<span className="badge badge-error">Compte Suspendu</span>
|
||||
<span className="text-[10px] text-rose-500/70 truncate max-w-[150px]" title={client.suspensionReason}>
|
||||
|
||||
Reference in New Issue
Block a user