src/app/api/admin/auth/username/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
5 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 { isValidCodeFormat, normalizeCode } from "@/lib/admin/code-acces";
export const dynamic = "force-dynamic";
const USERNAME_REGEX = /^[a-z0-9_-]{3,30}$/;
// POST /api/admin/auth/username
// Body: { code: "WARI-XXXX-XXXX", username: "..." }
// Check unicité globale + format, passe à onboardingStep=PWD.
export async function POST(req: NextRequest) {
let body: { code?: string; username?: string };
try {
body = await req.json();
} catch {
return NextResponse.json({ error: "Body invalide" }, { status: 400 });
}
const code = normalizeCode(body.code ?? "");
const usernameRaw = (body.username ?? "").trim().toLowerCase();
if (!isValidCodeFormat(code)) {
return NextResponse.json({ error: "Code d'accès invalide" }, { status: 400 });
}
if (!USERNAME_REGEX.test(usernameRaw)) {
return NextResponse.json(
{
error:
"Username invalide. 3 à 30 caractères, lettres minuscules, chiffres, tirets et underscores uniquement.",
},
{ 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, 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) {
return NextResponse.json(
{ error: "Vérifie d'abord ton email avant de choisir un username." },
{ status: 422 }
);
}
// Check unicité username globale (exclut le user courant si c'est le même)
const existant = await prisma.user.findFirst({
where: { username: usernameRaw, NOT: { id: primary.id } },
select: { id: true },
});
if (existant) {
return NextResponse.json(
{ error: "Ce nom d'utilisateur est déjà pris. Essaie une variante." },
{ status: 409 }
);
}
await prisma.user.update({
where: { id: primary.id },
data: { username: usernameRaw },
});
await prisma.tenant.update({
where: { id: tenant.id },
data: { onboardingStep: "PWD" },
});
return NextResponse.json({ ok: true, username: usernameRaw });
}
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import { isValidCodeFormat, normalizeCode } from "@/lib/admin/code-acces";
export const dynamic = "force-dynamic";
const USERNAME_REGEX = /^[a-z0-9_-]{3,30}$/;
// POST /api/admin/auth/username
// Body: { code: "WARI-XXXX-XXXX", username: "..." }
// Check unicité globale + format, passe à onboardingStep=PWD.
export async function POST(req: NextRequest) {
let body: { code?: string; username?: string };
try {
body = await req.json();
} catch {
return NextResponse.json({ error: "Body invalide" }, { status: 400 });
}
const code = normalizeCode(body.code ?? "");
const usernameRaw = (body.username ?? "").trim().toLowerCase();
if (!isValidCodeFormat(code)) {
return NextResponse.json({ error: "Code d'accès invalide" }, { status: 400 });
}
if (!USERNAME_REGEX.test(usernameRaw)) {
return NextResponse.json(
{
error:
"Username invalide. 3 à 30 caractères, lettres minuscules, chiffres, tirets et underscores uniquement.",
},
{ 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, 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) {
return NextResponse.json(
{ error: "Vérifie d'abord ton email avant de choisir un username." },
{ status: 422 }
);
}
// Check unicité username globale (exclut le user courant si c'est le même)
const existant = await prisma.user.findFirst({
where: { username: usernameRaw, NOT: { id: primary.id } },
select: { id: true },
});
if (existant) {
return NextResponse.json(
{ error: "Ce nom d'utilisateur est déjà pris. Essaie une variante." },
{ status: 409 }
);
}
await prisma.user.update({
where: { id: primary.id },
data: { username: usernameRaw },
});
await prisma.tenant.update({
where: { id: tenant.id },
data: { onboardingStep: "PWD" },
});
return NextResponse.json({ ok: true, username: usernameRaw });
}