feat: org admin can create chapter (tenant) admins

Add POST /org/tenants/:id/admins so org admins can provision the first
login for a chapter's admin team. New chapter admins can then sign in at
/login with email + password.

- OrgService.createTenantAdmin — validates chapter belongs to org, hashes
  password, creates Admin record
- OrgController POST tenants/:id/admins endpoint
- BFF route /api/org/tenants/[id]/admins
- Add Admin form on /org/tenants/[id] page (email, password, role select)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 17:21:50 +05:30
parent b26a635283
commit 6bff55db64
4 changed files with 116 additions and 1 deletions
@@ -0,0 +1,21 @@
import { getApiBaseUrl, jsonResponse } from '../../../../../_lib/api';
export const dynamic = 'force-dynamic';
async function getOrgToken(): Promise<string | undefined> {
const { cookies } = await import('next/headers');
return (await cookies()).get('tower_org_token')?.value;
}
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }): Promise<Response> {
const { id } = await params;
const token = await getOrgToken();
const body = await req.json().catch(() => ({}));
const res = await fetch(`${getApiBaseUrl()}/org/tenants/${id}/admins`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
body: JSON.stringify(body),
cache: 'no-store',
});
return jsonResponse(await res.json(), res.status);
}