multitenancy.ts136 lines · main
1/**
2 * Multitenancy on Doltgres — project → tenant rows in be_tenants.
3 */
4
5import { getEnginePool } from './db.js';
6import { isAuthCoreInitialized } from './engine.js';
7import { mapProjectToAuthCore } from './project-map.js';
8import { log } from '../../lib/logger.js';
9
10export type EnsureTenantResult = {
11 engine: 'briven-engine';
12 projectId: string;
13 appId: string;
14 tenantId: string;
15 created: boolean;
16 ok: boolean;
17 message?: string;
18 storage: 'doltgres';
19};
20
21export async function ensureBrivenEngineTenant(
22 projectId: string,
23): Promise<EnsureTenantResult> {
24 const map = mapProjectToAuthCore(projectId);
25 const base = {
26 engine: 'briven-engine' as const,
27 storage: 'doltgres' as const,
28 projectId: map.projectId,
29 appId: map.appId,
30 tenantId: map.tenantId,
31 };
32
33 if (!isAuthCoreInitialized()) {
34 return {
35 ...base,
36 created: false,
37 ok: false,
38 message: 'briven-engine not ready on Doltgres',
39 };
40 }
41
42 try {
43 const pool = getEnginePool();
44 const existing = await pool.query(
45 `SELECT tenant_id FROM be_tenants WHERE tenant_id = $1 LIMIT 1`,
46 [map.tenantId],
47 );
48 if (existing.rowCount && existing.rowCount > 0) {
49 return { ...base, created: false, ok: true, message: 'tenant exists' };
50 }
51 await pool.query(
52 `INSERT INTO be_tenants (tenant_id, project_id) VALUES ($1, $2)`,
53 [map.tenantId, map.projectId],
54 );
55 log.info('briven_engine_tenant_created', {
56 tenantId: map.tenantId,
57 projectId,
58 storage: 'doltgres',
59 });
60 return { ...base, created: true, ok: true, message: 'tenant created on Doltgres' };
61 } catch (err) {
62 const message = err instanceof Error ? err.message : String(err);
63 return { ...base, created: false, ok: false, message };
64 }
65}
66
67export async function listBrivenEngineTenants(): Promise<{
68 engine: 'briven-engine';
69 tenantIds: string[];
70 tenants: Array<{
71 tenantId: string;
72 projectId: string;
73 createdAt: string | null;
74 authEnabled: boolean;
75 }>;
76 ok: boolean;
77 message?: string;
78 storage: 'doltgres';
79}> {
80 if (!isAuthCoreInitialized()) {
81 return {
82 engine: 'briven-engine',
83 storage: 'doltgres',
84 tenantIds: [],
85 tenants: [],
86 ok: false,
87 message: 'sdk not ready',
88 };
89 }
90 try {
91 const pool = getEnginePool();
92 // Soft-disabled tenants stay in the table but must not count as Auth on.
93 let res;
94 try {
95 res = await pool.query(
96 `SELECT tenant_id, project_id, created_at, disabled_at
97 FROM be_tenants
98 WHERE disabled_at IS NULL
99 ORDER BY created_at`,
100 );
101 } catch {
102 res = await pool.query(
103 `SELECT tenant_id, project_id, created_at FROM be_tenants ORDER BY created_at`,
104 );
105 }
106 const tenants = (
107 res.rows as Array<{
108 tenant_id: string;
109 project_id: string;
110 created_at: Date | string | null;
111 disabled_at?: Date | string | null;
112 }>
113 ).map((r) => ({
114 tenantId: r.tenant_id,
115 projectId: r.project_id,
116 createdAt: r.created_at ? new Date(r.created_at).toISOString() : null,
117 authEnabled: true as boolean,
118 }));
119 return {
120 engine: 'briven-engine',
121 storage: 'doltgres',
122 tenantIds: tenants.map((t) => t.tenantId),
123 tenants,
124 ok: true,
125 };
126 } catch (err) {
127 return {
128 engine: 'briven-engine',
129 storage: 'doltgres',
130 tenantIds: [],
131 tenants: [],
132 ok: false,
133 message: err instanceof Error ? err.message : String(err),
134 };
135 }
136}