refactor: convert admin user management to Server Actions

- Add src/app/actions/admin.ts with updateUserRoles, deleteUser, resetUserPassword
- Update EditUserDialog, DeleteUserDialog, ResetPasswordDialog to use Server Actions
- Remove admin users API routes (PATCH/DELETE/PUT)
This commit is contained in:
2026-02-28 11:06:42 +01:00
parent 7134c069d7
commit b40f59bec6
7 changed files with 87 additions and 164 deletions

72
src/app/actions/admin.ts Normal file
View File

@@ -0,0 +1,72 @@
"use server";
import { AdminService } from "@/lib/services/admin.service";
import { ERROR_CODES } from "@/constants/errorCodes";
import { AppError } from "@/utils/errors";
import { AuthServerService } from "@/lib/services/auth-server.service";
/**
* Met à jour les rôles d'un utilisateur
*/
export async function updateUserRoles(
userId: string,
roles: string[]
): Promise<{ success: boolean; message: string }> {
try {
if (roles.length === 0) {
return { success: false, message: "L'utilisateur doit avoir au moins un rôle" };
}
await AdminService.updateUserRoles(userId, roles);
return { success: true, message: "Rôles mis à jour" };
} catch (error) {
if (error instanceof AppError) {
return { success: false, message: error.message };
}
return { success: false, message: "Erreur lors de la mise à jour des rôles" };
}
}
/**
* Supprime un utilisateur
*/
export async function deleteUser(
userId: string
): Promise<{ success: boolean; message: string }> {
try {
await AdminService.deleteUser(userId);
return { success: true, message: "Utilisateur supprimé" };
} catch (error) {
if (error instanceof AppError) {
return { success: false, message: error.message };
}
return { success: false, message: "Erreur lors de la suppression" };
}
}
/**
* Réinitialise le mot de passe d'un utilisateur
*/
export async function resetUserPassword(
userId: string,
newPassword: string
): Promise<{ success: boolean; message: string }> {
try {
if (!AuthServerService.isPasswordStrong(newPassword)) {
return {
success: false,
message: "Le mot de passe doit contenir au moins 8 caractères, une majuscule et un chiffre",
};
}
await AdminService.resetUserPassword(userId, newPassword);
return { success: true, message: "Mot de passe réinitialisé" };
} catch (error) {
if (error instanceof AppError) {
return { success: false, message: error.message };
}
return { success: false, message: "Erreur lors de la réinitialisation du mot de passe" };
}
}

View File

@@ -1,59 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { AdminService } from "@/lib/services/admin.service";
import { AppError } from "@/utils/errors";
import { AuthServerService } from "@/lib/services/auth-server.service";
import logger from "@/lib/logger";
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ userId: string }> }
) {
try {
const { userId } = await params;
const body = await request.json();
const { newPassword } = body;
if (!newPassword) {
return NextResponse.json({ error: "Nouveau mot de passe manquant" }, { status: 400 });
}
// Vérifier que le mot de passe est fort
if (!AuthServerService.isPasswordStrong(newPassword)) {
return NextResponse.json(
{
error: "Le mot de passe doit contenir au moins 8 caractères, une majuscule et un chiffre",
},
{ status: 400 }
);
}
await AdminService.resetUserPassword(userId, newPassword);
return NextResponse.json({ success: true });
} catch (error) {
logger.error({ err: error }, "Erreur lors de la réinitialisation du mot de passe:");
if (error instanceof AppError) {
return NextResponse.json(
{ error: error.message, code: error.code },
{
status:
error.code === "AUTH_FORBIDDEN"
? 403
: error.code === "AUTH_UNAUTHENTICATED"
? 401
: error.code === "AUTH_USER_NOT_FOUND"
? 404
: error.code === "ADMIN_CANNOT_RESET_OWN_PASSWORD"
? 400
: 500,
}
);
}
return NextResponse.json(
{ error: "Erreur lors de la réinitialisation du mot de passe" },
{ status: 500 }
);
}
}

View File

@@ -1,83 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { AdminService } from "@/lib/services/admin.service";
import { AppError } from "@/utils/errors";
import logger from "@/lib/logger";
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ userId: string }> }
) {
try {
const { userId } = await params;
const body = await request.json();
const { roles } = body;
if (!roles || !Array.isArray(roles)) {
return NextResponse.json({ error: "Rôles invalides" }, { status: 400 });
}
await AdminService.updateUserRoles(userId, roles);
return NextResponse.json({ success: true });
} catch (error) {
logger.error({ err: error }, "Erreur lors de la mise à jour de l'utilisateur:");
if (error instanceof AppError) {
return NextResponse.json(
{ error: error.message, code: error.code },
{
status:
error.code === "AUTH_FORBIDDEN"
? 403
: error.code === "AUTH_UNAUTHENTICATED"
? 401
: error.code === "AUTH_USER_NOT_FOUND"
? 404
: 500,
}
);
}
return NextResponse.json(
{ error: "Erreur lors de la mise à jour de l'utilisateur" },
{ status: 500 }
);
}
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ userId: string }> }
) {
try {
const { userId } = await params;
await AdminService.deleteUser(userId);
return NextResponse.json({ success: true });
} catch (error) {
logger.error({ err: error }, "Erreur lors de la suppression de l'utilisateur:");
if (error instanceof AppError) {
return NextResponse.json(
{ error: error.message, code: error.code },
{
status:
error.code === "AUTH_FORBIDDEN"
? 403
: error.code === "AUTH_UNAUTHENTICATED"
? 401
: error.code === "AUTH_USER_NOT_FOUND"
? 404
: error.code === "ADMIN_CANNOT_DELETE_SELF"
? 400
: 500,
}
);
}
return NextResponse.json(
{ error: "Erreur lors de la suppression de l'utilisateur" },
{ status: 500 }
);
}
}

View File

@@ -13,6 +13,7 @@ import {
} from "@/components/ui/alert-dialog";
import { useToast } from "@/components/ui/use-toast";
import type { AdminUserData } from "@/lib/services/admin.service";
import { deleteUser } from "@/app/actions/admin";
interface DeleteUserDialogProps {
user: AdminUserData;
@@ -29,13 +30,10 @@ export function DeleteUserDialog({ user, open, onOpenChange, onSuccess }: Delete
setIsLoading(true);
try {
const response = await fetch(`/api/admin/users/${user.id}`, {
method: "DELETE",
});
const result = await deleteUser(user.id);
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || "Erreur lors de la suppression");
if (!result.success) {
throw new Error(result.message);
}
toast({

View File

@@ -14,6 +14,7 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { useToast } from "@/components/ui/use-toast";
import type { AdminUserData } from "@/lib/services/admin.service";
import { updateUserRoles } from "@/app/actions/admin";
interface EditUserDialogProps {
user: AdminUserData;
@@ -51,15 +52,10 @@ export function EditUserDialog({ user, open, onOpenChange, onSuccess }: EditUser
setIsLoading(true);
try {
const response = await fetch(`/api/admin/users/${user.id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ roles: selectedRoles }),
});
const result = await updateUserRoles(user.id, selectedRoles);
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || "Erreur lors de la mise à jour");
if (!result.success) {
throw new Error(result.message);
}
toast({

View File

@@ -15,6 +15,7 @@ import { Label } from "@/components/ui/label";
import { useToast } from "@/components/ui/use-toast";
import { Lock } from "lucide-react";
import type { AdminUserData } from "@/lib/services/admin.service";
import { resetUserPassword } from "@/app/actions/admin";
interface ResetPasswordDialogProps {
user: AdminUserData;
@@ -65,15 +66,10 @@ export function ResetPasswordDialog({
setIsLoading(true);
try {
const response = await fetch(`/api/admin/users/${user.id}/password`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ newPassword }),
});
const result = await resetUserPassword(user.id, newPassword);
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || "Erreur lors de la réinitialisation");
if (!result.success) {
throw new Error(result.message);
}
toast({