refactor: déduplication — helpers actions, parseurs partagés, types auth
- Crée src/lib/action-helpers.ts avec ActionResult, requireAuth(), requireEvaluationAccess() — type et pattern dupliqués 3× supprimés - evaluations.ts, share.ts, admin.ts importent depuis action-helpers; admin.ts: "Forbidden" → "Accès refusé" pour cohérence - parseQuestions/parseRubric exportées depuis export-utils et supprimées de DimensionCard (copie exacte retirée) - next-auth.d.ts: Session.user.role passe de optional à required string Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
"use server";
|
||||
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { requireAuth, type ActionResult } from "@/lib/action-helpers";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export type ActionResult<T = void> = { success: true; data?: T } | { success: false; error: string };
|
||||
export type { ActionResult };
|
||||
|
||||
export async function setUserRole(userId: string, role: "admin" | "evaluator"): Promise<ActionResult> {
|
||||
const session = await auth();
|
||||
if (session?.user?.role !== "admin") return { success: false, error: "Forbidden" };
|
||||
const session = await requireAuth();
|
||||
if (!session || session.user.role !== "admin") return { success: false, error: "Accès refusé" };
|
||||
|
||||
if (!role || !["admin", "evaluator"].includes(role)) {
|
||||
return { success: false, error: "Rôle invalide (admin | evaluator)" };
|
||||
@@ -25,8 +25,8 @@ export async function setUserRole(userId: string, role: "admin" | "evaluator"):
|
||||
}
|
||||
|
||||
export async function deleteUser(userId: string): Promise<ActionResult> {
|
||||
const session = await auth();
|
||||
if (session?.user?.role !== "admin") return { success: false, error: "Forbidden" };
|
||||
const session = await requireAuth();
|
||||
if (!session || session.user.role !== "admin") return { success: false, error: "Accès refusé" };
|
||||
|
||||
if (userId === session.user.id) {
|
||||
return { success: false, error: "Impossible de supprimer votre propre compte" };
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
"use server";
|
||||
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { canAccessEvaluation } from "@/lib/evaluation-access";
|
||||
import { getEvaluation } from "@/lib/server-data";
|
||||
import { requireAuth, requireEvaluationAccess, type ActionResult } from "@/lib/action-helpers";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export type ActionResult<T = void> = { success: true; data?: T } | { success: false; error: string };
|
||||
export type { ActionResult };
|
||||
|
||||
export async function fetchEvaluation(id: string): Promise<ActionResult<Awaited<ReturnType<typeof getEvaluation>>>> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "Non authentifié" };
|
||||
const session = await requireAuth();
|
||||
if (!session) return { success: false, error: "Non authentifié" };
|
||||
|
||||
const evaluation = await getEvaluation(id);
|
||||
if (!evaluation) return { success: false, error: "Évaluation introuvable" };
|
||||
@@ -19,10 +18,10 @@ export async function fetchEvaluation(id: string): Promise<ActionResult<Awaited<
|
||||
}
|
||||
|
||||
export async function deleteEvaluation(id: string): Promise<ActionResult> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "Non authentifié" };
|
||||
const session = await requireAuth();
|
||||
if (!session) return { success: false, error: "Non authentifié" };
|
||||
|
||||
const hasAccess = await canAccessEvaluation(id, session.user.id, session.user.role === "admin");
|
||||
const hasAccess = await requireEvaluationAccess(id, session.user.id, session.user.role === "admin");
|
||||
if (!hasAccess) return { success: false, error: "Accès refusé" };
|
||||
|
||||
try {
|
||||
@@ -42,8 +41,8 @@ export async function createEvaluation(data: {
|
||||
evaluationDate: string;
|
||||
templateId: string;
|
||||
}): Promise<ActionResult<{ id: string }>> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "Non authentifié" };
|
||||
const session = await requireAuth();
|
||||
if (!session) return { success: false, error: "Non authentifié" };
|
||||
|
||||
const { candidateName, candidateRole, candidateTeam, evaluationDate, templateId } = data;
|
||||
if (!candidateName || !candidateRole || !evaluationDate || !templateId) {
|
||||
@@ -113,10 +112,10 @@ export async function updateDimensionScore(
|
||||
dimensionId: string,
|
||||
data: { score?: number | null; justification?: string | null; examplesObserved?: string | null; confidence?: string | null; candidateNotes?: string | null }
|
||||
): Promise<ActionResult> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "Non authentifié" };
|
||||
const session = await requireAuth();
|
||||
if (!session) return { success: false, error: "Non authentifié" };
|
||||
|
||||
const hasAccess = await canAccessEvaluation(evaluationId, session.user.id, session.user.role === "admin");
|
||||
const hasAccess = await requireEvaluationAccess(evaluationId, session.user.id, session.user.role === "admin");
|
||||
if (!hasAccess) return { success: false, error: "Accès refusé" };
|
||||
|
||||
try {
|
||||
@@ -133,10 +132,10 @@ export async function updateDimensionScore(
|
||||
}
|
||||
|
||||
export async function updateEvaluation(id: string, data: UpdateEvaluationInput): Promise<ActionResult> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "Non authentifié" };
|
||||
const session = await requireAuth();
|
||||
if (!session) return { success: false, error: "Non authentifié" };
|
||||
|
||||
const hasAccess = await canAccessEvaluation(id, session.user.id, session.user.role === "admin");
|
||||
const hasAccess = await requireEvaluationAccess(id, session.user.id, session.user.role === "admin");
|
||||
if (!hasAccess) return { success: false, error: "Accès refusé" };
|
||||
|
||||
const existing = await prisma.evaluation.findUnique({ where: { id } });
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
"use server";
|
||||
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { canAccessEvaluation } from "@/lib/evaluation-access";
|
||||
import { requireAuth, requireEvaluationAccess, type ActionResult } from "@/lib/action-helpers";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export type ActionResult<T = void> = { success: true; data?: T } | { success: false; error: string };
|
||||
export type { ActionResult };
|
||||
|
||||
export async function addShare(evaluationId: string, userId: string): Promise<ActionResult> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "Non authentifié" };
|
||||
const session = await requireAuth();
|
||||
if (!session) return { success: false, error: "Non authentifié" };
|
||||
|
||||
const hasAccess = await canAccessEvaluation(
|
||||
evaluationId,
|
||||
session.user.id,
|
||||
session.user.role === "admin"
|
||||
);
|
||||
const hasAccess = await requireEvaluationAccess(evaluationId, session.user.id, session.user.role === "admin");
|
||||
if (!hasAccess) return { success: false, error: "Accès refusé" };
|
||||
|
||||
if (userId === session.user.id) return { success: false, error: "Vous avez déjà accès" };
|
||||
@@ -43,14 +38,10 @@ export async function addShare(evaluationId: string, userId: string): Promise<Ac
|
||||
}
|
||||
|
||||
export async function removeShare(evaluationId: string, userId: string): Promise<ActionResult> {
|
||||
const session = await auth();
|
||||
if (!session?.user) return { success: false, error: "Non authentifié" };
|
||||
const session = await requireAuth();
|
||||
if (!session) return { success: false, error: "Non authentifié" };
|
||||
|
||||
const hasAccess = await canAccessEvaluation(
|
||||
evaluationId,
|
||||
session.user.id,
|
||||
session.user.role === "admin"
|
||||
);
|
||||
const hasAccess = await requireEvaluationAccess(evaluationId, session.user.id, session.user.role === "admin");
|
||||
if (!hasAccess) return { success: false, error: "Accès refusé" };
|
||||
|
||||
try {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { updateDimensionScore } from "@/actions/evaluations";
|
||||
import { parseQuestions, parseRubric } from "@/lib/export-utils";
|
||||
|
||||
const STORAGE_KEY_PREFIX = "eval-dim-expanded";
|
||||
|
||||
@@ -56,25 +57,6 @@ interface DimensionCardProps {
|
||||
collapseAllTrigger?: number;
|
||||
}
|
||||
|
||||
function parseRubric(rubric: string): string[] {
|
||||
if (rubric === "1-5" || !rubric) return ["1", "2", "3", "4", "5"];
|
||||
const labels: string[] = [];
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const m = rubric.match(new RegExp(`${i}:([^;]+)`));
|
||||
labels.push(m ? m[1].trim() : String(i));
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
function parseQuestions(s: string | null | undefined): string[] {
|
||||
if (!s) return [];
|
||||
try {
|
||||
const arr = JSON.parse(s) as unknown;
|
||||
return Array.isArray(arr) ? arr.filter((x): x is string => typeof x === "string") : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const inputClass =
|
||||
"w-full rounded border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-700/80 px-2.5 py-1.5 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 dark:placeholder-zinc-500 focus:border-cyan-500 focus:ring-1 focus:ring-cyan-500/30";
|
||||
|
||||
15
src/lib/action-helpers.ts
Normal file
15
src/lib/action-helpers.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { auth } from "@/auth";
|
||||
import { canAccessEvaluation } from "@/lib/evaluation-access";
|
||||
|
||||
export type ActionResult<T = void> = { success: true; data?: T } | { success: false; error: string };
|
||||
|
||||
export async function requireAuth() {
|
||||
const session = await auth();
|
||||
if (!session?.user) return null;
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function requireEvaluationAccess(evaluationId: string, userId: string, isAdmin: boolean) {
|
||||
const hasAccess = await canAccessEvaluation(evaluationId, userId, isAdmin);
|
||||
return hasAccess;
|
||||
}
|
||||
@@ -6,7 +6,7 @@ export interface EvaluationWithScores extends Evaluation {
|
||||
}
|
||||
|
||||
/** Parse suggestedQuestions JSON array */
|
||||
function parseQuestions(s: string | null | undefined): string[] {
|
||||
export function parseQuestions(s: string | null | undefined): string[] {
|
||||
if (!s) return [];
|
||||
try {
|
||||
const arr = JSON.parse(s) as unknown;
|
||||
@@ -17,7 +17,7 @@ function parseQuestions(s: string | null | undefined): string[] {
|
||||
}
|
||||
|
||||
/** Parse rubric "1:X;2:Y;..." into labels */
|
||||
function parseRubric(rubric: string): string[] {
|
||||
export function parseRubric(rubric: string): string[] {
|
||||
if (rubric === "1-5" || !rubric) return ["1", "2", "3", "4", "5"];
|
||||
const labels: string[] = [];
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
|
||||
2
src/types/next-auth.d.ts
vendored
2
src/types/next-auth.d.ts
vendored
@@ -10,7 +10,7 @@ declare module "next-auth" {
|
||||
id: string;
|
||||
email?: string | null;
|
||||
name?: string | null;
|
||||
role?: string;
|
||||
role: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user