src/app/api/admin/auth/subdomain-check/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
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
ORM Prisma
2 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
2 exports
GETdynamic
Code source· typescript
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
export const dynamic = "force-dynamic";
const SUBDOMAIN_REGEX = /^[a-z0-9](?:[a-z0-9-]{1,28}[a-z0-9])?$/;
const RESERVED = new Set([
"www",
"admin",
"api",
"app",
"staging",
"blog",
"help",
"support",
"dashboard",
"mail",
"smtp",
"ftp",
"ssh",
"vpn",
"test",
"dev",
"wari",
"superadmin",
]);
// GET /api/admin/auth/subdomain-check?q=
// Check disponibilité d'un subdomain en live (debounce client-side).
// Réponse rapide pour rester < 100ms.
export async function GET(req: NextRequest) {
const raw = (req.nextUrl.searchParams.get("q") ?? "").trim().toLowerCase();
if (!raw) {
return NextResponse.json({ available: false, error: "Subdomain requis" }, { status: 400 });
}
if (raw.length < 3) {
return NextResponse.json({ available: false, error: "Minimum 3 caractères" });
}
if (raw.length > 30) {
return NextResponse.json({ available: false, error: "Maximum 30 caractères" });
}
if (!SUBDOMAIN_REGEX.test(raw)) {
return NextResponse.json({
available: false,
error: "Lettres minuscules, chiffres et tirets uniquement (pas de tiret en début/fin)",
});
}
if (RESERVED.has(raw)) {
return NextResponse.json({ available: false, error: "Ce nom est réservé" });
}
const existant = await prisma.tenant.findUnique({
where: { subdomain: raw },
select: { id: true, onboardingStep: true },
});
if (existant) {
// Cas particulier : si c'est le tenant en cours de setup (subdomain temporaire
// "setup-wari-..."), on accepte. Mais le subdomain de setup commence par "setup-"
// donc ce check est rare. Pour l'instant on bloque tout subdomain pris.
return NextResponse.json({ available: false, error: "Ce sous-domaine est déjà pris" });
}
return NextResponse.json({ available: true });
}
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
export const dynamic = "force-dynamic";
const SUBDOMAIN_REGEX = /^[a-z0-9](?:[a-z0-9-]{1,28}[a-z0-9])?$/;
const RESERVED = new Set([
"www",
"admin",
"api",
"app",
"staging",
"blog",
"help",
"support",
"dashboard",
"mail",
"smtp",
"ftp",
"ssh",
"vpn",
"test",
"dev",
"wari",
"superadmin",
]);
// GET /api/admin/auth/subdomain-check?q=
// Check disponibilité d'un subdomain en live (debounce client-side).
// Réponse rapide pour rester < 100ms.
export async function GET(req: NextRequest) {
const raw = (req.nextUrl.searchParams.get("q") ?? "").trim().toLowerCase();
if (!raw) {
return NextResponse.json({ available: false, error: "Subdomain requis" }, { status: 400 });
}
if (raw.length < 3) {
return NextResponse.json({ available: false, error: "Minimum 3 caractères" });
}
if (raw.length > 30) {
return NextResponse.json({ available: false, error: "Maximum 30 caractères" });
}
if (!SUBDOMAIN_REGEX.test(raw)) {
return NextResponse.json({
available: false,
error: "Lettres minuscules, chiffres et tirets uniquement (pas de tiret en début/fin)",
});
}
if (RESERVED.has(raw)) {
return NextResponse.json({ available: false, error: "Ce nom est réservé" });
}
const existant = await prisma.tenant.findUnique({
where: { subdomain: raw },
select: { id: true, onboardingStep: true },
});
if (existant) {
// Cas particulier : si c'est le tenant en cours de setup (subdomain temporaire
// "setup-wari-..."), on accepte. Mais le subdomain de setup commence par "setup-"
// donc ce check est rare. Pour l'instant on bloque tout subdomain pris.
return NextResponse.json({ available: false, error: "Ce sous-domaine est déjà pris" });
}
return NextResponse.json({ available: true });
}