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:
@@ -15,6 +15,9 @@
|
|||||||
| `POST /api/komga/config` | `saveKomgaConfig()` | ✅ Done |
|
| `POST /api/komga/config` | `saveKomgaConfig()` | ✅ Done |
|
||||||
| `PUT /api/user/password` | `changePassword()` | ✅ Done |
|
| `PUT /api/user/password` | `changePassword()` | ✅ Done |
|
||||||
| `POST /api/auth/register` | `registerUser()` | ✅ Done |
|
| `POST /api/auth/register` | `registerUser()` | ✅ Done |
|
||||||
|
| `PATCH /api/admin/users/[userId]` | `updateUserRoles()` | ✅ Done |
|
||||||
|
| `DELETE /api/admin/users/[userId]` | `deleteUser()` | ✅ Done |
|
||||||
|
| `PUT /api/admin/users/[userId]/password` | `resetUserPassword()` | ✅ Done |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
72
src/app/actions/admin.ts
Normal file
72
src/app/actions/admin.ts
Normal 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" };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import type { AdminUserData } from "@/lib/services/admin.service";
|
import type { AdminUserData } from "@/lib/services/admin.service";
|
||||||
|
import { deleteUser } from "@/app/actions/admin";
|
||||||
|
|
||||||
interface DeleteUserDialogProps {
|
interface DeleteUserDialogProps {
|
||||||
user: AdminUserData;
|
user: AdminUserData;
|
||||||
@@ -29,13 +30,10 @@ export function DeleteUserDialog({ user, open, onOpenChange, onSuccess }: Delete
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/admin/users/${user.id}`, {
|
const result = await deleteUser(user.id);
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!result.success) {
|
||||||
const data = await response.json();
|
throw new Error(result.message);
|
||||||
throw new Error(data.error || "Erreur lors de la suppression");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import type { AdminUserData } from "@/lib/services/admin.service";
|
import type { AdminUserData } from "@/lib/services/admin.service";
|
||||||
|
import { updateUserRoles } from "@/app/actions/admin";
|
||||||
|
|
||||||
interface EditUserDialogProps {
|
interface EditUserDialogProps {
|
||||||
user: AdminUserData;
|
user: AdminUserData;
|
||||||
@@ -51,15 +52,10 @@ export function EditUserDialog({ user, open, onOpenChange, onSuccess }: EditUser
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/admin/users/${user.id}`, {
|
const result = await updateUserRoles(user.id, selectedRoles);
|
||||||
method: "PATCH",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ roles: selectedRoles }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!result.success) {
|
||||||
const data = await response.json();
|
throw new Error(result.message);
|
||||||
throw new Error(data.error || "Erreur lors de la mise à jour");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { Lock } from "lucide-react";
|
import { Lock } from "lucide-react";
|
||||||
import type { AdminUserData } from "@/lib/services/admin.service";
|
import type { AdminUserData } from "@/lib/services/admin.service";
|
||||||
|
import { resetUserPassword } from "@/app/actions/admin";
|
||||||
|
|
||||||
interface ResetPasswordDialogProps {
|
interface ResetPasswordDialogProps {
|
||||||
user: AdminUserData;
|
user: AdminUserData;
|
||||||
@@ -65,15 +66,10 @@ export function ResetPasswordDialog({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/admin/users/${user.id}/password`, {
|
const result = await resetUserPassword(user.id, newPassword);
|
||||||
method: "PUT",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ newPassword }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!result.success) {
|
||||||
const data = await response.json();
|
throw new Error(result.message);
|
||||||
throw new Error(data.error || "Erreur lors de la réinitialisation");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
Reference in New Issue
Block a user