@loomcore/api 0.1.59 → 0.1.62

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 (37) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +49 -49
  3. package/dist/__tests__/common-test.utils.js +10 -8
  4. package/dist/__tests__/postgres-test-migrations/postgres-test-schema.js +52 -52
  5. package/dist/__tests__/postgres.test-database.js +8 -8
  6. package/dist/__tests__/test-email-client.d.ts +4 -0
  7. package/dist/__tests__/test-email-client.js +6 -0
  8. package/dist/controllers/auth.controller.d.ts +2 -1
  9. package/dist/controllers/auth.controller.js +2 -3
  10. package/dist/databases/migrations/migration-runner.d.ts +3 -1
  11. package/dist/databases/migrations/migration-runner.js +27 -25
  12. package/dist/databases/mongo-db/migrations/mongo-initial-schema.d.ts +3 -2
  13. package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +127 -125
  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 +2 -1
  21. package/dist/databases/postgres/migrations/postgres-initial-schema.d.ts +2 -1
  22. package/dist/databases/postgres/migrations/postgres-initial-schema.js +284 -281
  23. package/dist/databases/postgres/postgres.database.js +17 -17
  24. package/dist/databases/postgres/utils/build-select-clause.js +6 -6
  25. package/dist/databases/postgres/utils/does-table-exist.util.js +4 -4
  26. package/dist/models/base-api-config.interface.d.ts +7 -10
  27. package/dist/models/email-client.interface.d.ts +3 -0
  28. package/dist/models/email-client.interface.js +1 -0
  29. package/dist/models/email-config.interface.d.ts +4 -0
  30. package/dist/models/email-config.interface.js +1 -0
  31. package/dist/models/multi-tenant-config.interface.d.ts +4 -0
  32. package/dist/models/multi-tenant-config.interface.js +1 -0
  33. package/dist/services/auth.service.d.ts +2 -1
  34. package/dist/services/auth.service.js +2 -2
  35. package/dist/services/email.service.d.ts +5 -4
  36. package/dist/services/email.service.js +12 -20
  37. package/package.json +88 -88
@@ -1,37 +1,39 @@
1
1
  import { initializeSystemUserContext, EmptyUserContext, getSystemUserContext, isSystemUserContextInitialized } from '@loomcore/common/models';
2
2
  import { PostgresDatabase } from '../postgres.database.js';
3
3
  import { AuthService, OrganizationService } from '../../../services/index.js';
4
- export const getPostgresInitialSchema = (config) => {
4
+ export const getPostgresInitialSchema = (config, emailClient) => {
5
5
  const migrations = [];
6
- const isMultiTenant = config.app.isMultiTenant === true;
7
- console.log('📋 Migration Config Diagnostic:');
8
- console.log(' isMultiTenant:', isMultiTenant);
9
- console.log(' config.app.metaOrgName:', config.app.metaOrgName ?? '(undefined)');
10
- console.log(' config.app.metaOrgCode:', config.app.metaOrgCode ?? '(undefined)');
11
- console.log(' config.auth?.adminUser?.email:', config.auth?.adminUser?.email ?? '(undefined)');
12
- console.log(' config.auth?.adminUser?.password:', config.auth?.adminUser?.password ? '(set)' : '(undefined)');
13
- console.log(' Will add meta-org migration:', isMultiTenant && !!config.app.metaOrgName && !!config.app.metaOrgCode);
14
- console.log(' Will add admin-user migration:', !!config.auth?.adminUser?.email && !!config.auth?.adminUser?.password);
6
+ const isMultiTenant = config.app.isMultiTenant;
7
+ if (isMultiTenant && !config.multiTenant) {
8
+ throw new Error('Multi-tenant configuration is enabled but multi-tenant configuration is not provided');
9
+ }
10
+ const isAuthEnabled = config.app.isAuthEnabled;
11
+ if (isAuthEnabled && !config.auth) {
12
+ throw new Error('Auth enabled without auth configuration');
13
+ }
14
+ if (isAuthEnabled && (!emailClient || !config.email)) {
15
+ throw new Error('Auth enabled without email client or email configuration');
16
+ }
15
17
  if (isMultiTenant) {
16
18
  migrations.push({
17
19
  name: '00000000000001_schema-organizations',
18
20
  up: async ({ context: pool }) => {
19
- await pool.query(`
20
- CREATE TABLE IF NOT EXISTS "organizations" (
21
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
22
- "name" VARCHAR(255) NOT NULL UNIQUE,
23
- "code" VARCHAR(255) NOT NULL UNIQUE,
24
- "description" TEXT,
25
- "status" INTEGER NOT NULL,
26
- "isMetaOrg" BOOLEAN NOT NULL,
27
- "authToken" TEXT,
28
- "_created" TIMESTAMPTZ NOT NULL,
29
- "_createdBy" INTEGER NOT NULL,
30
- "_updated" TIMESTAMPTZ NOT NULL,
31
- "_updatedBy" INTEGER NOT NULL,
32
- "_deleted" TIMESTAMPTZ,
33
- "_deletedBy" INTEGER
34
- )
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
+ "isMetaOrg" BOOLEAN NOT NULL,
29
+ "authToken" 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
+ )
35
37
  `);
36
38
  },
37
39
  down: async ({ context: pool }) => {
@@ -39,172 +41,178 @@ export const getPostgresInitialSchema = (config) => {
39
41
  }
40
42
  });
41
43
  }
42
- migrations.push({
43
- name: '00000000000002_schema-users',
44
- up: async ({ context: pool }) => {
45
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
46
- const uniqueConstraint = isMultiTenant
47
- ? 'CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")'
48
- : 'CONSTRAINT "uk_users_email" UNIQUE ("email")';
49
- await pool.query(`
50
- CREATE TABLE IF NOT EXISTS "users" (
51
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
52
- ${orgColumnDef}
53
- "email" VARCHAR(255) NOT NULL,
54
- "firstName" VARCHAR(255),
55
- "lastName" VARCHAR(255),
56
- "displayName" VARCHAR(255),
57
- "password" VARCHAR(255) NOT NULL,
58
- "_lastLoggedIn" TIMESTAMPTZ,
59
- "_lastPasswordChange" TIMESTAMPTZ,
60
- "_created" TIMESTAMPTZ NOT NULL,
61
- "_createdBy" INTEGER NOT NULL,
62
- "_updated" TIMESTAMPTZ NOT NULL,
63
- "_updatedBy" INTEGER NOT NULL,
64
- "_deleted" TIMESTAMPTZ,
65
- "_deletedBy" INTEGER,
66
- ${uniqueConstraint}
67
- )
44
+ if (isAuthEnabled)
45
+ migrations.push({
46
+ name: '00000000000002_schema-users',
47
+ up: async ({ context: pool }) => {
48
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
49
+ const uniqueConstraint = isMultiTenant
50
+ ? 'CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")'
51
+ : 'CONSTRAINT "uk_users_email" UNIQUE ("email")';
52
+ await pool.query(`
53
+ CREATE TABLE IF NOT EXISTS "users" (
54
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
55
+ ${orgColumnDef}
56
+ "email" VARCHAR(255) NOT NULL,
57
+ "firstName" VARCHAR(255),
58
+ "lastName" VARCHAR(255),
59
+ "displayName" VARCHAR(255),
60
+ "password" VARCHAR(255) NOT NULL,
61
+ "_lastLoggedIn" TIMESTAMPTZ,
62
+ "_lastPasswordChange" TIMESTAMPTZ,
63
+ "_created" TIMESTAMPTZ NOT NULL,
64
+ "_createdBy" INTEGER NOT NULL,
65
+ "_updated" TIMESTAMPTZ NOT NULL,
66
+ "_updatedBy" INTEGER NOT NULL,
67
+ "_deleted" TIMESTAMPTZ,
68
+ "_deletedBy" INTEGER,
69
+ ${uniqueConstraint}
70
+ )
68
71
  `);
69
- },
70
- down: async ({ context: pool }) => {
71
- await pool.query('DROP TABLE IF EXISTS "users"');
72
- }
73
- });
74
- migrations.push({
75
- name: '00000000000003_schema-refresh-tokens',
76
- up: async ({ context: pool }) => {
77
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
78
- await pool.query(`
79
- CREATE TABLE IF NOT EXISTS "refreshTokens" (
80
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
81
- ${orgColumnDef}
82
- "token" VARCHAR(255) NOT NULL,
83
- "deviceId" VARCHAR(255) NOT NULL,
84
- "userId" INTEGER NOT NULL,
85
- "expiresOn" BIGINT NOT NULL,
86
- "created" TIMESTAMPTZ NOT NULL,
87
- "createdBy" INTEGER NOT NULL,
88
- CONSTRAINT "fk_refreshTokens_user" FOREIGN KEY ("userId") REFERENCES "users"("_id") ON DELETE CASCADE
89
- )
72
+ },
73
+ down: async ({ context: pool }) => {
74
+ await pool.query('DROP TABLE IF EXISTS "users"');
75
+ }
76
+ });
77
+ if (isAuthEnabled)
78
+ migrations.push({
79
+ name: '00000000000003_schema-refresh-tokens',
80
+ up: async ({ context: pool }) => {
81
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
82
+ await pool.query(`
83
+ CREATE TABLE IF NOT EXISTS "refreshTokens" (
84
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
85
+ ${orgColumnDef}
86
+ "token" VARCHAR(255) NOT NULL,
87
+ "deviceId" VARCHAR(255) NOT NULL,
88
+ "userId" INTEGER NOT NULL,
89
+ "expiresOn" BIGINT NOT NULL,
90
+ "created" TIMESTAMPTZ NOT NULL,
91
+ "createdBy" INTEGER NOT NULL,
92
+ CONSTRAINT "fk_refreshTokens_user" FOREIGN KEY ("userId") REFERENCES "users"("_id") ON DELETE CASCADE
93
+ )
90
94
  `);
91
- },
92
- down: async ({ context: pool }) => {
93
- await pool.query('DROP TABLE IF EXISTS "refreshTokens"');
94
- }
95
- });
96
- migrations.push({
97
- name: '00000000000004_schema-roles',
98
- up: async ({ context: pool }) => {
99
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
100
- const uniqueConstraint = isMultiTenant
101
- ? 'CONSTRAINT "uk_roles_name" UNIQUE ("_orgId", "name")'
102
- : 'CONSTRAINT "uk_roles_name" UNIQUE ("name")';
103
- await pool.query(`
104
- CREATE TABLE IF NOT EXISTS "roles" (
105
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
106
- ${orgColumnDef}
107
- "name" VARCHAR(255) NOT NULL,
108
- "description" TEXT,
109
- ${uniqueConstraint}
110
- )
95
+ },
96
+ down: async ({ context: pool }) => {
97
+ await pool.query('DROP TABLE IF EXISTS "refreshTokens"');
98
+ }
99
+ });
100
+ if (isAuthEnabled)
101
+ migrations.push({
102
+ name: '00000000000004_schema-roles',
103
+ up: async ({ context: pool }) => {
104
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
105
+ const uniqueConstraint = isMultiTenant
106
+ ? 'CONSTRAINT "uk_roles_name" UNIQUE ("_orgId", "name")'
107
+ : 'CONSTRAINT "uk_roles_name" UNIQUE ("name")';
108
+ await pool.query(`
109
+ CREATE TABLE IF NOT EXISTS "roles" (
110
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
111
+ ${orgColumnDef}
112
+ "name" VARCHAR(255) NOT NULL,
113
+ "description" TEXT,
114
+ ${uniqueConstraint}
115
+ )
111
116
  `);
112
- },
113
- down: async ({ context: pool }) => {
114
- await pool.query('DROP TABLE IF EXISTS "roles"');
115
- }
116
- });
117
- migrations.push({
118
- name: '00000000000005_schema-user-roles',
119
- up: async ({ context: pool }) => {
120
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
121
- const uniqueConstraint = isMultiTenant
122
- ? 'CONSTRAINT "uk_user_roles" UNIQUE ("_orgId", "userId", "roleId")'
123
- : 'CONSTRAINT "uk_user_roles" UNIQUE ("userId", "roleId")';
124
- await pool.query(`
125
- CREATE TABLE IF NOT EXISTS "user_roles" (
126
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
127
- ${orgColumnDef}
128
- "userId" INTEGER NOT NULL,
129
- "roleId" INTEGER NOT NULL,
130
- "_created" TIMESTAMPTZ NOT NULL,
131
- "_createdBy" INTEGER NOT NULL,
132
- "_updated" TIMESTAMPTZ NOT NULL,
133
- "_updatedBy" INTEGER NOT NULL,
134
- "_deleted" TIMESTAMPTZ,
135
- "_deletedBy" INTEGER,
136
- CONSTRAINT "fk_user_roles_user" FOREIGN KEY ("userId") REFERENCES "users"("_id") ON DELETE CASCADE,
137
- CONSTRAINT "fk_user_roles_role" FOREIGN KEY ("roleId") REFERENCES "roles"("_id") ON DELETE CASCADE,
138
- ${uniqueConstraint}
139
- )
117
+ },
118
+ down: async ({ context: pool }) => {
119
+ await pool.query('DROP TABLE IF EXISTS "roles"');
120
+ }
121
+ });
122
+ if (isAuthEnabled)
123
+ migrations.push({
124
+ name: '00000000000005_schema-user-roles',
125
+ up: async ({ context: pool }) => {
126
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
127
+ const uniqueConstraint = isMultiTenant
128
+ ? 'CONSTRAINT "uk_user_roles" UNIQUE ("_orgId", "userId", "roleId")'
129
+ : 'CONSTRAINT "uk_user_roles" UNIQUE ("userId", "roleId")';
130
+ await pool.query(`
131
+ CREATE TABLE IF NOT EXISTS "user_roles" (
132
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
133
+ ${orgColumnDef}
134
+ "userId" INTEGER NOT NULL,
135
+ "roleId" INTEGER NOT NULL,
136
+ "_created" TIMESTAMPTZ NOT NULL,
137
+ "_createdBy" INTEGER NOT NULL,
138
+ "_updated" TIMESTAMPTZ NOT NULL,
139
+ "_updatedBy" INTEGER NOT NULL,
140
+ "_deleted" TIMESTAMPTZ,
141
+ "_deletedBy" INTEGER,
142
+ CONSTRAINT "fk_user_roles_user" FOREIGN KEY ("userId") REFERENCES "users"("_id") ON DELETE CASCADE,
143
+ CONSTRAINT "fk_user_roles_role" FOREIGN KEY ("roleId") REFERENCES "roles"("_id") ON DELETE CASCADE,
144
+ ${uniqueConstraint}
145
+ )
140
146
  `);
141
- },
142
- down: async ({ context: pool }) => {
143
- await pool.query('DROP TABLE IF EXISTS "user_roles"');
144
- }
145
- });
146
- migrations.push({
147
- name: '00000000000006_schema-features',
148
- up: async ({ context: pool }) => {
149
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
150
- const uniqueConstraint = isMultiTenant
151
- ? 'CONSTRAINT "uk_features" UNIQUE ("_orgId", "name")'
152
- : 'CONSTRAINT "uk_features" UNIQUE ("name")';
153
- await pool.query(`
154
- CREATE TABLE IF NOT EXISTS "features" (
155
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
156
- ${orgColumnDef}
157
- "name" VARCHAR(255) NOT NULL,
158
- "description" TEXT,
159
- ${uniqueConstraint}
160
- )
147
+ },
148
+ down: async ({ context: pool }) => {
149
+ await pool.query('DROP TABLE IF EXISTS "user_roles"');
150
+ }
151
+ });
152
+ if (isAuthEnabled)
153
+ migrations.push({
154
+ name: '00000000000006_schema-features',
155
+ up: async ({ context: pool }) => {
156
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
157
+ const uniqueConstraint = isMultiTenant
158
+ ? 'CONSTRAINT "uk_features" UNIQUE ("_orgId", "name")'
159
+ : 'CONSTRAINT "uk_features" UNIQUE ("name")';
160
+ await pool.query(`
161
+ CREATE TABLE IF NOT EXISTS "features" (
162
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
163
+ ${orgColumnDef}
164
+ "name" VARCHAR(255) NOT NULL,
165
+ "description" TEXT,
166
+ ${uniqueConstraint}
167
+ )
161
168
  `);
162
- },
163
- down: async ({ context: pool }) => {
164
- await pool.query('DROP TABLE IF EXISTS "features"');
165
- }
166
- });
167
- migrations.push({
168
- name: '00000000000007_schema-authorizations',
169
- up: async ({ context: pool }) => {
170
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
171
- const uniqueConstraint = isMultiTenant
172
- ? 'CONSTRAINT "uk_authorizations" UNIQUE ("_orgId", "roleId", "featureId")'
173
- : 'CONSTRAINT "uk_authorizations" UNIQUE ("roleId", "featureId")';
174
- await pool.query(`
175
- CREATE TABLE IF NOT EXISTS "authorizations" (
176
- "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
177
- ${orgColumnDef}
178
- "roleId" INTEGER NOT NULL,
179
- "featureId" INTEGER NOT NULL,
180
- "startDate" TIMESTAMPTZ,
181
- "endDate" TIMESTAMPTZ,
182
- "config" JSONB,
183
- "_created" TIMESTAMPTZ NOT NULL,
184
- "_createdBy" INTEGER NOT NULL,
185
- "_updated" TIMESTAMPTZ NOT NULL,
186
- "_updatedBy" INTEGER NOT NULL,
187
- "_deleted" TIMESTAMPTZ,
188
- "_deletedBy" INTEGER,
189
- CONSTRAINT "fk_authorizations_role" FOREIGN KEY ("roleId") REFERENCES "roles"("_id") ON DELETE CASCADE,
190
- CONSTRAINT "fk_authorizations_feature" FOREIGN KEY ("featureId") REFERENCES "features"("_id") ON DELETE CASCADE,
191
- ${uniqueConstraint}
192
- )
169
+ },
170
+ down: async ({ context: pool }) => {
171
+ await pool.query('DROP TABLE IF EXISTS "features"');
172
+ }
173
+ });
174
+ if (isAuthEnabled)
175
+ migrations.push({
176
+ name: '00000000000007_schema-authorizations',
177
+ up: async ({ context: pool }) => {
178
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
179
+ const uniqueConstraint = isMultiTenant
180
+ ? 'CONSTRAINT "uk_authorizations" UNIQUE ("_orgId", "roleId", "featureId")'
181
+ : 'CONSTRAINT "uk_authorizations" UNIQUE ("roleId", "featureId")';
182
+ await pool.query(`
183
+ CREATE TABLE IF NOT EXISTS "authorizations" (
184
+ "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
185
+ ${orgColumnDef}
186
+ "roleId" INTEGER NOT NULL,
187
+ "featureId" INTEGER NOT NULL,
188
+ "startDate" TIMESTAMPTZ,
189
+ "endDate" TIMESTAMPTZ,
190
+ "config" JSONB,
191
+ "_created" TIMESTAMPTZ NOT NULL,
192
+ "_createdBy" INTEGER NOT NULL,
193
+ "_updated" TIMESTAMPTZ NOT NULL,
194
+ "_updatedBy" INTEGER NOT NULL,
195
+ "_deleted" TIMESTAMPTZ,
196
+ "_deletedBy" INTEGER,
197
+ CONSTRAINT "fk_authorizations_role" FOREIGN KEY ("roleId") REFERENCES "roles"("_id") ON DELETE CASCADE,
198
+ CONSTRAINT "fk_authorizations_feature" FOREIGN KEY ("featureId") REFERENCES "features"("_id") ON DELETE CASCADE,
199
+ ${uniqueConstraint}
200
+ )
193
201
  `);
194
- },
195
- down: async ({ context: pool }) => {
196
- await pool.query('DROP TABLE IF EXISTS "authorizations"');
197
- }
198
- });
199
- if (isMultiTenant && config.app.metaOrgName && config.app.metaOrgCode) {
202
+ },
203
+ down: async ({ context: pool }) => {
204
+ await pool.query('DROP TABLE IF EXISTS "authorizations"');
205
+ }
206
+ });
207
+ if (isMultiTenant) {
200
208
  migrations.push({
201
209
  name: '00000000000008_data-meta-org',
202
210
  up: async ({ context: pool }) => {
203
- const result = await pool.query(`
204
- INSERT INTO "organizations" ("name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy")
205
- VALUES ($1, $2, 1, true, NOW(), 0, NOW(), 0)
206
- RETURNING "_id", "name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy"
207
- `, [config.app.metaOrgName, config.app.metaOrgCode]);
211
+ const result = await pool.query(`
212
+ INSERT INTO "organizations" ("name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy")
213
+ VALUES ($1, $2, 1, true, NOW(), 0, NOW(), 0)
214
+ RETURNING "_id", "name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy"
215
+ `, [config.multiTenant?.metaOrgName, config.multiTenant?.metaOrgCode]);
208
216
  if (result.rowCount === 0) {
209
217
  throw new Error('Failed to create meta organization');
210
218
  }
@@ -215,24 +223,22 @@ export const getPostgresInitialSchema = (config) => {
215
223
  }
216
224
  });
217
225
  }
218
- if (config.auth?.adminUser?.email && config.auth?.adminUser?.password) {
226
+ if (isAuthEnabled) {
219
227
  migrations.push({
220
228
  name: '00000000000009_data-admin-user',
221
229
  up: async ({ context: pool }) => {
222
- if (!config.auth?.adminUser?.email || !config.auth?.adminUser?.password) {
223
- throw new Error('Admin user email and password must be provided in config');
224
- }
225
230
  const client = await pool.connect();
226
231
  try {
227
232
  const database = new PostgresDatabase(client);
228
- const authService = new AuthService(database);
229
- if (isMultiTenant && !isSystemUserContextInitialized()) {
230
- throw new Error('SystemUserContext has not been initialized. The meta-org migration (00000000000008_data-meta-org) should have run before this migration. ' +
231
- 'This migration only runs if config.app.metaOrgName and config.app.metaOrgCode are provided. ' +
232
- 'Please ensure both values are set in your config.');
233
- }
234
- if (!isMultiTenant && !isSystemUserContextInitialized()) {
235
- initializeSystemUserContext(config.email?.systemEmailAddress || 'system@example.com', undefined);
233
+ const authService = new AuthService(database, emailClient);
234
+ if (!isSystemUserContextInitialized()) {
235
+ const errorMessage = isMultiTenant
236
+ ? 'SystemUserContext has not been initialized. The meta-org migration (00000000000008_data-meta-org) should have run before this migration. ' +
237
+ 'This migration only runs if config.app.metaOrgName and config.app.metaOrgCode are provided. ' +
238
+ 'Please ensure both values are set in your config.'
239
+ : 'BUG: SystemUserContext has not been initialized. For non-multi-tenant setups, SystemUserContext should be initialized before migrations run.';
240
+ console.error('❌ Migration Error:', errorMessage);
241
+ throw new Error(errorMessage);
236
242
  }
237
243
  const systemUserContext = getSystemUserContext();
238
244
  const userData = {
@@ -258,84 +264,81 @@ export const getPostgresInitialSchema = (config) => {
258
264
  }
259
265
  });
260
266
  }
261
- if (config.auth?.adminUser?.email) {
267
+ if (config.auth) {
262
268
  migrations.push({
263
269
  name: '00000000000010_data-admin-authorizations',
264
270
  up: async ({ context: pool }) => {
265
- if (!config.auth?.adminUser?.email) {
266
- throw new Error('Admin user email not found in config');
267
- }
268
271
  const client = await pool.connect();
269
272
  try {
270
273
  const database = new PostgresDatabase(client);
271
274
  const organizationService = new OrganizationService(database);
272
- const authService = new AuthService(database);
275
+ const authService = new AuthService(database, emailClient);
273
276
  const metaOrg = isMultiTenant ? await organizationService.getMetaOrg(EmptyUserContext) : undefined;
274
277
  if (isMultiTenant && !metaOrg) {
275
278
  throw new Error('Meta organization not found. Ensure meta-org migration ran successfully.');
276
279
  }
277
- const adminUser = await authService.getUserByEmail(config.auth?.adminUser?.email);
280
+ const adminUser = await authService.getUserByEmail(config.auth.adminUser.email);
278
281
  if (!adminUser) {
279
282
  throw new Error('Admin user not found. Ensure admin-user migration ran successfully.');
280
283
  }
281
284
  await client.query('BEGIN');
282
285
  try {
283
286
  const roleResult = isMultiTenant
284
- ? await client.query(`
285
- INSERT INTO "roles" ("_orgId", "name")
286
- VALUES ($1, 'admin')
287
- RETURNING "_id"
287
+ ? await client.query(`
288
+ INSERT INTO "roles" ("_orgId", "name")
289
+ VALUES ($1, 'admin')
290
+ RETURNING "_id"
288
291
  `, [metaOrg._id])
289
- : await client.query(`
290
- INSERT INTO "roles" ("name")
291
- VALUES ('admin')
292
- RETURNING "_id"
292
+ : await client.query(`
293
+ INSERT INTO "roles" ("name")
294
+ VALUES ('admin')
295
+ RETURNING "_id"
293
296
  `);
294
297
  if (roleResult.rowCount === 0) {
295
298
  throw new Error('Failed to create admin role');
296
299
  }
297
300
  const roleId = roleResult.rows[0]._id;
298
301
  const userRoleResult = isMultiTenant
299
- ? await client.query(`
300
- INSERT INTO "user_roles" ("_orgId", "userId", "roleId", "_created", "_createdBy", "_updated", "_updatedBy")
301
- VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
302
+ ? await client.query(`
303
+ INSERT INTO "user_roles" ("_orgId", "userId", "roleId", "_created", "_createdBy", "_updated", "_updatedBy")
304
+ VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
302
305
  `, [metaOrg._id, adminUser._id, roleId])
303
- : await client.query(`
304
- INSERT INTO "user_roles" ("userId", "roleId", "_created", "_createdBy", "_updated", "_updatedBy")
305
- VALUES ($1, $2, NOW(), 0, NOW(), 0)
306
+ : await client.query(`
307
+ INSERT INTO "user_roles" ("userId", "roleId", "_created", "_createdBy", "_updated", "_updatedBy")
308
+ VALUES ($1, $2, NOW(), 0, NOW(), 0)
306
309
  `, [adminUser._id, roleId]);
307
310
  if (userRoleResult.rowCount === 0) {
308
311
  throw new Error('Failed to create user role');
309
312
  }
310
313
  const featureResult = isMultiTenant
311
- ? await client.query(`
312
- INSERT INTO "features" ("_orgId", "name")
313
- VALUES ($1, 'admin')
314
- RETURNING "_id"
314
+ ? await client.query(`
315
+ INSERT INTO "features" ("_orgId", "name")
316
+ VALUES ($1, 'admin')
317
+ RETURNING "_id"
315
318
  `, [metaOrg._id])
316
- : await client.query(`
317
- INSERT INTO "features" ("name")
318
- VALUES ('admin')
319
- RETURNING "_id"
319
+ : await client.query(`
320
+ INSERT INTO "features" ("name")
321
+ VALUES ('admin')
322
+ RETURNING "_id"
320
323
  `);
321
324
  if (featureResult.rowCount === 0) {
322
325
  throw new Error('Failed to create admin feature');
323
326
  }
324
327
  const featureId = featureResult.rows[0]._id;
325
328
  const authorizationResult = isMultiTenant
326
- ? await client.query(`
327
- INSERT INTO "authorizations" (
328
- "_orgId", "roleId", "featureId",
329
- "_created", "_createdBy", "_updated", "_updatedBy"
330
- )
331
- VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
329
+ ? await client.query(`
330
+ INSERT INTO "authorizations" (
331
+ "_orgId", "roleId", "featureId",
332
+ "_created", "_createdBy", "_updated", "_updatedBy"
333
+ )
334
+ VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
332
335
  `, [metaOrg._id, roleId, featureId])
333
- : await client.query(`
334
- INSERT INTO "authorizations" (
335
- "roleId", "featureId",
336
- "_created", "_createdBy", "_updated", "_updatedBy"
337
- )
338
- VALUES ($1, $2, NOW(), 0, NOW(), 0)
336
+ : await client.query(`
337
+ INSERT INTO "authorizations" (
338
+ "roleId", "featureId",
339
+ "_created", "_createdBy", "_updated", "_updatedBy"
340
+ )
341
+ VALUES ($1, $2, NOW(), 0, NOW(), 0)
339
342
  `, [roleId, featureId]);
340
343
  if (authorizationResult.rowCount === 0) {
341
344
  throw new Error('Failed to create admin authorization');
@@ -362,61 +365,61 @@ export const getPostgresInitialSchema = (config) => {
362
365
  await client.query('BEGIN');
363
366
  try {
364
367
  if (isMultiTenant) {
365
- await client.query(`
366
- DELETE FROM "authorizations"
367
- WHERE "_orgId" = $1
368
- AND "featureId" IN (
369
- SELECT "_id" FROM "features"
370
- WHERE "_orgId" = $1 AND "name" = 'admin'
371
- )
372
- AND "roleId" IN (
373
- SELECT "_id" FROM "roles"
374
- WHERE "_orgId" = $1 AND "name" = 'admin'
375
- )
368
+ await client.query(`
369
+ DELETE FROM "authorizations"
370
+ WHERE "_orgId" = $1
371
+ AND "featureId" IN (
372
+ SELECT "_id" FROM "features"
373
+ WHERE "_orgId" = $1 AND "name" = 'admin'
374
+ )
375
+ AND "roleId" IN (
376
+ SELECT "_id" FROM "roles"
377
+ WHERE "_orgId" = $1 AND "name" = 'admin'
378
+ )
376
379
  `, [metaOrg._id]);
377
- await client.query(`
378
- DELETE FROM "features"
379
- WHERE "_orgId" = $1 AND "name" = 'admin'
380
+ await client.query(`
381
+ DELETE FROM "features"
382
+ WHERE "_orgId" = $1 AND "name" = 'admin'
380
383
  `, [metaOrg._id]);
381
- await client.query(`
382
- DELETE FROM "user_roles"
383
- WHERE "_orgId" = $1
384
- AND "roleId" IN (
385
- SELECT "_id" FROM "roles"
386
- WHERE "_orgId" = $1 AND "name" = 'admin'
387
- )
384
+ await client.query(`
385
+ DELETE FROM "user_roles"
386
+ WHERE "_orgId" = $1
387
+ AND "roleId" IN (
388
+ SELECT "_id" FROM "roles"
389
+ WHERE "_orgId" = $1 AND "name" = 'admin'
390
+ )
388
391
  `, [metaOrg._id]);
389
- await client.query(`
390
- DELETE FROM "roles"
391
- WHERE "_orgId" = $1 AND "name" = 'admin'
392
+ await client.query(`
393
+ DELETE FROM "roles"
394
+ WHERE "_orgId" = $1 AND "name" = 'admin'
392
395
  `, [metaOrg._id]);
393
396
  }
394
397
  else {
395
- await client.query(`
396
- DELETE FROM "authorizations"
397
- WHERE "featureId" IN (
398
- SELECT "_id" FROM "features"
399
- WHERE "name" = 'admin'
400
- )
401
- AND "roleId" IN (
402
- SELECT "_id" FROM "roles"
403
- WHERE "name" = 'admin'
404
- )
398
+ await client.query(`
399
+ DELETE FROM "authorizations"
400
+ WHERE "featureId" IN (
401
+ SELECT "_id" FROM "features"
402
+ WHERE "name" = 'admin'
403
+ )
404
+ AND "roleId" IN (
405
+ SELECT "_id" FROM "roles"
406
+ WHERE "name" = 'admin'
407
+ )
405
408
  `);
406
- await client.query(`
407
- DELETE FROM "features"
408
- WHERE "name" = 'admin'
409
+ await client.query(`
410
+ DELETE FROM "features"
411
+ WHERE "name" = 'admin'
409
412
  `);
410
- await client.query(`
411
- DELETE FROM "user_roles"
412
- WHERE "roleId" IN (
413
- SELECT "_id" FROM "roles"
414
- WHERE "name" = 'admin'
415
- )
413
+ await client.query(`
414
+ DELETE FROM "user_roles"
415
+ WHERE "roleId" IN (
416
+ SELECT "_id" FROM "roles"
417
+ WHERE "name" = 'admin'
418
+ )
416
419
  `);
417
- await client.query(`
418
- DELETE FROM "roles"
419
- WHERE "name" = 'admin'
420
+ await client.query(`
421
+ DELETE FROM "roles"
422
+ WHERE "name" = 'admin'
420
423
  `);
421
424
  }
422
425
  await client.query('COMMIT');