@loomcore/api 0.1.91 → 0.1.94

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.
Files changed (39) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +77 -77
  3. package/dist/__tests__/common-test.utils.js +0 -4
  4. package/dist/__tests__/postgres-test-migrations/postgres-test-schema.js +266 -266
  5. package/dist/__tests__/postgres.test-database.js +8 -8
  6. package/dist/config/base-api-config.d.ts +1 -1
  7. package/dist/config/base-api-config.js +12 -7
  8. package/dist/databases/migrations/migration-runner.d.ts +3 -5
  9. package/dist/databases/migrations/migration-runner.js +40 -44
  10. package/dist/databases/mongo-db/migrations/mongo-initial-schema.d.ts +2 -3
  11. package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +102 -81
  12. package/dist/databases/mongo-db/utils/build-mongo-url.util.d.ts +4 -2
  13. package/dist/databases/mongo-db/utils/convert-operations-to-pipeline.util.js +141 -39
  14. package/dist/databases/postgres/commands/postgres-batch-update.command.js +7 -7
  15. package/dist/databases/postgres/commands/postgres-create-many.command.js +4 -4
  16. package/dist/databases/postgres/commands/postgres-create.command.js +4 -4
  17. package/dist/databases/postgres/commands/postgres-full-update-by-id.command.js +13 -13
  18. package/dist/databases/postgres/commands/postgres-partial-update-by-id.command.js +7 -7
  19. package/dist/databases/postgres/commands/postgres-update.command.js +7 -7
  20. package/dist/databases/postgres/migrations/__tests__/test-migration-helper.js +8 -1
  21. package/dist/databases/postgres/migrations/postgres-initial-schema.d.ts +2 -3
  22. package/dist/databases/postgres/migrations/postgres-initial-schema.js +263 -266
  23. package/dist/databases/postgres/postgres.database.js +17 -17
  24. package/dist/databases/postgres/utils/build-join-clauses.js +151 -151
  25. package/dist/databases/postgres/utils/build-postgres-url.util.d.ts +4 -2
  26. package/dist/databases/postgres/utils/build-select-clause.js +6 -6
  27. package/dist/databases/postgres/utils/does-table-exist.util.js +4 -4
  28. package/dist/models/app-config.interface.d.ts +8 -0
  29. package/dist/models/base-api-config.interface.d.ts +4 -17
  30. package/dist/models/database-config.interface.d.ts +7 -0
  31. package/dist/models/index.d.ts +3 -1
  32. package/dist/models/index.js +3 -1
  33. package/dist/models/initial-database-config.interface.d.ts +16 -0
  34. package/dist/models/initial-database-config.interface.js +1 -0
  35. package/package.json +92 -92
  36. package/dist/models/multi-tenant-config.interface.d.ts +0 -4
  37. package/dist/models/reset-api-config.interface.d.ts +0 -6
  38. /package/dist/models/{multi-tenant-config.interface.js → app-config.interface.js} +0 -0
  39. /package/dist/models/{reset-api-config.interface.js → database-config.interface.js} +0 -0
@@ -1,39 +1,34 @@
1
1
  import { initializeSystemUserContext, EmptyUserContext, getSystemUserContext, isSystemUserContextInitialized } from '@loomcore/common/models';
2
2
  import { PostgresDatabase } from '../postgres.database.js';
3
- import { AuthService, OrganizationService } from '../../../services/index.js';
4
- export const getPostgresInitialSchema = (config, resetConfig) => {
3
+ import { OrganizationService } from '../../../services/index.js';
4
+ import { passwordUtils } from '../../../utils/index.js';
5
+ export const getPostgresInitialSchema = (dbConfig) => {
5
6
  const migrations = [];
6
- const isMultiTenant = config.app.isMultiTenant;
7
- if (isMultiTenant && !config.multiTenant) {
7
+ const isMultiTenant = dbConfig.app.isMultiTenant;
8
+ if (isMultiTenant && !dbConfig.multiTenant) {
8
9
  throw new Error('Multi-tenant configuration is enabled but multi-tenant configuration is not provided');
9
10
  }
10
- const isAuthEnabled = config.app.isAuthEnabled;
11
- if (isAuthEnabled && !config.auth) {
12
- throw new Error('Auth enabled without auth configuration');
13
- }
14
- if (isAuthEnabled && (!config.thirdPartyClients?.emailClient || !config.email)) {
15
- throw new Error('Auth enabled without email client or email configuration');
16
- }
11
+ const isAuthEnabled = dbConfig.app.isAuthEnabled;
17
12
  if (isMultiTenant) {
18
13
  migrations.push({
19
14
  name: '00000000000001_schema-organizations',
20
15
  up: async ({ context: pool }) => {
21
- await pool.query(`
22
- CREATE TABLE IF NOT EXISTS "organizations" (
23
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
24
- "name" VARCHAR(255) NOT NULL UNIQUE,
25
- "code" VARCHAR(255) NOT NULL UNIQUE,
26
- "description" TEXT,
27
- "status" INTEGER NOT NULL,
28
- "is_meta_org" BOOLEAN NOT NULL,
29
- "auth_token" TEXT,
30
- "_created" TIMESTAMPTZ NOT NULL,
31
- "_createdBy" INTEGER NOT NULL,
32
- "_updated" TIMESTAMPTZ NOT NULL,
33
- "_updatedBy" INTEGER NOT NULL,
34
- "_deleted" TIMESTAMPTZ,
35
- "_deletedBy" INTEGER
36
- )
16
+ await pool.query(`
17
+ CREATE TABLE IF NOT EXISTS "organizations" (
18
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
19
+ "name" VARCHAR(255) NOT NULL UNIQUE,
20
+ "code" VARCHAR(255) NOT NULL UNIQUE,
21
+ "description" TEXT,
22
+ "status" INTEGER NOT NULL,
23
+ "is_meta_org" BOOLEAN NOT NULL,
24
+ "auth_token" TEXT,
25
+ "_created" TIMESTAMPTZ NOT NULL,
26
+ "_createdBy" INTEGER NOT NULL,
27
+ "_updated" TIMESTAMPTZ NOT NULL,
28
+ "_updatedBy" INTEGER NOT NULL,
29
+ "_deleted" TIMESTAMPTZ,
30
+ "_deletedBy" INTEGER
31
+ )
37
32
  `);
38
33
  },
39
34
  down: async ({ context: pool }) => {
@@ -46,26 +41,26 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
46
41
  name: '00000000000002_schema-persons',
47
42
  up: async ({ context: pool }) => {
48
43
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
49
- await pool.query(`
50
- CREATE TABLE IF NOT EXISTS "persons" (
51
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
52
- ${orgColumnDef}
53
- "external_id" VARCHAR(255) UNIQUE,
54
- "first_name" VARCHAR(255) NOT NULL,
55
- "middle_name" VARCHAR(255),
56
- "last_name" VARCHAR(255) NOT NULL,
57
- "date_of_birth" DATE,
58
- "is_agent" BOOLEAN NOT NULL DEFAULT FALSE,
59
- "is_client" BOOLEAN NOT NULL DEFAULT FALSE,
60
- "is_employee" BOOLEAN NOT NULL DEFAULT FALSE,
61
- "extended_types" INTEGER,
62
- "_created" TIMESTAMPTZ NOT NULL,
63
- "_createdBy" INTEGER NOT NULL,
64
- "_updated" TIMESTAMPTZ NOT NULL,
65
- "_updatedBy" INTEGER NOT NULL,
66
- "_deleted" TIMESTAMPTZ,
67
- "_deletedBy" INTEGER
68
- )
44
+ await pool.query(`
45
+ CREATE TABLE IF NOT EXISTS "persons" (
46
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
47
+ ${orgColumnDef}
48
+ "external_id" VARCHAR(255) UNIQUE,
49
+ "first_name" VARCHAR(255) NOT NULL,
50
+ "middle_name" VARCHAR(255),
51
+ "last_name" VARCHAR(255) NOT NULL,
52
+ "date_of_birth" DATE,
53
+ "is_agent" BOOLEAN NOT NULL DEFAULT FALSE,
54
+ "is_client" BOOLEAN NOT NULL DEFAULT FALSE,
55
+ "is_employee" BOOLEAN NOT NULL DEFAULT FALSE,
56
+ "extended_types" INTEGER,
57
+ "_created" TIMESTAMPTZ NOT NULL,
58
+ "_createdBy" INTEGER NOT NULL,
59
+ "_updated" TIMESTAMPTZ NOT NULL,
60
+ "_updatedBy" INTEGER NOT NULL,
61
+ "_deleted" TIMESTAMPTZ,
62
+ "_deletedBy" INTEGER
63
+ )
69
64
  `);
70
65
  },
71
66
  down: async ({ context: pool }) => {
@@ -80,27 +75,27 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
80
75
  let uniqueConstraint = isMultiTenant
81
76
  ? 'CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")'
82
77
  : 'CONSTRAINT "uk_users_email" UNIQUE ("email")';
83
- uniqueConstraint += `,
78
+ uniqueConstraint += `,
84
79
  CONSTRAINT "fk_users_person_id" FOREIGN KEY("person_id") REFERENCES "persons"("_id") ON DELETE CASCADE`;
85
- await pool.query(`
86
- CREATE TABLE IF NOT EXISTS "users" (
87
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
88
- ${orgColumnDef}
89
- "external_id" VARCHAR(255) UNIQUE,
90
- "email" VARCHAR(255) NOT NULL,
91
- "display_name" VARCHAR(255),
92
- "password" VARCHAR(255) NOT NULL,
93
- "person_id" INTEGER UNIQUE,
94
- "_lastLoggedIn" TIMESTAMPTZ,
95
- "_lastPasswordChange" TIMESTAMPTZ,
96
- "_created" TIMESTAMPTZ NOT NULL,
97
- "_createdBy" INTEGER NOT NULL,
98
- "_updated" TIMESTAMPTZ NOT NULL,
99
- "_updatedBy" INTEGER NOT NULL,
100
- "_deleted" TIMESTAMPTZ,
101
- "_deletedBy" INTEGER,
102
- ${uniqueConstraint}
103
- )
80
+ await pool.query(`
81
+ CREATE TABLE IF NOT EXISTS "users" (
82
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
83
+ ${orgColumnDef}
84
+ "external_id" VARCHAR(255) UNIQUE,
85
+ "email" VARCHAR(255) NOT NULL,
86
+ "display_name" VARCHAR(255),
87
+ "password" VARCHAR(255) NOT NULL,
88
+ "person_id" INTEGER UNIQUE,
89
+ "_lastLoggedIn" TIMESTAMPTZ,
90
+ "_lastPasswordChange" TIMESTAMPTZ,
91
+ "_created" TIMESTAMPTZ NOT NULL,
92
+ "_createdBy" INTEGER NOT NULL,
93
+ "_updated" TIMESTAMPTZ NOT NULL,
94
+ "_updatedBy" INTEGER NOT NULL,
95
+ "_deleted" TIMESTAMPTZ,
96
+ "_deletedBy" INTEGER,
97
+ ${uniqueConstraint}
98
+ )
104
99
  `);
105
100
  },
106
101
  down: async ({ context: pool }) => {
@@ -112,18 +107,18 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
112
107
  name: '00000000000004_schema-refresh-tokens',
113
108
  up: async ({ context: pool }) => {
114
109
  const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
115
- await pool.query(`
116
- CREATE TABLE IF NOT EXISTS "refresh_tokens" (
117
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
118
- ${orgColumnDef}
119
- "token" VARCHAR(255) NOT NULL,
120
- "device_id" VARCHAR(255) NOT NULL,
121
- "user_id" INTEGER NOT NULL,
122
- "expires_on" BIGINT NOT NULL,
123
- "created" TIMESTAMPTZ NOT NULL,
124
- "created_by" INTEGER NOT NULL,
125
- CONSTRAINT "fk_refresh_tokens_user" FOREIGN KEY ("user_id") REFERENCES "users"("_id") ON DELETE CASCADE
126
- )
110
+ await pool.query(`
111
+ CREATE TABLE IF NOT EXISTS "refresh_tokens" (
112
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
113
+ ${orgColumnDef}
114
+ "token" VARCHAR(255) NOT NULL,
115
+ "device_id" VARCHAR(255) NOT NULL,
116
+ "user_id" INTEGER NOT NULL,
117
+ "expires_on" BIGINT NOT NULL,
118
+ "created" TIMESTAMPTZ NOT NULL,
119
+ "created_by" INTEGER NOT NULL,
120
+ CONSTRAINT "fk_refresh_tokens_user" FOREIGN KEY ("user_id") REFERENCES "users"("_id") ON DELETE CASCADE
121
+ )
127
122
  `);
128
123
  },
129
124
  down: async ({ context: pool }) => {
@@ -138,21 +133,21 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
138
133
  const uniqueConstraint = isMultiTenant
139
134
  ? 'CONSTRAINT "uk_passwordResetTokens_email" UNIQUE ("_orgId", "email")'
140
135
  : 'CONSTRAINT "uk_passwordResetTokens_email" UNIQUE ("email")';
141
- await pool.query(`
142
- CREATE TABLE IF NOT EXISTS "password_reset_tokens" (
143
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
144
- ${orgColumnDef}
145
- "email" VARCHAR(255) NOT NULL,
146
- "token" VARCHAR(255) NOT NULL,
147
- "expires_on" BIGINT NOT NULL,
148
- "_created" TIMESTAMPTZ NOT NULL,
149
- "_createdBy" INTEGER NOT NULL,
150
- "_updated" TIMESTAMPTZ NOT NULL,
151
- "_updatedBy" INTEGER NOT NULL,
152
- "_deleted" TIMESTAMPTZ,
153
- "_deletedBy" INTEGER,
154
- ${uniqueConstraint}
155
- )
136
+ await pool.query(`
137
+ CREATE TABLE IF NOT EXISTS "password_reset_tokens" (
138
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
139
+ ${orgColumnDef}
140
+ "email" VARCHAR(255) NOT NULL,
141
+ "token" VARCHAR(255) NOT NULL,
142
+ "expires_on" BIGINT NOT NULL,
143
+ "_created" TIMESTAMPTZ NOT NULL,
144
+ "_createdBy" INTEGER NOT NULL,
145
+ "_updated" TIMESTAMPTZ NOT NULL,
146
+ "_updatedBy" INTEGER NOT NULL,
147
+ "_deleted" TIMESTAMPTZ,
148
+ "_deletedBy" INTEGER,
149
+ ${uniqueConstraint}
150
+ )
156
151
  `);
157
152
  },
158
153
  down: async ({ context: pool }) => {
@@ -167,14 +162,14 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
167
162
  const uniqueConstraint = isMultiTenant
168
163
  ? 'CONSTRAINT "uk_roles_name" UNIQUE ("_orgId", "name")'
169
164
  : 'CONSTRAINT "uk_roles_name" UNIQUE ("name")';
170
- await pool.query(`
171
- CREATE TABLE IF NOT EXISTS "roles" (
172
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
173
- ${orgColumnDef}
174
- "name" VARCHAR(255) NOT NULL,
175
- "description" TEXT,
176
- ${uniqueConstraint}
177
- )
165
+ await pool.query(`
166
+ CREATE TABLE IF NOT EXISTS "roles" (
167
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
168
+ ${orgColumnDef}
169
+ "name" VARCHAR(255) NOT NULL,
170
+ "description" TEXT,
171
+ ${uniqueConstraint}
172
+ )
178
173
  `);
179
174
  },
180
175
  down: async ({ context: pool }) => {
@@ -189,22 +184,22 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
189
184
  const uniqueConstraint = isMultiTenant
190
185
  ? 'CONSTRAINT "uk_user_roles" UNIQUE ("_orgId", "user_id", "role_id")'
191
186
  : 'CONSTRAINT "uk_user_roles" UNIQUE ("user_id", "role_id")';
192
- await pool.query(`
193
- CREATE TABLE IF NOT EXISTS "user_roles" (
194
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
195
- ${orgColumnDef}
196
- "user_id" INTEGER NOT NULL,
197
- "role_id" INTEGER NOT NULL,
198
- "_created" TIMESTAMPTZ NOT NULL,
199
- "_createdBy" INTEGER NOT NULL,
200
- "_updated" TIMESTAMPTZ NOT NULL,
201
- "_updatedBy" INTEGER NOT NULL,
202
- "_deleted" TIMESTAMPTZ,
203
- "_deletedBy" INTEGER,
204
- CONSTRAINT "fk_user_roles_user" FOREIGN KEY ("user_id") REFERENCES "users"("_id") ON DELETE CASCADE,
205
- CONSTRAINT "fk_user_roles_role" FOREIGN KEY ("role_id") REFERENCES "roles"("_id") ON DELETE CASCADE,
206
- ${uniqueConstraint}
207
- )
187
+ await pool.query(`
188
+ CREATE TABLE IF NOT EXISTS "user_roles" (
189
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
190
+ ${orgColumnDef}
191
+ "user_id" INTEGER NOT NULL,
192
+ "role_id" INTEGER NOT NULL,
193
+ "_created" TIMESTAMPTZ NOT NULL,
194
+ "_createdBy" INTEGER NOT NULL,
195
+ "_updated" TIMESTAMPTZ NOT NULL,
196
+ "_updatedBy" INTEGER NOT NULL,
197
+ "_deleted" TIMESTAMPTZ,
198
+ "_deletedBy" INTEGER,
199
+ CONSTRAINT "fk_user_roles_user" FOREIGN KEY ("user_id") REFERENCES "users"("_id") ON DELETE CASCADE,
200
+ CONSTRAINT "fk_user_roles_role" FOREIGN KEY ("role_id") REFERENCES "roles"("_id") ON DELETE CASCADE,
201
+ ${uniqueConstraint}
202
+ )
208
203
  `);
209
204
  },
210
205
  down: async ({ context: pool }) => {
@@ -219,14 +214,14 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
219
214
  const uniqueConstraint = isMultiTenant
220
215
  ? 'CONSTRAINT "uk_features" UNIQUE ("_orgId", "name")'
221
216
  : 'CONSTRAINT "uk_features" UNIQUE ("name")';
222
- await pool.query(`
223
- CREATE TABLE IF NOT EXISTS "features" (
224
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
225
- ${orgColumnDef}
226
- "name" VARCHAR(255) NOT NULL,
227
- "description" TEXT,
228
- ${uniqueConstraint}
229
- )
217
+ await pool.query(`
218
+ CREATE TABLE IF NOT EXISTS "features" (
219
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
220
+ ${orgColumnDef}
221
+ "name" VARCHAR(255) NOT NULL,
222
+ "description" TEXT,
223
+ ${uniqueConstraint}
224
+ )
230
225
  `);
231
226
  },
232
227
  down: async ({ context: pool }) => {
@@ -241,25 +236,25 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
241
236
  const uniqueConstraint = isMultiTenant
242
237
  ? 'CONSTRAINT "uk_authorizations" UNIQUE ("_orgId", "role_id", "feature_id")'
243
238
  : 'CONSTRAINT "uk_authorizations" UNIQUE ("role_id", "feature_id")';
244
- await pool.query(`
245
- CREATE TABLE IF NOT EXISTS "authorizations" (
246
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
247
- ${orgColumnDef}
248
- "role_id" INTEGER NOT NULL,
249
- "feature_id" INTEGER NOT NULL,
250
- "start_date" TIMESTAMPTZ,
251
- "end_date" TIMESTAMPTZ,
252
- "config" JSONB,
253
- "_created" TIMESTAMPTZ NOT NULL,
254
- "_createdBy" INTEGER NOT NULL,
255
- "_updated" TIMESTAMPTZ NOT NULL,
256
- "_updatedBy" INTEGER NOT NULL,
257
- "_deleted" TIMESTAMPTZ,
258
- "_deletedBy" INTEGER,
259
- CONSTRAINT "fk_authorizations_role" FOREIGN KEY ("role_id") REFERENCES "roles"("_id") ON DELETE CASCADE,
260
- CONSTRAINT "fk_authorizations_feature" FOREIGN KEY ("feature_id") REFERENCES "features"("_id") ON DELETE CASCADE,
261
- ${uniqueConstraint}
262
- )
239
+ await pool.query(`
240
+ CREATE TABLE IF NOT EXISTS "authorizations" (
241
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
242
+ ${orgColumnDef}
243
+ "role_id" INTEGER NOT NULL,
244
+ "feature_id" INTEGER NOT NULL,
245
+ "start_date" TIMESTAMPTZ,
246
+ "end_date" TIMESTAMPTZ,
247
+ "config" JSONB,
248
+ "_created" TIMESTAMPTZ NOT NULL,
249
+ "_createdBy" INTEGER NOT NULL,
250
+ "_updated" TIMESTAMPTZ NOT NULL,
251
+ "_updatedBy" INTEGER NOT NULL,
252
+ "_deleted" TIMESTAMPTZ,
253
+ "_deletedBy" INTEGER,
254
+ CONSTRAINT "fk_authorizations_role" FOREIGN KEY ("role_id") REFERENCES "roles"("_id") ON DELETE CASCADE,
255
+ CONSTRAINT "fk_authorizations_feature" FOREIGN KEY ("feature_id") REFERENCES "features"("_id") ON DELETE CASCADE,
256
+ ${uniqueConstraint}
257
+ )
263
258
  `);
264
259
  },
265
260
  down: async ({ context: pool }) => {
@@ -270,68 +265,68 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
270
265
  migrations.push({
271
266
  name: '00000000000010_data-meta-org',
272
267
  up: async ({ context: pool }) => {
273
- const result = await pool.query(`
274
- INSERT INTO "organizations" ("name", "code", "status", "is_meta_org", "_created", "_createdBy", "_updated", "_updatedBy")
275
- VALUES ($1, $2, 1, true, NOW(), 0, NOW(), 0)
276
- RETURNING "_id", "name", "code", "status", "is_meta_org", "_created", "_createdBy", "_updated", "_updatedBy"
277
- `, [config.multiTenant?.metaOrgName, config.multiTenant?.metaOrgCode]);
268
+ const result = await pool.query(`
269
+ INSERT INTO "organizations" ("name", "code", "status", "is_meta_org", "_created", "_createdBy", "_updated", "_updatedBy")
270
+ VALUES ($1, $2, 1, true, NOW(), 0, NOW(), 0)
271
+ RETURNING "_id", "name", "code", "status", "is_meta_org", "_created", "_createdBy", "_updated", "_updatedBy"
272
+ `, [dbConfig.multiTenant?.metaOrgName, dbConfig.multiTenant?.metaOrgCode]);
278
273
  if (result.rowCount === 0) {
279
274
  throw new Error('Failed to create meta organization');
280
275
  }
281
- initializeSystemUserContext(config.email?.systemEmailAddress || 'system@example.com', result.rows[0]);
276
+ initializeSystemUserContext(dbConfig.email?.systemEmailAddress || 'system@example.com', result.rows[0]);
282
277
  },
283
278
  down: async ({ context: pool }) => {
284
279
  await pool.query(`DELETE FROM "organizations" WHERE "is_meta_org" = TRUE`);
285
280
  }
286
281
  });
287
282
  }
288
- if (isAuthEnabled && resetConfig?.adminUser) {
283
+ if (isAuthEnabled && dbConfig.adminUser) {
289
284
  migrations.push({
290
285
  name: '00000000000011_data-admin-user',
291
286
  up: async ({ context: pool }) => {
287
+ if (!isSystemUserContextInitialized()) {
288
+ const errorMessage = isMultiTenant
289
+ ? 'SystemUserContext has not been initialized. The meta-org migration (00000000000010_data-meta-org) should have run before this migration. ' +
290
+ 'Please ensure metaOrgName and metaOrgCode are provided in your dbConfig.'
291
+ : 'BUG: SystemUserContext has not been initialized. For non-multi-tenant setups, SystemUserContext should be initialized before migrations run.';
292
+ console.error('❌ Migration Error:', errorMessage);
293
+ throw new Error(errorMessage);
294
+ }
295
+ const systemUserContext = getSystemUserContext();
296
+ const orgId = isMultiTenant ? systemUserContext.organization?._id : undefined;
297
+ const hashedPassword = await passwordUtils.hashPassword(dbConfig.adminUser.password);
298
+ const email = dbConfig.adminUser.email.toLowerCase();
292
299
  const client = await pool.connect();
293
300
  try {
294
- const database = new PostgresDatabase(client);
295
- const authService = new AuthService(database);
296
- if (!isSystemUserContextInitialized()) {
297
- const errorMessage = isMultiTenant
298
- ? 'SystemUserContext has not been initialized. The meta-org migration (00000000000009_data-meta-org) should have run before this migration. ' +
299
- 'This migration only runs if config.app.metaOrgName and config.app.metaOrgCode are provided. ' +
300
- 'Please ensure both values are set in your config.'
301
- : 'BUG: SystemUserContext has not been initialized. For non-multi-tenant setups, SystemUserContext should be initialized before migrations run.';
302
- console.error('❌ Migration Error:', errorMessage);
303
- throw new Error(errorMessage);
304
- }
305
- const systemUserContext = getSystemUserContext();
306
- const userData = {
307
- email: resetConfig.adminUser.email,
308
- password: resetConfig.adminUser.password,
309
- displayName: 'Admin User',
310
- };
311
- if (isMultiTenant && systemUserContext.organization?._id) {
312
- userData._orgId = systemUserContext.organization._id;
301
+ const personResult = isMultiTenant && orgId
302
+ ? await client.query(`INSERT INTO "persons" ("_orgId", "first_name", "last_name", "is_agent", "is_client", "is_employee", "_created", "_createdBy", "_updated", "_updatedBy")
303
+ VALUES ($1, 'Admin', 'User', false, false, false, NOW(), 0, NOW(), 0)
304
+ RETURNING "_id"`, [orgId])
305
+ : await client.query(`INSERT INTO "persons" ("first_name", "last_name", "is_agent", "is_client", "is_employee", "_created", "_createdBy", "_updated", "_updatedBy")
306
+ VALUES ('Admin', 'User', false, false, false, NOW(), 0, NOW(), 0)
307
+ RETURNING "_id"`);
308
+ const personId = personResult.rows[0]._id;
309
+ if (isMultiTenant && orgId) {
310
+ await client.query(`INSERT INTO "users" ("_orgId", "email", "display_name", "password", "person_id", "_created", "_createdBy", "_updated", "_updatedBy")
311
+ VALUES ($1, $2, 'Admin User', $3, $4, NOW(), 0, NOW(), 0)`, [orgId, email, hashedPassword, personId]);
313
312
  }
314
- const personData = {
315
- firstName: 'Admin',
316
- lastName: 'User',
317
- };
318
- if (isMultiTenant && systemUserContext.organization?._id) {
319
- personData._orgId = systemUserContext.organization._id;
313
+ else {
314
+ await client.query(`INSERT INTO "users" ("email", "display_name", "password", "person_id", "_created", "_createdBy", "_updated", "_updatedBy")
315
+ VALUES ($1, 'Admin User', $2, $3, NOW(), 0, NOW(), 0)`, [email, hashedPassword, personId]);
320
316
  }
321
- await authService.createUser(systemUserContext, userData, personData);
322
317
  }
323
318
  finally {
324
319
  client.release();
325
320
  }
326
321
  },
327
322
  down: async ({ context: pool }) => {
328
- if (!resetConfig?.adminUser?.email)
323
+ if (!dbConfig.adminUser?.email)
329
324
  return;
330
- const result = await pool.query(`DELETE FROM "users" WHERE "email" = $1`, [resetConfig.adminUser.email]);
325
+ await pool.query(`DELETE FROM "users" WHERE "email" = $1`, [dbConfig.adminUser.email.toLowerCase()]);
331
326
  }
332
327
  });
333
328
  }
334
- if (config.auth && resetConfig) {
329
+ if (isAuthEnabled && dbConfig.adminUser) {
335
330
  migrations.push({
336
331
  name: '00000000000012_data-admin-authorizations',
337
332
  up: async ({ context: pool }) => {
@@ -339,73 +334,75 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
339
334
  try {
340
335
  const database = new PostgresDatabase(client);
341
336
  const organizationService = new OrganizationService(database);
342
- const authService = new AuthService(database);
343
337
  const metaOrg = isMultiTenant ? await organizationService.getMetaOrg(EmptyUserContext) : undefined;
344
338
  if (isMultiTenant && !metaOrg) {
345
339
  throw new Error('Meta organization not found. Ensure meta-org migration ran successfully.');
346
340
  }
347
- const adminUser = await authService.getUserByEmail(resetConfig.adminUser.email);
348
- if (!adminUser) {
341
+ const email = dbConfig.adminUser.email.toLowerCase();
342
+ const userResult = await client.query(`SELECT "_id" FROM "users" WHERE "email" = $1`, [email]);
343
+ const adminUserRow = userResult.rows[0];
344
+ if (!adminUserRow) {
349
345
  throw new Error('Admin user not found. Ensure admin-user migration ran successfully.');
350
346
  }
347
+ const adminUserId = adminUserRow._id;
351
348
  await client.query('BEGIN');
352
349
  try {
353
350
  const roleResult = isMultiTenant
354
- ? await client.query(`
355
- INSERT INTO "roles" ("_orgId", "name")
356
- VALUES ($1, 'admin')
357
- RETURNING "_id"
351
+ ? await client.query(`
352
+ INSERT INTO "roles" ("_orgId", "name")
353
+ VALUES ($1, 'admin')
354
+ RETURNING "_id"
358
355
  `, [metaOrg._id])
359
- : await client.query(`
360
- INSERT INTO "roles" ("name")
361
- VALUES ('admin')
362
- RETURNING "_id"
356
+ : await client.query(`
357
+ INSERT INTO "roles" ("name")
358
+ VALUES ('admin')
359
+ RETURNING "_id"
363
360
  `);
364
361
  if (roleResult.rowCount === 0) {
365
362
  throw new Error('Failed to create admin role');
366
363
  }
367
364
  const roleId = roleResult.rows[0]._id;
368
365
  const userRoleResult = isMultiTenant
369
- ? await client.query(`
370
- INSERT INTO "user_roles" ("_orgId", "user_id", "role_id", "_created", "_createdBy", "_updated", "_updatedBy")
371
- VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
372
- `, [metaOrg._id, adminUser._id, roleId])
373
- : await client.query(`
374
- INSERT INTO "user_roles" ("user_id", "role_id", "_created", "_createdBy", "_updated", "_updatedBy")
375
- VALUES ($1, $2, NOW(), 0, NOW(), 0)
376
- `, [adminUser._id, roleId]);
366
+ ? await client.query(`
367
+ INSERT INTO "user_roles" ("_orgId", "user_id", "role_id", "_created", "_createdBy", "_updated", "_updatedBy")
368
+ VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
369
+ `, [metaOrg._id, adminUserId, roleId])
370
+ : await client.query(`
371
+ INSERT INTO "user_roles" ("user_id", "role_id", "_created", "_createdBy", "_updated", "_updatedBy")
372
+ VALUES ($1, $2, NOW(), 0, NOW(), 0)
373
+ `, [adminUserId, roleId]);
377
374
  if (userRoleResult.rowCount === 0) {
378
375
  throw new Error('Failed to create user role');
379
376
  }
380
377
  const featureResult = isMultiTenant
381
- ? await client.query(`
382
- INSERT INTO "features" ("_orgId", "name")
383
- VALUES ($1, 'admin')
384
- RETURNING "_id"
378
+ ? await client.query(`
379
+ INSERT INTO "features" ("_orgId", "name")
380
+ VALUES ($1, 'admin')
381
+ RETURNING "_id"
385
382
  `, [metaOrg._id])
386
- : await client.query(`
387
- INSERT INTO "features" ("name")
388
- VALUES ('admin')
389
- RETURNING "_id"
383
+ : await client.query(`
384
+ INSERT INTO "features" ("name")
385
+ VALUES ('admin')
386
+ RETURNING "_id"
390
387
  `);
391
388
  if (featureResult.rowCount === 0) {
392
389
  throw new Error('Failed to create admin feature');
393
390
  }
394
391
  const featureId = featureResult.rows[0]._id;
395
392
  const authorizationResult = isMultiTenant
396
- ? await client.query(`
397
- INSERT INTO "authorizations" (
398
- "_orgId", "role_id", "feature_id",
399
- "_created", "_createdBy", "_updated", "_updatedBy"
400
- )
401
- VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
393
+ ? await client.query(`
394
+ INSERT INTO "authorizations" (
395
+ "_orgId", "role_id", "feature_id",
396
+ "_created", "_createdBy", "_updated", "_updatedBy"
397
+ )
398
+ VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
402
399
  `, [metaOrg._id, roleId, featureId])
403
- : await client.query(`
404
- INSERT INTO "authorizations" (
405
- "role_id", "feature_id",
406
- "_created", "_createdBy", "_updated", "_updatedBy"
407
- )
408
- VALUES ($1, $2, NOW(), 0, NOW(), 0)
400
+ : await client.query(`
401
+ INSERT INTO "authorizations" (
402
+ "role_id", "feature_id",
403
+ "_created", "_createdBy", "_updated", "_updatedBy"
404
+ )
405
+ VALUES ($1, $2, NOW(), 0, NOW(), 0)
409
406
  `, [roleId, featureId]);
410
407
  if (authorizationResult.rowCount === 0) {
411
408
  throw new Error('Failed to create admin authorization');
@@ -432,61 +429,61 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
432
429
  await client.query('BEGIN');
433
430
  try {
434
431
  if (isMultiTenant) {
435
- await client.query(`
436
- DELETE FROM "authorizations"
437
- WHERE "_orgId" = $1
438
- AND "feature_id" IN (
439
- SELECT "_id" FROM "features"
440
- WHERE "_orgId" = $1 AND "name" = 'admin'
441
- )
442
- AND "role_id" IN (
443
- SELECT "_id" FROM "roles"
444
- WHERE "_orgId" = $1 AND "name" = 'admin'
445
- )
432
+ await client.query(`
433
+ DELETE FROM "authorizations"
434
+ WHERE "_orgId" = $1
435
+ AND "feature_id" IN (
436
+ SELECT "_id" FROM "features"
437
+ WHERE "_orgId" = $1 AND "name" = 'admin'
438
+ )
439
+ AND "role_id" IN (
440
+ SELECT "_id" FROM "roles"
441
+ WHERE "_orgId" = $1 AND "name" = 'admin'
442
+ )
446
443
  `, [metaOrg._id]);
447
- await client.query(`
448
- DELETE FROM "features"
449
- WHERE "_orgId" = $1 AND "name" = 'admin'
444
+ await client.query(`
445
+ DELETE FROM "features"
446
+ WHERE "_orgId" = $1 AND "name" = 'admin'
450
447
  `, [metaOrg._id]);
451
- await client.query(`
452
- DELETE FROM "user_roles"
453
- WHERE "_orgId" = $1
454
- AND "role_id" IN (
455
- SELECT "_id" FROM "roles"
456
- WHERE "_orgId" = $1 AND "name" = 'admin'
457
- )
448
+ await client.query(`
449
+ DELETE FROM "user_roles"
450
+ WHERE "_orgId" = $1
451
+ AND "role_id" IN (
452
+ SELECT "_id" FROM "roles"
453
+ WHERE "_orgId" = $1 AND "name" = 'admin'
454
+ )
458
455
  `, [metaOrg._id]);
459
- await client.query(`
460
- DELETE FROM "roles"
461
- WHERE "_orgId" = $1 AND "name" = 'admin'
456
+ await client.query(`
457
+ DELETE FROM "roles"
458
+ WHERE "_orgId" = $1 AND "name" = 'admin'
462
459
  `, [metaOrg._id]);
463
460
  }
464
461
  else {
465
- await client.query(`
466
- DELETE FROM "authorizations"
467
- WHERE "feature_id" IN (
468
- SELECT "_id" FROM "features"
469
- WHERE "name" = 'admin'
470
- )
471
- AND "role_id" IN (
472
- SELECT "_id" FROM "roles"
473
- WHERE "name" = 'admin'
474
- )
462
+ await client.query(`
463
+ DELETE FROM "authorizations"
464
+ WHERE "feature_id" IN (
465
+ SELECT "_id" FROM "features"
466
+ WHERE "name" = 'admin'
467
+ )
468
+ AND "role_id" IN (
469
+ SELECT "_id" FROM "roles"
470
+ WHERE "name" = 'admin'
471
+ )
475
472
  `);
476
- await client.query(`
477
- DELETE FROM "features"
478
- WHERE "name" = 'admin'
473
+ await client.query(`
474
+ DELETE FROM "features"
475
+ WHERE "name" = 'admin'
479
476
  `);
480
- await client.query(`
481
- DELETE FROM "user_roles"
482
- WHERE "role_id" IN (
483
- SELECT "_id" FROM "roles"
484
- WHERE "name" = 'admin'
485
- )
477
+ await client.query(`
478
+ DELETE FROM "user_roles"
479
+ WHERE "role_id" IN (
480
+ SELECT "_id" FROM "roles"
481
+ WHERE "name" = 'admin'
482
+ )
486
483
  `);
487
- await client.query(`
488
- DELETE FROM "roles"
489
- WHERE "name" = 'admin'
484
+ await client.query(`
485
+ DELETE FROM "roles"
486
+ WHERE "name" = 'admin'
490
487
  `);
491
488
  }
492
489
  await client.query('COMMIT');