makearmy-app/app/api/auth/register/route.ts

114 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// app/app/api/auth/register/route.ts
import { NextRequest, NextResponse } from "next/server";
const BASE = process.env.DIRECTUS_URL!;
const ADMIN_TOKEN = process.env.DIRECTUS_TOKEN_ADMIN_REGISTER!;
const MEMBER_ROLE_ID = process.env.DIRECTUS_ROLE_MEMBER_ID || ""; // optional override
if (!BASE) console.warn("[auth/register] Missing DIRECTUS_URL");
if (!ADMIN_TOKEN) console.warn("[auth/register] Missing DIRECTUS_TOKEN_ADMIN_REGISTER");
type DirectusList<T> = { data: T[] };
type DirectusItem<T> = { data: T };
async function dFetch<T=any>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
...init,
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": `Bearer ${ADMIN_TOKEN}`,
...(init?.headers || {}),
},
});
const text = await res.text();
const json = text ? JSON.parse(text) : null;
if (!res.ok) {
throw new Error(`Directus ${res.status}: ${text || res.statusText}`);
}
return (json ?? {}) as T;
}
async function getMemberRoleId(): Promise<string> {
if (MEMBER_ROLE_ID) return MEMBER_ROLE_ID;
const q = `/roles?limit=1&filter[name][_in]=Member,member&fields=id,name`;
const out = await dFetch<DirectusList<{id:string;name:string}>>(q);
const hit = out.data?.[0];
if (!hit?.id) throw new Error("Member role not found. Set DIRECTUS_ROLE_MEMBER_ID.");
return String(hit.id);
}
async function usernameExists(username: string): Promise<boolean> {
const q = `/users?limit=1&filter[username][_eq]=${encodeURIComponent(username)}&fields=id`;
const out = await dFetch<DirectusList<{id:string}>>(q);
return !!out.data?.length;
}
async function emailExists(email: string): Promise<boolean> {
const q = `/users?limit=1&filter[email][_eq]=${encodeURIComponent(email)}&fields=id`;
const out = await dFetch<DirectusList<{id:string}>>(q);
return !!out.data?.length;
}
function isEmailLike(s: string) {
return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s);
}
export async function POST(req: NextRequest) {
try {
const body = await req.json().catch(() => ({}));
const rawUsername: string = (body?.username ?? "").trim();
const rawPassword: string = (body?.password ?? "").trim();
const rawEmail: string = String(body?.email ?? "").trim();
if (!rawUsername || rawUsername.length < 3) {
return NextResponse.json({ error: "Username must be at least 3 characters." }, { status: 400 });
}
if (!rawPassword || rawPassword.length < 8) {
return NextResponse.json({ error: "Password must be at least 8 characters." }, { status: 400 });
}
if (rawEmail && !isEmailLike(rawEmail)) {
return NextResponse.json({ error: "Email format is invalid." }, { status: 400 });
}
if (await usernameExists(rawUsername)) {
return NextResponse.json({ error: "Username is already taken." }, { status: 409 });
}
// If no email provided, synthesize a placeholder thats unique.
let email = rawEmail;
if (!email) {
const base = `${rawUsername.toLowerCase()}+noemail@users.makearmy.local`;
let candidate = base;
let i = 1;
while (await emailExists(candidate)) {
i += 1;
candidate = `${rawUsername.toLowerCase()}+noemail${i}@users.makearmy.local`;
}
email = candidate;
} else {
if (await emailExists(email)) {
return NextResponse.json({ error: "Email already in use." }, { status: 409 });
}
}
const role = await getMemberRoleId();
// Create the user (status active to allow immediate login)
const create = await dFetch<DirectusItem<{id:string}>>(`/users`, {
method: "POST",
body: JSON.stringify({
email,
password: rawPassword,
role,
status: "active",
// Add custom field username on directus_users (unique)
username: rawUsername,
}),
});
return NextResponse.json({ ok: true, id: create.data.id, warning: rawEmail ? null : "No email provided. You won't be able to reset your password until you add one." });
} catch (err: any) {
return NextResponse.json({ error: err?.message || "Register failed" }, { status: 500 });
}
}