src/app/api/admin/auth/password/route.ts
Annotation non disponible
Lance npm run annotate (nécessite ANTHROPIC_API_KEY dans .env.local) pour générer une annotation française par Claude Haiku 4.5.
Concepts détectés — comprends la théorie
ORM Prisma
4 occurrencesCe fichier accède à la base de données via Prisma. Prisma est l'ORM utilisé côté backend pour les requêtes typées sur PostgreSQL.
Voir l'article général
Route API Next.js
3 occurrencesCe fichier est une route API Next.js (App Router). Voir le contrat API complet pour les conventions de réponse et d'auth.
Voir l'article général
2 exports
POSTdynamic
Code source· typescript
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import bcrypt from "bcryptjs";
import { isValidCodeFormat, normalizeCode } from "@/lib/admin/code-acces";
export const dynamic = "force-dynamic";
// POST /api/admin/auth/password
// Body: { code: "WARI-XXXX-XXXX", password, confirm }
// Force min 8 chars. Passe à onboardingStep=VITRINE (Sprint 4 prendra la suite).
export async function POST(req: NextRequest) {
let body: { code?: string; password?: string; confirm?: string };
try {
body = await req.json();
} catch {
return NextResponse.json({ error: "Body invalide" }, { status: 400 });
}
const code = normalizeCode(body.code ?? "");
const password = body.password ?? "";
const confirm = body.confirm ?? "";
if (!isValidCodeFormat(code)) {
return NextResponse.json({ error: "Code d'accès invalide" }, { status: 400 });
}
if (password.length < 8) {
return NextResponse.json(
{ error: "Le mot de passe doit contenir au moins 8 caractères." },
{ status: 400 }
);
}
if (password !== confirm) {
return NextResponse.json(
{ error: "Les deux mots de passe ne correspondent pas." },
{ status: 400 }
);
}
const tenant = await prisma.tenant.findUnique({
where: { codeAcces: code },
select: {
id: true,
onboardingStep: true,
actif: true,
users: {
where: { isPrimary: true },
select: { id: true, username: true, emailVerifiedAt: true },
take: 1,
},
},
});
if (!tenant) {
return NextResponse.json({ error: "Code invalide" }, { status: 404 });
}
if (!tenant.actif || tenant.onboardingStep === "DONE") {
return NextResponse.json({ error: "Setup non disponible" }, { status: 422 });
}
const primary = tenant.users[0];
if (!primary || !primary.emailVerifiedAt || !primary.username) {
return NextResponse.json(
{
error:
"Complète d'abord l'email et le username avant de définir un mot de passe.",
},
{ status: 422 }
);
}
const motDePasseHash = await bcrypt.hash(password, 10);
await prisma.user.update({
where: { id: primary.id },
data: { motDePasseHash },
});
await prisma.tenant.update({
where: { id: tenant.id },
data: { onboardingStep: "VITRINE" },
});
return NextResponse.json({ ok: true });
}
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import bcrypt from "bcryptjs";
import { isValidCodeFormat, normalizeCode } from "@/lib/admin/code-acces";
export const dynamic = "force-dynamic";
// POST /api/admin/auth/password
// Body: { code: "WARI-XXXX-XXXX", password, confirm }
// Force min 8 chars. Passe à onboardingStep=VITRINE (Sprint 4 prendra la suite).
export async function POST(req: NextRequest) {
let body: { code?: string; password?: string; confirm?: string };
try {
body = await req.json();
} catch {
return NextResponse.json({ error: "Body invalide" }, { status: 400 });
}
const code = normalizeCode(body.code ?? "");
const password = body.password ?? "";
const confirm = body.confirm ?? "";
if (!isValidCodeFormat(code)) {
return NextResponse.json({ error: "Code d'accès invalide" }, { status: 400 });
}
if (password.length < 8) {
return NextResponse.json(
{ error: "Le mot de passe doit contenir au moins 8 caractères." },
{ status: 400 }
);
}
if (password !== confirm) {
return NextResponse.json(
{ error: "Les deux mots de passe ne correspondent pas." },
{ status: 400 }
);
}
const tenant = await prisma.tenant.findUnique({
where: { codeAcces: code },
select: {
id: true,
onboardingStep: true,
actif: true,
users: {
where: { isPrimary: true },
select: { id: true, username: true, emailVerifiedAt: true },
take: 1,
},
},
});
if (!tenant) {
return NextResponse.json({ error: "Code invalide" }, { status: 404 });
}
if (!tenant.actif || tenant.onboardingStep === "DONE") {
return NextResponse.json({ error: "Setup non disponible" }, { status: 422 });
}
const primary = tenant.users[0];
if (!primary || !primary.emailVerifiedAt || !primary.username) {
return NextResponse.json(
{
error:
"Complète d'abord l'email et le username avant de définir un mot de passe.",
},
{ status: 422 }
);
}
const motDePasseHash = await bcrypt.hash(password, 10);
await prisma.user.update({
where: { id: primary.id },
data: { motDePasseHash },
});
await prisma.tenant.update({
where: { id: tenant.id },
data: { onboardingStep: "VITRINE" },
});
return NextResponse.json({ ok: true });
}