@loomcore/api 0.1.60 → 0.1.63

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