Promote uat → main (PROD): GRO-2359 OOBE portal-creation routing (api) (#214)
GRO-2359: add POST /api/portal/clients-from-auth for OOBE (#214) Co-authored-by: Flea Flicker <22+gb_flea@noreply.git.farh.net> Co-committed-by: Flea Flicker <22+gb_flea@noreply.git.farh.net>
This commit was merged in pull request #214.
This commit is contained in:
@@ -147,6 +147,114 @@ portalRouter.post("/session-from-auth", async (c) => {
|
||||
);
|
||||
});
|
||||
|
||||
// GRO-2359 — register a brand-new SSO user. The post-auth handler in the
|
||||
// web portal redirects here when `session-from-auth` returns 404, so the
|
||||
// OOBE can complete a customer record for the new user. Auth is via the
|
||||
// Better Auth session (same shape as `session-from-auth`), so this is
|
||||
// registered BEFORE the `validatePortalSession` middleware.
|
||||
//
|
||||
// Contract:
|
||||
// POST /api/portal/clients-from-auth
|
||||
// Body: { name: string; phone?: string|null; address?: string|null; notes?: string|null }
|
||||
// 201: { id, name, email }
|
||||
// 400: invalid body (zod failure)
|
||||
// 401: no Better Auth session
|
||||
// 409: a `clients` row already exists for this email (portal selection case)
|
||||
// 500: insert failed
|
||||
//
|
||||
// We do NOT auto-link the user's auth account to the new client row; the
|
||||
// existing `session-from-auth` endpoint re-resolves the row by email on the
|
||||
// next call, so the OOBE's success path just navigates the user back to
|
||||
// `/` and lets the bridge mint a portal session.
|
||||
const createClientFromAuthSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
phone: z.string().max(50).nullish(),
|
||||
address: z.string().max(500).nullish(),
|
||||
notes: z.string().max(2000).nullish(),
|
||||
});
|
||||
|
||||
portalRouter.post(
|
||||
"/clients-from-auth",
|
||||
zValidator("json", createClientFromAuthSchema),
|
||||
async (c) => {
|
||||
let auth;
|
||||
try {
|
||||
auth = getAuth();
|
||||
} catch {
|
||||
return c.json({ error: "Authentication not configured" }, 503);
|
||||
}
|
||||
|
||||
const session = await auth.api.getSession({
|
||||
headers: c.req.raw.headers,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return c.json({ error: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
const body = c.req.valid("json");
|
||||
const db = getDb();
|
||||
|
||||
// Pre-check: if a client already exists for this email, return 409 so
|
||||
// the OOBE can render the "portal selection" message (the user needs
|
||||
// to contact their groomer to link the new SSO identity to the
|
||||
// pre-existing customer record). We don't return the existing row to
|
||||
// avoid leaking PII about other accounts.
|
||||
const [existing] = await db
|
||||
.select({ id: clients.id })
|
||||
.from(clients)
|
||||
.where(eq(clients.email, session.user.email))
|
||||
.limit(1);
|
||||
|
||||
if (existing) {
|
||||
return c.json(
|
||||
{ error: "A customer record with this email already exists" },
|
||||
409,
|
||||
);
|
||||
}
|
||||
|
||||
let row;
|
||||
try {
|
||||
[row] = await db
|
||||
.insert(clients)
|
||||
.values({
|
||||
name: body.name.trim(),
|
||||
email: session.user.email,
|
||||
phone: body.phone?.trim() || null,
|
||||
address: body.address?.trim() || null,
|
||||
notes: body.notes?.trim() || null,
|
||||
})
|
||||
.returning();
|
||||
} catch (err) {
|
||||
// Concurrent insert from a parallel OOBE submit — treat as 409.
|
||||
if (
|
||||
err instanceof Error &&
|
||||
"code" in err &&
|
||||
(err as { code?: string }).code === "23505"
|
||||
) {
|
||||
return c.json(
|
||||
{ error: "A customer record with this email already exists" },
|
||||
409,
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!row) {
|
||||
return c.json({ error: "Failed to create client" }, 500);
|
||||
}
|
||||
|
||||
return c.json(
|
||||
{
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
email: row.email,
|
||||
},
|
||||
201,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Apply middleware to all portal routes
|
||||
portalRouter.use("/*", validatePortalSession, portalAudit);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user