@loomcore/api 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,8 +11,9 @@ import { PostgresDatabase } from "../databases/postgres/postgres.database.js";
11
11
  import { GenericApiService, UserService } from "../services/index.js";
12
12
  import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
13
13
  import { OrganizationService } from "../services/organization.service.js";
14
+ import { OrganizationDomainService } from "../services/organization-domain.service.js";
14
15
  import * as testObjectsModule from "./test-objects.js";
15
- const { getTestMetaOrg, getTestMetaOrgRefererUrl, getTestOrg, getTestMetaOrgUser, getTestMetaOrgUserContext, getTestOrgUser, getTestOrgUserContext, setTestOrgId, setTestMetaOrgId, setTestMetaOrgUserId, setTestOrgUserId, } = testObjectsModule;
16
+ const { getTestMetaOrg, getTestMetaOrgDomain, getTestMetaOrgRefererUrl, getTestOrg, getTestOrgDomain, getTestMetaOrgUser, getTestMetaOrgUserContext, getTestOrgUser, getTestOrgUserContext, setTestOrgId, setTestMetaOrgId, setTestMetaOrgUserId, setTestOrgUserId, TEST_META_ORG_DOMAIN, TEST_ORG_DOMAIN, } = testObjectsModule;
16
17
  import { entityUtils } from "@loomcore/common/utils";
17
18
  import { config, setBaseApiConfig } from "../config/index.js";
18
19
  import { attemptLogin } from "../utils/auth/index.js";
@@ -24,6 +25,7 @@ import { TestEmailClient } from "./test-email-client.js";
24
25
  let deviceIdCookie;
25
26
  let database;
26
27
  let organizationService;
28
+ let organizationDomainService;
27
29
  let userService;
28
30
  const JWT_SECRET = "test-secret";
29
31
  const newUser1Email = "one@test.com";
@@ -32,6 +34,7 @@ const constDeviceIdCookie = crypto.randomBytes(16).toString("hex");
32
34
  function initialize(db) {
33
35
  database = db;
34
36
  organizationService = new OrganizationService(db);
37
+ organizationDomainService = new OrganizationDomainService(db);
35
38
  userService = new UserService(db);
36
39
  deviceIdCookie = constDeviceIdCookie;
37
40
  }
@@ -51,7 +54,7 @@ async function createMetaOrg() {
51
54
  if (!config.app.isMultiTenant) {
52
55
  return;
53
56
  }
54
- if (!organizationService) {
57
+ if (!organizationService || !organizationDomainService) {
55
58
  throw new Error("OrganizationService not initialized. Call initialize() first.");
56
59
  }
57
60
  try {
@@ -60,10 +63,15 @@ async function createMetaOrg() {
60
63
  const metaOrgInsertResult = await organizationService.create(EmptyUserContext, getTestMetaOrg());
61
64
  if (metaOrgInsertResult) {
62
65
  setTestMetaOrgId(metaOrgInsertResult._id);
66
+ await organizationDomainService.create(EmptyUserContext, getTestMetaOrgDomain(metaOrgInsertResult._id));
63
67
  }
64
68
  }
65
69
  else {
66
70
  setTestMetaOrgId(existingMetaOrg._id);
71
+ const existingDomain = await organizationDomainService.findOne(EmptyUserContext, { filters: { domain: { eq: TEST_META_ORG_DOMAIN } } });
72
+ if (!existingDomain) {
73
+ await organizationDomainService.create(EmptyUserContext, getTestMetaOrgDomain(existingMetaOrg._id));
74
+ }
67
75
  }
68
76
  }
69
77
  catch (error) {
@@ -96,7 +104,7 @@ async function setupTestUsers() {
96
104
  }
97
105
  }
98
106
  async function createTestUsers() {
99
- if (!organizationService || !userService) {
107
+ if (!organizationService || !organizationDomainService || !userService) {
100
108
  throw new Error("Database not initialized. Call initialize() first.");
101
109
  }
102
110
  try {
@@ -112,9 +120,14 @@ async function createTestUsers() {
112
120
  throw new Error("Failed to create test organization");
113
121
  }
114
122
  setTestOrgId(createdTestOrg._id);
123
+ await organizationDomainService.create(EmptyUserContext, getTestOrgDomain(createdTestOrg._id));
115
124
  }
116
125
  else {
117
126
  setTestOrgId(existingTestOrg._id);
127
+ const existingDomain = await organizationDomainService.findOne(EmptyUserContext, { filters: { domain: { eq: TEST_ORG_DOMAIN } } });
128
+ if (!existingDomain) {
129
+ await organizationDomainService.create(EmptyUserContext, getTestOrgDomain(existingTestOrg._id));
130
+ }
118
131
  }
119
132
  const createdTestOrgUser = await userService.create(getTestOrgUserContext(), getTestOrgUser());
120
133
  const createdMetaOrgUser = await userService.create(getTestMetaOrgUserContext(), getTestMetaOrgUser());
@@ -1,3 +1,3 @@
1
- import { IBaseApiConfig } from '../../models/base-api-config.interface.js';
2
- import { SyntheticMigration } from '../../databases/postgres/migrations/postgres-initial-schema.js';
1
+ import type { SyntheticMigration } from "../../databases/postgres/migrations/postgres-initial-schema.js";
2
+ import type { IBaseApiConfig } from "../../models/base-api-config.interface.js";
3
3
  export declare const getPostgresTestSchema: (config: IBaseApiConfig) => SyntheticMigration[];
@@ -2,9 +2,9 @@ export const getPostgresTestSchema = (config) => {
2
2
  const migrations = [];
3
3
  const isMultiTenant = config.app.isMultiTenant === true;
4
4
  migrations.push({
5
- name: '00000000000100_schema-test-entities',
5
+ name: "00000000000100_schema-test-entities",
6
6
  up: async ({ context: pool }) => {
7
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
7
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
8
8
  await pool.query(`
9
9
  CREATE TABLE IF NOT EXISTS "testEntities" (
10
10
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -25,12 +25,12 @@ export const getPostgresTestSchema = (config) => {
25
25
  },
26
26
  down: async ({ context: pool }) => {
27
27
  await pool.query('DROP TABLE IF EXISTS "testEntities"');
28
- }
28
+ },
29
29
  });
30
30
  migrations.push({
31
- name: '00000000000101_schema-categories',
31
+ name: "00000000000101_schema-categories",
32
32
  up: async ({ context: pool }) => {
33
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
33
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
34
34
  await pool.query(`
35
35
  CREATE TABLE IF NOT EXISTS "categories" (
36
36
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -41,12 +41,12 @@ export const getPostgresTestSchema = (config) => {
41
41
  },
42
42
  down: async ({ context: pool }) => {
43
43
  await pool.query('DROP TABLE IF EXISTS "categories"');
44
- }
44
+ },
45
45
  });
46
46
  migrations.push({
47
- name: '00000000000102_schema-products',
47
+ name: "00000000000102_schema-products",
48
48
  up: async ({ context: pool }) => {
49
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
49
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
50
50
  await pool.query(`
51
51
  CREATE TABLE IF NOT EXISTS "products" (
52
52
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -67,12 +67,12 @@ export const getPostgresTestSchema = (config) => {
67
67
  },
68
68
  down: async ({ context: pool }) => {
69
69
  await pool.query('DROP TABLE IF EXISTS "products"');
70
- }
70
+ },
71
71
  });
72
72
  migrations.push({
73
- name: '00000000000103_schema-test-items',
73
+ name: "00000000000103_schema-test-items",
74
74
  up: async ({ context: pool }) => {
75
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
75
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
76
76
  await pool.query(`
77
77
  CREATE TABLE IF NOT EXISTS "testItems" (
78
78
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -91,12 +91,53 @@ export const getPostgresTestSchema = (config) => {
91
91
  },
92
92
  down: async ({ context: pool }) => {
93
93
  await pool.query('DROP TABLE IF EXISTS "testItems"');
94
- }
94
+ },
95
95
  });
96
96
  migrations.push({
97
- name: '00000000000105_5_schema-agents',
97
+ name: "00000000000104_schema-persons",
98
98
  up: async ({ context: pool }) => {
99
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
99
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
100
+ const personsUniqueConstraints = isMultiTenant
101
+ ? `CONSTRAINT "uk_persons_org_external_id" UNIQUE ("_orgId", "external_id"),
102
+ CONSTRAINT "uk_persons_org_ssn" UNIQUE ("_orgId", "ssn")`
103
+ : `CONSTRAINT "uk_persons_external_id" UNIQUE ("external_id"),
104
+ CONSTRAINT "uk_persons_ssn" UNIQUE ("ssn")`;
105
+ await pool.query(`
106
+ CREATE TABLE IF NOT EXISTS "persons" (
107
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
108
+ ${orgColumnDef}
109
+ "external_id" VARCHAR(255),
110
+ "first_name" VARCHAR(255) NOT NULL,
111
+ "middle_name" VARCHAR(255),
112
+ "last_name" VARCHAR(255) NOT NULL,
113
+ "date_of_birth" DATE,
114
+ "ssn" VARCHAR(255),
115
+ "is_agent" BOOLEAN NOT NULL DEFAULT FALSE,
116
+ "is_client" BOOLEAN NOT NULL DEFAULT FALSE,
117
+ "is_employee" BOOLEAN NOT NULL DEFAULT FALSE,
118
+ "extended_types" INTEGER,
119
+ "_created" TIMESTAMPTZ NOT NULL,
120
+ "_createdBy" INTEGER NOT NULL,
121
+ "_updated" TIMESTAMPTZ,
122
+ "_updatedBy" INTEGER,
123
+ "_deleted" TIMESTAMPTZ,
124
+ "_deletedBy" INTEGER,
125
+ ${personsUniqueConstraints}
126
+ )
127
+ `);
128
+ await pool.query(`CREATE INDEX IF NOT EXISTS "idx_persons_external_id" ON "persons" ("external_id")`);
129
+ await pool.query(`CREATE INDEX IF NOT EXISTS "idx_persons_ssn" ON "persons" ("ssn")`);
130
+ },
131
+ down: async ({ context: pool }) => {
132
+ await pool.query(`DROP INDEX IF EXISTS "idx_persons_external_id"`);
133
+ await pool.query(`DROP INDEX IF EXISTS "idx_persons_ssn"`);
134
+ await pool.query('DROP TABLE IF EXISTS "persons"');
135
+ },
136
+ });
137
+ migrations.push({
138
+ name: "00000000000105_5_schema-agents",
139
+ up: async ({ context: pool }) => {
140
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
100
141
  await pool.query(`
101
142
  CREATE TABLE IF NOT EXISTS "agents" (
102
143
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -114,12 +155,12 @@ export const getPostgresTestSchema = (config) => {
114
155
  },
115
156
  down: async ({ context: pool }) => {
116
157
  await pool.query('DROP TABLE IF EXISTS "agents"');
117
- }
158
+ },
118
159
  });
119
160
  migrations.push({
120
- name: '00000000000105_6_schema-clients',
161
+ name: "00000000000105_6_schema-clients",
121
162
  up: async ({ context: pool }) => {
122
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
163
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
123
164
  await pool.query(`
124
165
  CREATE TABLE IF NOT EXISTS "clients" (
125
166
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -140,12 +181,12 @@ export const getPostgresTestSchema = (config) => {
140
181
  },
141
182
  down: async ({ context: pool }) => {
142
183
  await pool.query('DROP TABLE IF EXISTS "clients"');
143
- }
184
+ },
144
185
  });
145
186
  migrations.push({
146
- name: '00000000000105_7_schema-policies',
187
+ name: "00000000000105_7_schema-policies",
147
188
  up: async ({ context: pool }) => {
148
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
189
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
149
190
  await pool.query(`
150
191
  CREATE TABLE IF NOT EXISTS "policies" (
151
192
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -165,12 +206,12 @@ export const getPostgresTestSchema = (config) => {
165
206
  },
166
207
  down: async ({ context: pool }) => {
167
208
  await pool.query('DROP TABLE IF EXISTS "policies"');
168
- }
209
+ },
169
210
  });
170
211
  migrations.push({
171
- name: '00000000000105_8_schema-agents-policies',
212
+ name: "00000000000105_8_schema-agents-policies",
172
213
  up: async ({ context: pool }) => {
173
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
214
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
174
215
  await pool.query(`
175
216
  CREATE TABLE IF NOT EXISTS agents_policies (
176
217
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -191,12 +232,12 @@ export const getPostgresTestSchema = (config) => {
191
232
  },
192
233
  down: async ({ context: pool }) => {
193
234
  await pool.query('DROP TABLE IF EXISTS "agents_policies"');
194
- }
235
+ },
195
236
  });
196
237
  migrations.push({
197
- name: '00000000000105_9_schema-premiums',
238
+ name: "00000000000105_9_schema-premiums",
198
239
  up: async ({ context: pool }) => {
199
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
240
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
200
241
  await pool.query(`
201
242
  CREATE TABLE IF NOT EXISTS "premiums" (
202
243
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -216,12 +257,12 @@ export const getPostgresTestSchema = (config) => {
216
257
  },
217
258
  down: async ({ context: pool }) => {
218
259
  await pool.query('DROP TABLE IF EXISTS "premiums"');
219
- }
260
+ },
220
261
  });
221
262
  migrations.push({
222
- name: '00000000000106_schema-email-addresses',
263
+ name: "00000000000106_schema-email-addresses",
223
264
  up: async ({ context: pool }) => {
224
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
265
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
225
266
  await pool.query(`
226
267
  CREATE TABLE IF NOT EXISTS email_addresses (
227
268
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -243,12 +284,12 @@ export const getPostgresTestSchema = (config) => {
243
284
  },
244
285
  down: async ({ context: pool }) => {
245
286
  await pool.query('DROP TABLE IF EXISTS "email_addresses"');
246
- }
287
+ },
247
288
  });
248
289
  migrations.push({
249
- name: '00000000000107_schema-phone-numbers',
290
+ name: "00000000000107_schema-phone-numbers",
250
291
  up: async ({ context: pool }) => {
251
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
292
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
252
293
  await pool.query(`
253
294
  CREATE TABLE IF NOT EXISTS phone_numbers (
254
295
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -268,12 +309,12 @@ export const getPostgresTestSchema = (config) => {
268
309
  },
269
310
  down: async ({ context: pool }) => {
270
311
  await pool.query('DROP TABLE IF EXISTS "phone_numbers"');
271
- }
312
+ },
272
313
  });
273
314
  migrations.push({
274
- name: '00000000000108_schema-addresses',
315
+ name: "00000000000108_schema-addresses",
275
316
  up: async ({ context: pool }) => {
276
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
317
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
277
318
  await pool.query(`
278
319
  CREATE TABLE IF NOT EXISTS addresses (
279
320
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -297,12 +338,12 @@ export const getPostgresTestSchema = (config) => {
297
338
  },
298
339
  down: async ({ context: pool }) => {
299
340
  await pool.query('DROP TABLE IF EXISTS "addresses"');
300
- }
341
+ },
301
342
  });
302
343
  migrations.push({
303
- name: '00000000000109_schema-person-addresses',
344
+ name: "00000000000109_schema-person-addresses",
304
345
  up: async ({ context: pool }) => {
305
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
346
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
306
347
  await pool.query(`
307
348
  CREATE TABLE IF NOT EXISTS persons_addresses (
308
349
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -323,12 +364,12 @@ export const getPostgresTestSchema = (config) => {
323
364
  },
324
365
  down: async ({ context: pool }) => {
325
366
  await pool.query('DROP TABLE IF EXISTS "persons_addresses"');
326
- }
367
+ },
327
368
  });
328
369
  migrations.push({
329
- name: '00000000000110_schema-person-phone-numbers',
370
+ name: "00000000000110_schema-person-phone-numbers",
330
371
  up: async ({ context: pool }) => {
331
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
372
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
332
373
  await pool.query(`
333
374
  CREATE TABLE IF NOT EXISTS persons_phone_numbers (
334
375
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -349,12 +390,12 @@ export const getPostgresTestSchema = (config) => {
349
390
  },
350
391
  down: async ({ context: pool }) => {
351
392
  await pool.query('DROP TABLE IF EXISTS "persons_phone_numbers"');
352
- }
393
+ },
353
394
  });
354
395
  migrations.push({
355
- name: '00000000000111_schema-states',
396
+ name: "00000000000111_schema-states",
356
397
  up: async ({ context: pool }) => {
357
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
398
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
358
399
  await pool.query(`
359
400
  CREATE TABLE IF NOT EXISTS states (
360
401
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -371,12 +412,12 @@ export const getPostgresTestSchema = (config) => {
371
412
  },
372
413
  down: async ({ context: pool }) => {
373
414
  await pool.query('DROP TABLE IF EXISTS "states"');
374
- }
415
+ },
375
416
  });
376
417
  migrations.push({
377
- name: '00000000000112_schema-districts',
418
+ name: "00000000000112_schema-districts",
378
419
  up: async ({ context: pool }) => {
379
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
420
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
380
421
  await pool.query(`
381
422
  CREATE TABLE IF NOT EXISTS districts (
382
423
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -395,12 +436,12 @@ export const getPostgresTestSchema = (config) => {
395
436
  },
396
437
  down: async ({ context: pool }) => {
397
438
  await pool.query('DROP TABLE IF EXISTS "districts"');
398
- }
439
+ },
399
440
  });
400
441
  migrations.push({
401
- name: '00000000000113_schema-schools',
442
+ name: "00000000000113_schema-schools",
402
443
  up: async ({ context: pool }) => {
403
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
444
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
404
445
  await pool.query(`
405
446
  CREATE TABLE IF NOT EXISTS schools (
406
447
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -419,12 +460,12 @@ export const getPostgresTestSchema = (config) => {
419
460
  },
420
461
  down: async ({ context: pool }) => {
421
462
  await pool.query('DROP TABLE IF EXISTS "schools"');
422
- }
463
+ },
423
464
  });
424
465
  migrations.push({
425
- name: '00000000000114_schema-person-schools',
466
+ name: "00000000000114_schema-person-schools",
426
467
  up: async ({ context: pool }) => {
427
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
468
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : "";
428
469
  await pool.query(`
429
470
  CREATE TABLE IF NOT EXISTS persons_schools (
430
471
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -445,7 +486,7 @@ export const getPostgresTestSchema = (config) => {
445
486
  },
446
487
  down: async ({ context: pool }) => {
447
488
  await pool.query('DROP TABLE IF EXISTS "persons_schools"');
448
- }
489
+ },
449
490
  });
450
491
  return migrations;
451
492
  };
@@ -1,4 +1,4 @@
1
- import type { IOrganization, IUser, IUserContext } from "@loomcore/common/models";
1
+ import type { IOrganization, IOrganizationDomain, IUser, IUserContext } from "@loomcore/common/models";
2
2
  export declare let TEST_META_ORG_ID: string | number;
3
3
  export declare let TEST_META_ORG_USER_ID: string | number;
4
4
  export declare function setTestMetaOrgId(metaOrgId: string | number): void;
@@ -6,15 +6,17 @@ export declare function setTestMetaOrgUserId(userId: string | number): void;
6
6
  export declare const TEST_META_ORG_USER_PASSWORD = "test-meta-org-user-password";
7
7
  export declare const TEST_META_ORG_DOMAIN = "test-meta-org.example.com";
8
8
  export declare const TEST_ORG_DOMAIN = "test-org.example.com";
9
- export declare function getRefererUrlForOrg(org: Pick<IOrganization, "domain">): string;
9
+ export declare function getRefererUrlForOrg(domain: string): string;
10
10
  export declare function getTestMetaOrgRefererUrl(): string;
11
11
  export declare function getTestOrgRefererUrl(): string;
12
12
  export declare function getTestMetaOrg(): IOrganization;
13
+ export declare function getTestMetaOrgDomain(organizationId?: string | number): Partial<IOrganizationDomain>;
13
14
  export declare function getTestMetaOrgUser(): IUser;
14
15
  export declare function getTestMetaOrgUserContext(): IUserContext;
15
16
  export declare function setTestOrgId(orgId: string | number): void;
16
17
  export declare function setTestOrgUserId(userId: string | number): void;
17
18
  export declare function getTestOrg(): IOrganization;
19
+ export declare function getTestOrgDomain(organizationId?: string | number): Partial<IOrganizationDomain>;
18
20
  export declare const TEST_ORG_USER_PASSWORD = "test-org-user-password";
19
21
  export declare function getTestOrgUser(): IUser;
20
22
  export declare function getTestOrgUserContext(): IUserContext;
@@ -9,30 +9,35 @@ export function setTestMetaOrgUserId(userId) {
9
9
  export const TEST_META_ORG_USER_PASSWORD = "test-meta-org-user-password";
10
10
  export const TEST_META_ORG_DOMAIN = "test-meta-org.example.com";
11
11
  export const TEST_ORG_DOMAIN = "test-org.example.com";
12
- export function getRefererUrlForOrg(org) {
13
- if (!org.domain) {
12
+ export function getRefererUrlForOrg(domain) {
13
+ if (!domain) {
14
14
  throw new Error("Organization domain is required for auth referer URL");
15
15
  }
16
- return `https://${org.domain}`;
16
+ return `https://${domain}`;
17
17
  }
18
18
  export function getTestMetaOrgRefererUrl() {
19
- return getRefererUrlForOrg(getTestMetaOrg());
19
+ return getRefererUrlForOrg(TEST_META_ORG_DOMAIN);
20
20
  }
21
21
  export function getTestOrgRefererUrl() {
22
- return getRefererUrlForOrg(getTestOrg());
22
+ return getRefererUrlForOrg(TEST_ORG_DOMAIN);
23
23
  }
24
24
  export function getTestMetaOrg() {
25
25
  return {
26
26
  _id: TEST_META_ORG_ID,
27
27
  name: "Test Meta Organization",
28
28
  code: "test-meta-org",
29
- domain: TEST_META_ORG_DOMAIN,
30
29
  status: 1,
31
30
  isMetaOrg: true,
32
31
  _created: new Date(),
33
32
  _createdBy: "system",
34
33
  };
35
34
  }
35
+ export function getTestMetaOrgDomain(organizationId = TEST_META_ORG_ID) {
36
+ return {
37
+ organizationId,
38
+ domain: TEST_META_ORG_DOMAIN,
39
+ };
40
+ }
36
41
  export function getTestMetaOrgUser() {
37
42
  return {
38
43
  _id: TEST_META_ORG_USER_ID,
@@ -73,13 +78,18 @@ export function getTestOrg() {
73
78
  _id: TEST_ORG_ID,
74
79
  name: "Test Organization",
75
80
  code: "test-org",
76
- domain: TEST_ORG_DOMAIN,
77
81
  status: 1,
78
82
  isMetaOrg: false,
79
83
  _created: new Date(),
80
84
  _createdBy: "system",
81
85
  };
82
86
  }
87
+ export function getTestOrgDomain(organizationId = TEST_ORG_ID) {
88
+ return {
89
+ organizationId,
90
+ domain: TEST_ORG_DOMAIN,
91
+ };
92
+ }
83
93
  export const TEST_ORG_USER_PASSWORD = "test-org-user-password";
84
94
  export function getTestOrgUser() {
85
95
  return {
@@ -38,9 +38,7 @@ export class AuthController {
38
38
  if (!referer) {
39
39
  throw new BadRequestError("Missing required fields: referer is required.");
40
40
  }
41
- organization = await this.organizationService.findOne(EmptyUserContext, {
42
- filters: { domain: { eq: referer.split("/")[2] } },
43
- });
41
+ organization = await this.organizationService.findByDomain(EmptyUserContext, referer.split("/")[2]);
44
42
  if (!organization) {
45
43
  throw new BadRequestError("Missing required fields: organization is required.");
46
44
  }
@@ -96,9 +94,7 @@ export class AuthController {
96
94
  referer = referer.replace(/\/$/, "");
97
95
  let organization = null;
98
96
  if (config.app.isMultiTenant) {
99
- organization = await this.organizationService.findOne(EmptyUserContext, {
100
- filters: { domain: { eq: referer.split("/")[2] } },
101
- });
97
+ organization = await this.organizationService.findByDomain(EmptyUserContext, referer.split("/")[2]);
102
98
  if (!organization) {
103
99
  throw new BadRequestError("Missing required fields: organization is required.");
104
100
  }
@@ -126,9 +122,7 @@ export class AuthController {
126
122
  if (!referer) {
127
123
  throw new BadRequestError("Missing required fields: referer is required.");
128
124
  }
129
- organization = await this.organizationService.findOne(EmptyUserContext, {
130
- filters: { domain: { eq: referer.split("/")[2] } },
131
- });
125
+ organization = await this.organizationService.findByDomain(EmptyUserContext, referer.split("/")[2]);
132
126
  if (!organization) {
133
127
  throw new BadRequestError("Missing required fields: organization is required.");
134
128
  }
@@ -9,7 +9,7 @@ export const getMongoInitialSchema = (dbConfig) => {
9
9
  throw new Error("Multi-tenant configuration is enabled but multi-tenant configuration is not provided");
10
10
  }
11
11
  const isAuthEnabled = dbConfig.app.isAuthEnabled;
12
- if (isMultiTenant)
12
+ if (isMultiTenant) {
13
13
  migrations.push({
14
14
  name: "00000000000001_schema-organizations",
15
15
  up: async ({ context: db }) => {
@@ -26,30 +26,25 @@ export const getMongoInitialSchema = (dbConfig) => {
26
26
  await db.collection("organizations").drop();
27
27
  },
28
28
  });
29
- if (isAuthEnabled)
30
29
  migrations.push({
31
- name: "00000000000002_schema-persons",
30
+ name: "00000000000001_schema-organization-domains",
32
31
  up: async ({ context: db }) => {
33
- await db.createCollection("persons");
34
- if (isMultiTenant) {
35
- await db.collection("persons").createIndex({ _orgId: 1 });
36
- await db
37
- .collection("persons")
38
- .createIndex({ _orgId: 1, externalId: 1 }, { unique: true, sparse: true });
39
- }
40
- else {
41
- await db
42
- .collection("persons")
43
- .createIndex({ externalId: 1 }, { unique: true, sparse: true });
44
- }
32
+ await db.createCollection("organization_domains");
33
+ await db
34
+ .collection("organization_domains")
35
+ .createIndex({ domain: 1 }, { unique: true });
36
+ await db
37
+ .collection("organization_domains")
38
+ .createIndex({ organizationId: 1 });
45
39
  },
46
40
  down: async ({ context: db }) => {
47
- await db.collection("persons").drop();
41
+ await db.collection("organization_domains").drop();
48
42
  },
49
43
  });
44
+ }
50
45
  if (isAuthEnabled)
51
46
  migrations.push({
52
- name: "00000000000003_schema-users",
47
+ name: "00000000000002_schema-users",
53
48
  up: async ({ context: db }) => {
54
49
  await db.createCollection("users");
55
50
  if (dbConfig.app.isMultiTenant) {
@@ -70,7 +65,7 @@ export const getMongoInitialSchema = (dbConfig) => {
70
65
  });
71
66
  if (isAuthEnabled)
72
67
  migrations.push({
73
- name: "00000000000004_schema-refresh-tokens",
68
+ name: "00000000000003_schema-refresh-tokens",
74
69
  up: async ({ context: db }) => {
75
70
  await db.createCollection("refresh_tokens");
76
71
  await db
@@ -88,7 +83,7 @@ export const getMongoInitialSchema = (dbConfig) => {
88
83
  });
89
84
  if (isAuthEnabled)
90
85
  migrations.push({
91
- name: "00000000000005_schema-password-reset-tokens",
86
+ name: "00000000000004_schema-password-reset-tokens",
92
87
  up: async ({ context: db }) => {
93
88
  await db.createCollection("password_reset_tokens");
94
89
  if (isMultiTenant) {
@@ -111,7 +106,7 @@ export const getMongoInitialSchema = (dbConfig) => {
111
106
  });
112
107
  if (isAuthEnabled)
113
108
  migrations.push({
114
- name: "00000000000006_schema-roles",
109
+ name: "00000000000005_schema-roles",
115
110
  up: async ({ context: db }) => {
116
111
  await db.createCollection("roles");
117
112
  if (isMultiTenant) {
@@ -132,7 +127,7 @@ export const getMongoInitialSchema = (dbConfig) => {
132
127
  });
133
128
  if (isAuthEnabled)
134
129
  migrations.push({
135
- name: "00000000000007_schema-user-roles",
130
+ name: "00000000000006_schema-user-roles",
136
131
  up: async ({ context: db }) => {
137
132
  await db.createCollection("user_roles");
138
133
  if (isMultiTenant) {
@@ -155,7 +150,7 @@ export const getMongoInitialSchema = (dbConfig) => {
155
150
  });
156
151
  if (isAuthEnabled)
157
152
  migrations.push({
158
- name: "00000000000008_schema-features",
153
+ name: "00000000000007_schema-features",
159
154
  up: async ({ context: db }) => {
160
155
  await db.createCollection("features");
161
156
  if (isMultiTenant) {
@@ -176,7 +171,7 @@ export const getMongoInitialSchema = (dbConfig) => {
176
171
  });
177
172
  if (isAuthEnabled)
178
173
  migrations.push({
179
- name: "00000000000009_schema-authorizations",
174
+ name: "00000000000008_schema-authorizations",
180
175
  up: async ({ context: db }) => {
181
176
  await db.createCollection("authorizations");
182
177
  if (isMultiTenant) {
@@ -199,7 +194,7 @@ export const getMongoInitialSchema = (dbConfig) => {
199
194
  });
200
195
  if (isMultiTenant) {
201
196
  migrations.push({
202
- name: "00000000000010_data-meta-org",
197
+ name: "00000000000009_data-meta-org",
203
198
  up: async ({ context: db }) => {
204
199
  const metaOrgDoc = {
205
200
  name: dbConfig.multiTenant.metaOrgName,
@@ -222,20 +217,32 @@ export const getMongoInitialSchema = (dbConfig) => {
222
217
  ...metaOrgDoc,
223
218
  _id: result.insertedId,
224
219
  };
220
+ const metaOrgDomains = dbConfig.multiTenant.metaOrgDomains ?? [];
221
+ if (metaOrgDomains.length > 0) {
222
+ await db.collection("organization_domains").insertMany(metaOrgDomains.map((domain) => ({
223
+ organizationId: metaOrg._id,
224
+ domain,
225
+ _created: new Date(),
226
+ _createdBy: "system",
227
+ _updated: new Date(),
228
+ _updatedBy: "system",
229
+ })));
230
+ }
225
231
  initializeSystemUserContext(dbConfig.email?.systemEmailAddress || "system@example.com", metaOrg);
226
232
  },
227
233
  down: async ({ context: db }) => {
234
+ await db.collection("organization_domains").deleteMany({});
228
235
  await db.collection("organizations").deleteMany({ isMetaOrg: true });
229
236
  },
230
237
  });
231
238
  }
232
239
  if (isAuthEnabled && dbConfig.adminUser) {
233
240
  migrations.push({
234
- name: "00000000000011_data-admin-user",
241
+ name: "00000000000010_data-admin-user",
235
242
  up: async ({ context: db }) => {
236
243
  if (!isSystemUserContextInitialized()) {
237
244
  const errorMessage = isMultiTenant
238
- ? "SystemUserContext has not been initialized. The meta-org migration (00000000000010_data-meta-org) should have run before this migration. " +
245
+ ? "SystemUserContext has not been initialized. The meta-org migration (00000000000009_data-meta-org) should have run before this migration. " +
239
246
  "Please ensure metaOrgName and metaOrgCode are provided in your dbConfig."
240
247
  : "BUG: SystemUserContext has not been initialized. For non-multi-tenant setups, SystemUserContext should be initialized before migrations run.";
241
248
  console.error("❌ Migration Error:", errorMessage);
@@ -247,26 +254,12 @@ export const getMongoInitialSchema = (dbConfig) => {
247
254
  : {};
248
255
  const hashedPassword = await passwordUtils.hashPassword(dbConfig.adminUser.password);
249
256
  const email = dbConfig.adminUser.email.toLowerCase();
250
- const personResult = await db.collection("persons").insertOne({
251
- ...orgDoc,
252
- externalId: "admin-user-person-external-id",
253
- firstName: "Admin",
254
- lastName: "User",
255
- isAgent: false,
256
- isClient: false,
257
- isEmployee: false,
258
- _created: new Date(),
259
- _createdBy: "system",
260
- _updated: new Date(),
261
- _updatedBy: "system",
262
- });
263
257
  await db.collection("users").insertOne({
264
258
  ...orgDoc,
265
259
  externalId: "admin-user-external-id",
266
260
  email,
267
261
  password: hashedPassword,
268
262
  displayName: "Admin User",
269
- personId: personResult.insertedId,
270
263
  _created: new Date(),
271
264
  _createdBy: "system",
272
265
  _updated: new Date(),
@@ -284,7 +277,7 @@ export const getMongoInitialSchema = (dbConfig) => {
284
277
  }
285
278
  if (isAuthEnabled && dbConfig.adminUser) {
286
279
  migrations.push({
287
- name: "00000000000012_data-admin-authorizations",
280
+ name: "00000000000011_data-admin-authorizations",
288
281
  up: async ({ context: db }) => {
289
282
  const database = new MongoDBDatabase(db);
290
283
  const organizationService = new OrganizationService(database);
@@ -8,7 +8,7 @@ export async function runInitialSchemaMigrations(pool, config) {
8
8
  app: config.app,
9
9
  database: config.database,
10
10
  adminUser: config.adminUser ?? { email: 'admin@test.com', password: 'admin-password' },
11
- multiTenant: config.multiTenant ?? (config.app.isMultiTenant ? { metaOrgName: 'Test Meta Organization', metaOrgCode: 'TEST_META_ORG', metaOrgDomain: TEST_META_ORG_DOMAIN } : { metaOrgName: '', metaOrgCode: '', metaOrgDomain: '' }),
11
+ multiTenant: config.multiTenant ?? (config.app.isMultiTenant ? { metaOrgName: 'Test Meta Organization', metaOrgCode: 'TEST_META_ORG', metaOrgDomains: [TEST_META_ORG_DOMAIN] } : { metaOrgName: '', metaOrgCode: '', metaOrgDomains: [] }),
12
12
  email: config.email,
13
13
  };
14
14
  const initialSchema = getPostgresInitialSchema(migrationConfig);
@@ -34,7 +34,6 @@ export const getPostgresInitialSchema = (dbConfig) => {
34
34
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
35
35
  "name" VARCHAR(255) NOT NULL UNIQUE,
36
36
  "code" VARCHAR(255) UNIQUE,
37
- "domain" VARCHAR(255) UNIQUE,
38
37
  "description" TEXT,
39
38
  "status" INTEGER NOT NULL,
40
39
  "is_meta_org" BOOLEAN NOT NULL,
@@ -52,67 +51,48 @@ export const getPostgresInitialSchema = (dbConfig) => {
52
51
  await pool.query('DROP TABLE IF EXISTS "organizations"');
53
52
  },
54
53
  });
55
- }
56
- if (isAuthEnabled)
57
54
  migrations.push({
58
- name: "00000000000003_schema-persons",
55
+ name: "00000000000002_schema-organization-domains",
59
56
  up: async ({ context: pool }) => {
60
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
61
- const personsUniqueConstraints = isMultiTenant
62
- ? `CONSTRAINT "uk_persons_org_external_id" UNIQUE ("_orgId", "external_id"),
63
- CONSTRAINT "uk_persons_org_ssn" UNIQUE ("_orgId", "ssn")`
64
- : `CONSTRAINT "uk_persons_external_id" UNIQUE ("external_id"),
65
- CONSTRAINT "uk_persons_ssn" UNIQUE ("ssn")`;
66
57
  await pool.query(`
67
- CREATE TABLE IF NOT EXISTS "persons" (
68
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
69
- ${orgColumnDef}
70
- "external_id" VARCHAR(255),
71
- "first_name" VARCHAR(255) NOT NULL,
72
- "middle_name" VARCHAR(255),
73
- "last_name" VARCHAR(255) NOT NULL,
74
- "date_of_birth" DATE,
75
- "ssn" VARCHAR(255),
76
- "is_agent" BOOLEAN NOT NULL DEFAULT FALSE,
77
- "is_client" BOOLEAN NOT NULL DEFAULT FALSE,
78
- "is_employee" BOOLEAN NOT NULL DEFAULT FALSE,
79
- "extended_types" INTEGER,
80
- "_created" TIMESTAMPTZ NOT NULL,
81
- "_createdBy" INTEGER NOT NULL,
82
- "_updated" TIMESTAMPTZ,
83
- "_updatedBy" INTEGER,
84
- "_deleted" TIMESTAMPTZ,
85
- "_deletedBy" INTEGER,
86
- ${personsUniqueConstraints}
87
- )`);
88
- await pool.query(`CREATE INDEX IF NOT EXISTS "idx_persons_external_id" ON "persons" ("external_id")`);
89
- await pool.query(`CREATE INDEX IF NOT EXISTS "idx_persons_ssn" ON "persons" ("ssn")`);
58
+ CREATE TABLE IF NOT EXISTS "organization_domains" (
59
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
60
+ "organization_id" INTEGER NOT NULL,
61
+ "domain" VARCHAR(255) NOT NULL UNIQUE,
62
+ "_created" TIMESTAMPTZ NOT NULL,
63
+ "_createdBy" INTEGER NOT NULL,
64
+ "_updated" TIMESTAMPTZ,
65
+ "_updatedBy" INTEGER,
66
+ "_deleted" TIMESTAMPTZ,
67
+ "_deletedBy" INTEGER,
68
+ CONSTRAINT "fk_organization_domains_organization"
69
+ FOREIGN KEY("organization_id") REFERENCES "organizations"("_id") ON DELETE CASCADE
70
+ )
71
+ `);
72
+ await pool.query(`CREATE INDEX IF NOT EXISTS "idx_organization_domains_organization_id" ON "organization_domains"("organization_id")`);
90
73
  },
91
74
  down: async ({ context: pool }) => {
92
- await pool.query(`DROP INDEX IF EXISTS "idx_persons_external_id"`);
93
- await pool.query(`DROP INDEX IF EXISTS "idx_persons_ssn"`);
94
- await pool.query(`DROP TABLE IF EXISTS "persons"`);
75
+ await pool.query(`DROP INDEX IF EXISTS "idx_organization_domains_organization_id"`);
76
+ await pool.query('DROP TABLE IF EXISTS "organization_domains"');
95
77
  },
96
78
  });
79
+ }
97
80
  if (isAuthEnabled)
98
81
  migrations.push({
99
- name: "00000000000004_schema-users",
82
+ name: "00000000000003_schema-users",
100
83
  up: async ({ context: pool }) => {
101
84
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
102
- let uniqueConstraint = isMultiTenant
85
+ const uniqueConstraint = isMultiTenant
103
86
  ? 'CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")'
104
87
  : 'CONSTRAINT "uk_users_email" UNIQUE ("email")';
105
- uniqueConstraint += `,
106
- CONSTRAINT "fk_users_person_id" FOREIGN KEY("person_id") REFERENCES "persons"("_id") ON DELETE CASCADE`;
107
88
  await pool.query(`
108
89
  CREATE TABLE IF NOT EXISTS "users"(
109
90
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
110
91
  ${orgColumnDef}
111
- "external_id" VARCHAR(255) UNIQUE,
92
+ "external_id" VARCHAR(255) UNIQUE,
112
93
  "email" VARCHAR(255) NOT NULL,
113
94
  "display_name" VARCHAR(255),
114
95
  "password" VARCHAR(255) NOT NULL,
115
- "person_id" INTEGER UNIQUE,
116
96
  "_lastLoggedIn" TIMESTAMPTZ,
117
97
  "_lastPasswordChange" TIMESTAMPTZ,
118
98
  "_created" TIMESTAMPTZ NOT NULL,
@@ -125,18 +105,16 @@ export const getPostgresInitialSchema = (dbConfig) => {
125
105
  )`);
126
106
  await pool.query(`CREATE INDEX IF NOT EXISTS "idx_users_external_id" ON "users"("external_id")`);
127
107
  await pool.query(`CREATE INDEX IF NOT EXISTS "idx_users_email" ON "users"("email")`);
128
- await pool.query(`CREATE INDEX IF NOT EXISTS "idx_users_person_id" ON "users"("person_id")`);
129
108
  },
130
109
  down: async ({ context: pool }) => {
131
110
  await pool.query(`DROP INDEX IF EXISTS "idx_users_external_id"`);
132
111
  await pool.query(`DROP INDEX IF EXISTS "idx_users_email"`);
133
- await pool.query(`DROP INDEX IF EXISTS "idx_users_person_id"`);
134
112
  await pool.query(`DROP TABLE IF EXISTS "users"`);
135
113
  },
136
114
  });
137
115
  if (isAuthEnabled)
138
116
  migrations.push({
139
- name: "00000000000005_schema-refresh-tokens",
117
+ name: "00000000000004_schema-refresh-tokens",
140
118
  up: async ({ context: pool }) => {
141
119
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
142
120
  await pool.query(`
@@ -159,7 +137,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
159
137
  });
160
138
  if (isAuthEnabled)
161
139
  migrations.push({
162
- name: "00000000000006_schema-password-reset-tokens",
140
+ name: "00000000000005_schema-password-reset-tokens",
163
141
  up: async ({ context: pool }) => {
164
142
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
165
143
  const uniqueConstraint = isMultiTenant
@@ -188,7 +166,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
188
166
  });
189
167
  if (isAuthEnabled)
190
168
  migrations.push({
191
- name: "00000000000007_schema-roles",
169
+ name: "00000000000006_schema-roles",
192
170
  up: async ({ context: pool }) => {
193
171
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
194
172
  const uniqueConstraint = isMultiTenant
@@ -210,7 +188,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
210
188
  });
211
189
  if (isAuthEnabled)
212
190
  migrations.push({
213
- name: "00000000000008_schema-user-roles",
191
+ name: "00000000000007_schema-user-roles",
214
192
  up: async ({ context: pool }) => {
215
193
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
216
194
  const uniqueConstraint = isMultiTenant
@@ -240,7 +218,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
240
218
  });
241
219
  if (isAuthEnabled)
242
220
  migrations.push({
243
- name: "00000000000009_schema-features",
221
+ name: "00000000000008_schema-features",
244
222
  up: async ({ context: pool }) => {
245
223
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
246
224
  const uniqueConstraint = isMultiTenant
@@ -262,7 +240,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
262
240
  });
263
241
  if (isAuthEnabled)
264
242
  migrations.push({
265
- name: "00000000000010_schema-authorizations",
243
+ name: "00000000000009_schema-authorizations",
266
244
  up: async ({ context: pool }) => {
267
245
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
268
246
  const uniqueConstraint = isMultiTenant
@@ -295,21 +273,28 @@ export const getPostgresInitialSchema = (dbConfig) => {
295
273
  });
296
274
  if (isMultiTenant) {
297
275
  migrations.push({
298
- name: "00000000000011_data-meta-org",
276
+ name: "00000000000010_data-meta-org",
299
277
  up: async ({ context: pool }) => {
300
278
  const result = await pool.query(`
301
- INSERT INTO "organizations"("name", "code", "domain", "status", "is_meta_org", "_created", "_createdBy")
302
- VALUES($1, $2, $3, 1, true, NOW(), 0)
303
- RETURNING "_id", "name", "code", "domain", "status", "is_meta_org", "_created", "_createdBy"
279
+ INSERT INTO "organizations"("name", "code", "status", "is_meta_org", "_created", "_createdBy")
280
+ VALUES($1, $2, 1, true, NOW(), 0)
281
+ RETURNING "_id", "name", "code", "status", "is_meta_org", "_created", "_createdBy"
304
282
  `, [
305
283
  dbConfig.multiTenant?.metaOrgName,
306
284
  dbConfig.multiTenant?.metaOrgCode,
307
- dbConfig.multiTenant?.metaOrgDomain,
308
285
  ]);
309
286
  if (result.rowCount === 0) {
310
287
  throw new Error("Failed to create meta organization");
311
288
  }
312
- initializeSystemUserContext(dbConfig.email?.systemEmailAddress || "system@example.com", result.rows[0]);
289
+ const metaOrg = result.rows[0];
290
+ const metaOrgDomains = dbConfig.multiTenant?.metaOrgDomains ?? [];
291
+ for (const domain of metaOrgDomains) {
292
+ await pool.query(`
293
+ INSERT INTO "organization_domains"("organization_id", "domain", "_created", "_createdBy")
294
+ VALUES($1, $2, NOW(), 0)
295
+ `, [metaOrg._id, domain]);
296
+ }
297
+ initializeSystemUserContext(dbConfig.email?.systemEmailAddress || "system@example.com", metaOrg);
313
298
  },
314
299
  down: async ({ context: pool }) => {
315
300
  await pool.query(`DELETE FROM "organizations" WHERE "is_meta_org" = TRUE`);
@@ -318,11 +303,11 @@ export const getPostgresInitialSchema = (dbConfig) => {
318
303
  }
319
304
  if (isAuthEnabled && dbConfig.adminUser) {
320
305
  migrations.push({
321
- name: "00000000000012_data-admin-user",
306
+ name: "00000000000011_data-admin-user",
322
307
  up: async ({ context: pool }) => {
323
308
  if (!isSystemUserContextInitialized()) {
324
309
  const errorMessage = isMultiTenant
325
- ? "SystemUserContext has not been initialized. The meta-org migration (00000000000011_data-meta-org) should have run before this migration. " +
310
+ ? "SystemUserContext has not been initialized. The meta-org migration (00000000000010_data-meta-org) should have run before this migration. " +
326
311
  "Please ensure metaOrgName and metaOrgCode are provided in your dbConfig."
327
312
  : "BUG: SystemUserContext has not been initialized. For non-multi-tenant setups, SystemUserContext should be initialized before migrations run.";
328
313
  console.error("❌ Migration Error:", errorMessage);
@@ -336,21 +321,13 @@ export const getPostgresInitialSchema = (dbConfig) => {
336
321
  const email = dbConfig.adminUser.email.toLowerCase();
337
322
  const client = await pool.connect();
338
323
  try {
339
- const personResult = isMultiTenant && orgId
340
- ? await client.query(`INSERT INTO "persons"("_orgId", "first_name", "last_name", "is_agent", "is_client", "is_employee", "_created", "_createdBy")
341
- VALUES($1, 'Admin', 'User', false, false, false, NOW(), 0)
342
- RETURNING "_id"`, [orgId])
343
- : await client.query(`INSERT INTO "persons"("first_name", "last_name", "is_agent", "is_client", "is_employee", "_created", "_createdBy")
344
- VALUES('Admin', 'User', false, false, false, NOW(), 0)
345
- RETURNING "_id"`);
346
- const personId = personResult.rows[0]._id;
347
- if (isMultiTenant && orgId) {
348
- await client.query(`INSERT INTO "users"("_orgId", "email", "display_name", "password", "person_id", "_created", "_createdBy")
349
- VALUES($1, $2, 'Admin User', $3, $4, NOW(), 0)`, [orgId, email, hashedPassword, personId]);
324
+ if (isMultiTenant) {
325
+ await client.query(`INSERT INTO "users"("_orgId", "email", "display_name", "password", "_created", "_createdBy")
326
+ VALUES($1, $2, 'Admin User', $3, NOW(), 0)`, [orgId, email, hashedPassword]);
350
327
  }
351
328
  else {
352
- await client.query(`INSERT INTO "users"("email", "display_name", "password", "person_id", "_created", "_createdBy")
353
- VALUES($1, 'Admin User', $2, $3, NOW(), 0)`, [email, hashedPassword, personId]);
329
+ await client.query(`INSERT INTO "users"("email", "display_name", "password", "_created", "_createdBy")
330
+ VALUES($1, 'Admin User', $2, NOW(), 0)`, [email, hashedPassword]);
354
331
  }
355
332
  }
356
333
  finally {
@@ -368,7 +345,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
368
345
  }
369
346
  if (isAuthEnabled && dbConfig.adminUser) {
370
347
  migrations.push({
371
- name: "00000000000013_data-admin-authorizations",
348
+ name: "00000000000012_data-admin-authorizations",
372
349
  up: async ({ context: pool }) => {
373
350
  const client = await pool.connect();
374
351
  try {
@@ -12,7 +12,7 @@ export interface IInitialDbMigrationConfig {
12
12
  multiTenant: {
13
13
  metaOrgName: string;
14
14
  metaOrgCode: string;
15
- metaOrgDomain: string;
15
+ metaOrgDomains: string[];
16
16
  };
17
17
  email?: IEmailConfig;
18
18
  }
@@ -5,6 +5,7 @@ export * from "./generic-query-service/generic-query.service.js";
5
5
  export * from "./generic-query-service/generic-query-service.interface.js";
6
6
  export * from "./multi-tenant-api.service.js";
7
7
  export * from "./organization.service.js";
8
+ export * from "./organization-domain.service.js";
8
9
  export * from "./password-reset-token.service.js";
9
10
  export * from "./refresh-token.service.js";
10
11
  export * from "./tenant-query-decorator.js";
@@ -5,6 +5,7 @@ export * from "./generic-query-service/generic-query.service.js";
5
5
  export * from "./generic-query-service/generic-query-service.interface.js";
6
6
  export * from "./multi-tenant-api.service.js";
7
7
  export * from "./organization.service.js";
8
+ export * from "./organization-domain.service.js";
8
9
  export * from "./password-reset-token.service.js";
9
10
  export * from "./refresh-token.service.js";
10
11
  export * from "./tenant-query-decorator.js";
@@ -0,0 +1,6 @@
1
+ import { type IOrganizationDomain } from "@loomcore/common/models";
2
+ import type { IDatabase } from "../databases/models/index.js";
3
+ import { GenericApiService } from "./generic-api-service/generic-api.service.js";
4
+ export declare class OrganizationDomainService extends GenericApiService<IOrganizationDomain> {
5
+ constructor(database: IDatabase);
6
+ }
@@ -0,0 +1,7 @@
1
+ import { OrganizationDomainSpec, } from "@loomcore/common/models";
2
+ import { GenericApiService } from "./generic-api-service/generic-api.service.js";
3
+ export class OrganizationDomainService extends GenericApiService {
4
+ constructor(database) {
5
+ super(database, "organization_domains", "organization_domain", OrganizationDomainSpec);
6
+ }
7
+ }
@@ -3,9 +3,11 @@ import type { AppIdType } from "@loomcore/common/types";
3
3
  import type { IDatabase } from "../databases/models/index.js";
4
4
  import { GenericApiService } from "./generic-api-service/generic-api.service.js";
5
5
  export declare class OrganizationService extends GenericApiService<IOrganization> {
6
+ private organizationDomainService;
6
7
  constructor(database: IDatabase);
7
8
  preProcessEntity(userContext: IUserContext, entity: Partial<IOrganization>, isCreate: boolean, allowId?: boolean): Promise<Partial<IOrganization>>;
8
9
  getAuthTokenByRepoCode(userContext: IUserContext, orgId: AppIdType): Promise<string | null>;
9
10
  validateRepoAuthToken(userContext: IUserContext, orgCode: string, authToken: string): Promise<AppIdType | null>;
10
11
  getMetaOrg(userContext: IUserContext): Promise<IOrganization | null>;
12
+ findByDomain(userContext: IUserContext, domain: string): Promise<IOrganization | null>;
11
13
  }
@@ -1,9 +1,12 @@
1
1
  import { OrganizationSpec, } from "@loomcore/common/models";
2
2
  import { BadRequestError } from "../errors/index.js";
3
3
  import { GenericApiService } from "./generic-api-service/generic-api.service.js";
4
+ import { OrganizationDomainService } from "./organization-domain.service.js";
4
5
  export class OrganizationService extends GenericApiService {
6
+ organizationDomainService;
5
7
  constructor(database) {
6
8
  super(database, "organizations", "organization", OrganizationSpec);
9
+ this.organizationDomainService = new OrganizationDomainService(database);
7
10
  }
8
11
  async preProcessEntity(userContext, entity, isCreate, allowId = true) {
9
12
  if (isCreate) {
@@ -38,4 +41,15 @@ export class OrganizationService extends GenericApiService {
38
41
  });
39
42
  return org;
40
43
  }
44
+ async findByDomain(userContext, domain) {
45
+ const organizationDomain = await this.organizationDomainService.findOne(userContext, {
46
+ filters: { domain: { eq: domain } },
47
+ });
48
+ if (!organizationDomain) {
49
+ return null;
50
+ }
51
+ return this.findOne(userContext, {
52
+ filters: { _id: { eq: organizationDomain.organizationId } },
53
+ });
54
+ }
41
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "private": false,
5
5
  "description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb or PostgreSQL",
6
6
  "scripts": {
@@ -58,7 +58,7 @@
58
58
  "qs": "^6.15.2"
59
59
  },
60
60
  "peerDependencies": {
61
- "@loomcore/common": "^0.0.74",
61
+ "@loomcore/common": "^0.0.77",
62
62
  "@sinclair/typebox": "0.34.33",
63
63
  "cookie-parser": "^1.4.6",
64
64
  "cors": "^2.8.5",