@loomcore/api 0.2.3 → 0.2.5

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 (35) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +77 -77
  3. package/dist/__tests__/common-test.utils.d.ts +12 -12
  4. package/dist/__tests__/common-test.utils.js +96 -90
  5. package/dist/__tests__/postgres.test-database.d.ts +2 -2
  6. package/dist/__tests__/postgres.test-database.js +16 -15
  7. package/dist/config/base-api-config.d.ts +2 -2
  8. package/dist/config/base-api-config.js +17 -10
  9. package/dist/databases/mongo-db/migrations/mongo-initial-schema.d.ts +2 -2
  10. package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +185 -128
  11. package/dist/databases/postgres/migrations/postgres-initial-schema.d.ts +2 -2
  12. package/dist/databases/postgres/migrations/postgres-initial-schema.js +75 -64
  13. package/dist/services/auth.service.d.ts +7 -7
  14. package/dist/services/auth.service.js +71 -58
  15. package/dist/services/email.service.js +5 -5
  16. package/dist/services/jwt.service.js +2 -2
  17. package/dist/services/multi-tenant-api.service.d.ts +4 -4
  18. package/dist/services/multi-tenant-api.service.js +8 -8
  19. package/dist/services/organization.service.d.ts +4 -4
  20. package/dist/services/organization.service.js +12 -8
  21. package/dist/services/password-reset-token.service.d.ts +4 -4
  22. package/dist/services/password-reset-token.service.js +13 -9
  23. package/dist/services/person.service.d.ts +4 -4
  24. package/dist/services/person.service.js +3 -3
  25. package/dist/services/tenant-query-decorator.d.ts +1 -1
  26. package/dist/services/tenant-query-decorator.js +17 -11
  27. package/dist/services/user.service.d.ts +6 -6
  28. package/dist/services/user.service.js +7 -7
  29. package/dist/services/utils/audit-for-create.util.d.ts +1 -1
  30. package/dist/services/utils/audit-for-update.util.d.ts +1 -1
  31. package/dist/services/utils/audit-for-update.util.js +0 -1
  32. package/dist/services/utils/getUserContextAuthorizations.util.d.ts +2 -2
  33. package/dist/services/utils/strip-sender-provided-system-properties.util.d.ts +1 -1
  34. package/dist/services/utils/strip-sender-provided-system-properties.util.js +5 -3
  35. package/package.json +1 -1
@@ -1,33 +1,33 @@
1
- import { initializeSystemUserContext, EmptyUserContext, getSystemUserContext, isSystemUserContextInitialized } from '@loomcore/common/models';
2
- import { PostgresDatabase } from '../postgres.database.js';
3
- import { OrganizationService } from '../../../services/index.js';
4
- import { passwordUtils } from '../../../utils/index.js';
1
+ import { EmptyUserContext, getSystemUserContext, initializeSystemUserContext, isSystemUserContextInitialized, } from "@loomcore/common/models";
2
+ import { OrganizationService } from "../../../services/index.js";
3
+ import { passwordUtils } from "../../../utils/index.js";
4
+ import { PostgresDatabase } from "../postgres.database.js";
5
5
  export const getPostgresInitialSchema = (dbConfig) => {
6
6
  const migrations = [];
7
7
  const isMultiTenant = dbConfig.app.isMultiTenant;
8
8
  if (isMultiTenant && !dbConfig.multiTenant) {
9
- throw new Error('Multi-tenant configuration is enabled but multi-tenant configuration is not provided');
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
12
  const dbName = dbConfig.database.name.replace(/"/g, '""');
13
13
  migrations.push({
14
- name: '00000000000001_system-configurations',
14
+ name: "00000000000001_system-configurations",
15
15
  up: async ({ context: pool }) => {
16
- if (dbConfig.env === 'test') {
16
+ if (dbConfig.env === "test") {
17
17
  return;
18
18
  }
19
19
  await pool.query(`ALTER DATABASE "${dbName}" SET statement_timeout = '60s'`);
20
20
  },
21
21
  down: async ({ context: pool }) => {
22
- if (dbConfig.env === 'test') {
22
+ if (dbConfig.env === "test") {
23
23
  return;
24
24
  }
25
25
  await pool.query(`ALTER DATABASE "${dbName}" RESET statement_timeout`);
26
- }
26
+ },
27
27
  });
28
28
  if (isMultiTenant) {
29
29
  migrations.push({
30
- name: '00000000000002_schema-organizations',
30
+ name: "00000000000002_schema-organizations",
31
31
  up: async ({ context: pool }) => {
32
32
  await pool.query(`
33
33
  CREATE TABLE IF NOT EXISTS "organizations" (
@@ -49,14 +49,14 @@ export const getPostgresInitialSchema = (dbConfig) => {
49
49
  },
50
50
  down: async ({ context: pool }) => {
51
51
  await pool.query('DROP TABLE IF EXISTS "organizations"');
52
- }
52
+ },
53
53
  });
54
54
  }
55
55
  if (isAuthEnabled)
56
56
  migrations.push({
57
- name: '00000000000003_schema-persons',
57
+ name: "00000000000003_schema-persons",
58
58
  up: async ({ context: pool }) => {
59
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
59
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
60
60
  const personsUniqueConstraints = isMultiTenant
61
61
  ? `CONSTRAINT "uk_persons_org_external_id" UNIQUE ("_orgId", "external_id"),
62
62
  CONSTRAINT "uk_persons_org_ssn" UNIQUE ("_orgId", "ssn")`
@@ -91,13 +91,13 @@ export const getPostgresInitialSchema = (dbConfig) => {
91
91
  await pool.query(`DROP INDEX IF EXISTS "idx_persons_external_id"`);
92
92
  await pool.query(`DROP INDEX IF EXISTS "idx_persons_ssn"`);
93
93
  await pool.query(`DROP TABLE IF EXISTS "persons"`);
94
- }
94
+ },
95
95
  });
96
96
  if (isAuthEnabled)
97
97
  migrations.push({
98
- name: '00000000000004_schema-users',
98
+ name: "00000000000004_schema-users",
99
99
  up: async ({ context: pool }) => {
100
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
100
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
101
101
  let uniqueConstraint = isMultiTenant
102
102
  ? 'CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")'
103
103
  : 'CONSTRAINT "uk_users_email" UNIQUE ("email")';
@@ -131,13 +131,13 @@ export const getPostgresInitialSchema = (dbConfig) => {
131
131
  await pool.query(`DROP INDEX IF EXISTS "idx_users_email"`);
132
132
  await pool.query(`DROP INDEX IF EXISTS "idx_users_person_id"`);
133
133
  await pool.query(`DROP TABLE IF EXISTS "users"`);
134
- }
134
+ },
135
135
  });
136
136
  if (isAuthEnabled)
137
137
  migrations.push({
138
- name: '00000000000005_schema-refresh-tokens',
138
+ name: "00000000000005_schema-refresh-tokens",
139
139
  up: async ({ context: pool }) => {
140
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
140
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
141
141
  await pool.query(`
142
142
  CREATE TABLE IF NOT EXISTS "refresh_tokens"(
143
143
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
@@ -154,13 +154,13 @@ export const getPostgresInitialSchema = (dbConfig) => {
154
154
  },
155
155
  down: async ({ context: pool }) => {
156
156
  await pool.query('DROP TABLE IF EXISTS "refresh_tokens"');
157
- }
157
+ },
158
158
  });
159
159
  if (isAuthEnabled)
160
160
  migrations.push({
161
- name: '00000000000006_schema-password-reset-tokens',
161
+ name: "00000000000006_schema-password-reset-tokens",
162
162
  up: async ({ context: pool }) => {
163
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
163
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
164
164
  const uniqueConstraint = isMultiTenant
165
165
  ? 'CONSTRAINT "uk_passwordResetTokens_email" UNIQUE ("_orgId", "email")'
166
166
  : 'CONSTRAINT "uk_passwordResetTokens_email" UNIQUE ("email")';
@@ -183,13 +183,13 @@ export const getPostgresInitialSchema = (dbConfig) => {
183
183
  },
184
184
  down: async ({ context: pool }) => {
185
185
  await pool.query('DROP TABLE IF EXISTS "password_reset_tokens"');
186
- }
186
+ },
187
187
  });
188
188
  if (isAuthEnabled)
189
189
  migrations.push({
190
- name: '00000000000007_schema-roles',
190
+ name: "00000000000007_schema-roles",
191
191
  up: async ({ context: pool }) => {
192
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
192
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
193
193
  const uniqueConstraint = isMultiTenant
194
194
  ? 'CONSTRAINT "uk_roles_name" UNIQUE ("_orgId", "name")'
195
195
  : 'CONSTRAINT "uk_roles_name" UNIQUE ("name")';
@@ -205,13 +205,13 @@ export const getPostgresInitialSchema = (dbConfig) => {
205
205
  },
206
206
  down: async ({ context: pool }) => {
207
207
  await pool.query('DROP TABLE IF EXISTS "roles"');
208
- }
208
+ },
209
209
  });
210
210
  if (isAuthEnabled)
211
211
  migrations.push({
212
- name: '00000000000008_schema-user-roles',
212
+ name: "00000000000008_schema-user-roles",
213
213
  up: async ({ context: pool }) => {
214
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
214
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
215
215
  const uniqueConstraint = isMultiTenant
216
216
  ? 'CONSTRAINT "uk_user_roles" UNIQUE ("_orgId", "user_id", "role_id")'
217
217
  : 'CONSTRAINT "uk_user_roles" UNIQUE ("user_id", "role_id")';
@@ -235,13 +235,13 @@ export const getPostgresInitialSchema = (dbConfig) => {
235
235
  },
236
236
  down: async ({ context: pool }) => {
237
237
  await pool.query('DROP TABLE IF EXISTS "user_roles"');
238
- }
238
+ },
239
239
  });
240
240
  if (isAuthEnabled)
241
241
  migrations.push({
242
- name: '00000000000009_schema-features',
242
+ name: "00000000000009_schema-features",
243
243
  up: async ({ context: pool }) => {
244
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
244
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
245
245
  const uniqueConstraint = isMultiTenant
246
246
  ? 'CONSTRAINT "uk_features" UNIQUE ("_orgId", "name")'
247
247
  : 'CONSTRAINT "uk_features" UNIQUE ("name")';
@@ -257,13 +257,13 @@ export const getPostgresInitialSchema = (dbConfig) => {
257
257
  },
258
258
  down: async ({ context: pool }) => {
259
259
  await pool.query('DROP TABLE IF EXISTS "features"');
260
- }
260
+ },
261
261
  });
262
262
  if (isAuthEnabled)
263
263
  migrations.push({
264
- name: '00000000000010_schema-authorizations',
264
+ name: "00000000000010_schema-authorizations",
265
265
  up: async ({ context: pool }) => {
266
- const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
266
+ const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER NOT NULL,' : "";
267
267
  const uniqueConstraint = isMultiTenant
268
268
  ? 'CONSTRAINT "uk_authorizations" UNIQUE ("_orgId", "role_id", "feature_id")'
269
269
  : 'CONSTRAINT "uk_authorizations" UNIQUE ("role_id", "feature_id")';
@@ -290,41 +290,46 @@ export const getPostgresInitialSchema = (dbConfig) => {
290
290
  },
291
291
  down: async ({ context: pool }) => {
292
292
  await pool.query('DROP TABLE IF EXISTS "authorizations"');
293
- }
293
+ },
294
294
  });
295
295
  if (isMultiTenant) {
296
296
  migrations.push({
297
- name: '00000000000011_data-meta-org',
297
+ name: "00000000000011_data-meta-org",
298
298
  up: async ({ context: pool }) => {
299
299
  const result = await pool.query(`
300
300
  INSERT INTO "organizations"("name", "code", "status", "is_meta_org", "_created", "_createdBy")
301
301
  VALUES($1, $2, 1, true, NOW(), 0)
302
302
  RETURNING "_id", "name", "code", "status", "is_meta_org", "_created", "_createdBy"
303
- `, [dbConfig.multiTenant?.metaOrgName, dbConfig.multiTenant?.metaOrgCode]);
303
+ `, [
304
+ dbConfig.multiTenant?.metaOrgName,
305
+ dbConfig.multiTenant?.metaOrgCode,
306
+ ]);
304
307
  if (result.rowCount === 0) {
305
- throw new Error('Failed to create meta organization');
308
+ throw new Error("Failed to create meta organization");
306
309
  }
307
- initializeSystemUserContext(dbConfig.email?.systemEmailAddress || 'system@example.com', result.rows[0]);
310
+ initializeSystemUserContext(dbConfig.email?.systemEmailAddress || "system@example.com", result.rows[0]);
308
311
  },
309
312
  down: async ({ context: pool }) => {
310
313
  await pool.query(`DELETE FROM "organizations" WHERE "is_meta_org" = TRUE`);
311
- }
314
+ },
312
315
  });
313
316
  }
314
317
  if (isAuthEnabled && dbConfig.adminUser) {
315
318
  migrations.push({
316
- name: '00000000000012_data-admin-user',
319
+ name: "00000000000012_data-admin-user",
317
320
  up: async ({ context: pool }) => {
318
321
  if (!isSystemUserContextInitialized()) {
319
322
  const errorMessage = isMultiTenant
320
- ? 'SystemUserContext has not been initialized. The meta-org migration (00000000000011_data-meta-org) should have run before this migration. ' +
321
- 'Please ensure metaOrgName and metaOrgCode are provided in your dbConfig.'
322
- : 'BUG: SystemUserContext has not been initialized. For non-multi-tenant setups, SystemUserContext should be initialized before migrations run.';
323
- console.error('❌ Migration Error:', errorMessage);
323
+ ? "SystemUserContext has not been initialized. The meta-org migration (00000000000011_data-meta-org) should have run before this migration. " +
324
+ "Please ensure metaOrgName and metaOrgCode are provided in your dbConfig."
325
+ : "BUG: SystemUserContext has not been initialized. For non-multi-tenant setups, SystemUserContext should be initialized before migrations run.";
326
+ console.error("❌ Migration Error:", errorMessage);
324
327
  throw new Error(errorMessage);
325
328
  }
326
329
  const systemUserContext = getSystemUserContext();
327
- const orgId = isMultiTenant ? systemUserContext.organization?._id : undefined;
330
+ const orgId = isMultiTenant
331
+ ? systemUserContext.organization?._id
332
+ : undefined;
328
333
  const hashedPassword = await passwordUtils.hashPassword(dbConfig.adminUser.password);
329
334
  const email = dbConfig.adminUser.email.toLowerCase();
330
335
  const client = await pool.connect();
@@ -353,30 +358,34 @@ export const getPostgresInitialSchema = (dbConfig) => {
353
358
  down: async ({ context: pool }) => {
354
359
  if (!dbConfig.adminUser?.email)
355
360
  return;
356
- await pool.query(`DELETE FROM "users" WHERE "email" = $1`, [dbConfig.adminUser.email.toLowerCase()]);
357
- }
361
+ await pool.query(`DELETE FROM "users" WHERE "email" = $1`, [
362
+ dbConfig.adminUser.email.toLowerCase(),
363
+ ]);
364
+ },
358
365
  });
359
366
  }
360
367
  if (isAuthEnabled && dbConfig.adminUser) {
361
368
  migrations.push({
362
- name: '00000000000013_data-admin-authorizations',
369
+ name: "00000000000013_data-admin-authorizations",
363
370
  up: async ({ context: pool }) => {
364
371
  const client = await pool.connect();
365
372
  try {
366
373
  const database = new PostgresDatabase(client);
367
374
  const organizationService = new OrganizationService(database);
368
- const metaOrg = isMultiTenant ? await organizationService.getMetaOrg(EmptyUserContext) : undefined;
375
+ const metaOrg = isMultiTenant
376
+ ? await organizationService.getMetaOrg(EmptyUserContext)
377
+ : undefined;
369
378
  if (isMultiTenant && !metaOrg) {
370
- throw new Error('Meta organization not found. Ensure meta-org migration ran successfully.');
379
+ throw new Error("Meta organization not found. Ensure meta-org migration ran successfully.");
371
380
  }
372
381
  const email = dbConfig.adminUser.email.toLowerCase();
373
382
  const userResult = await client.query(`SELECT "_id" FROM "users" WHERE "email" = $1`, [email]);
374
383
  const adminUserRow = userResult.rows[0];
375
384
  if (!adminUserRow) {
376
- throw new Error('Admin user not found. Ensure admin-user migration ran successfully.');
385
+ throw new Error("Admin user not found. Ensure admin-user migration ran successfully.");
377
386
  }
378
387
  const adminUserId = adminUserRow._id;
379
- await client.query('BEGIN');
388
+ await client.query("BEGIN");
380
389
  try {
381
390
  const roleResult = isMultiTenant
382
391
  ? await client.query(`
@@ -390,7 +399,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
390
399
  RETURNING "_id"
391
400
  `);
392
401
  if (roleResult.rowCount === 0) {
393
- throw new Error('Failed to create admin role');
402
+ throw new Error("Failed to create admin role");
394
403
  }
395
404
  const roleId = roleResult.rows[0]._id;
396
405
  const userRoleResult = isMultiTenant
@@ -403,7 +412,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
403
412
  VALUES($1, $2, NOW(), 0)
404
413
  `, [adminUserId, roleId]);
405
414
  if (userRoleResult.rowCount === 0) {
406
- throw new Error('Failed to create user role');
415
+ throw new Error("Failed to create user role");
407
416
  }
408
417
  const featureResult = isMultiTenant
409
418
  ? await client.query(`
@@ -417,7 +426,7 @@ export const getPostgresInitialSchema = (dbConfig) => {
417
426
  RETURNING "_id"
418
427
  `);
419
428
  if (featureResult.rowCount === 0) {
420
- throw new Error('Failed to create admin feature');
429
+ throw new Error("Failed to create admin feature");
421
430
  }
422
431
  const featureId = featureResult.rows[0]._id;
423
432
  const authorizationResult = isMultiTenant
@@ -436,12 +445,12 @@ export const getPostgresInitialSchema = (dbConfig) => {
436
445
  VALUES($1, $2, NOW(), 0)
437
446
  `, [roleId, featureId]);
438
447
  if (authorizationResult.rowCount === 0) {
439
- throw new Error('Failed to create admin authorization');
448
+ throw new Error("Failed to create admin authorization");
440
449
  }
441
- await client.query('COMMIT');
450
+ await client.query("COMMIT");
442
451
  }
443
452
  catch (error) {
444
- await client.query('ROLLBACK');
453
+ await client.query("ROLLBACK");
445
454
  throw error;
446
455
  }
447
456
  }
@@ -454,10 +463,12 @@ export const getPostgresInitialSchema = (dbConfig) => {
454
463
  try {
455
464
  const database = new PostgresDatabase(client);
456
465
  const organizationService = new OrganizationService(database);
457
- const metaOrg = isMultiTenant ? await organizationService.getMetaOrg(EmptyUserContext) : undefined;
466
+ const metaOrg = isMultiTenant
467
+ ? await organizationService.getMetaOrg(EmptyUserContext)
468
+ : undefined;
458
469
  if (isMultiTenant && !metaOrg)
459
470
  return;
460
- await client.query('BEGIN');
471
+ await client.query("BEGIN");
461
472
  try {
462
473
  if (isMultiTenant) {
463
474
  await client.query(`
@@ -517,17 +528,17 @@ export const getPostgresInitialSchema = (dbConfig) => {
517
528
  WHERE "name" = 'admin'
518
529
  `);
519
530
  }
520
- await client.query('COMMIT');
531
+ await client.query("COMMIT");
521
532
  }
522
533
  catch (error) {
523
- await client.query('ROLLBACK');
534
+ await client.query("ROLLBACK");
524
535
  throw error;
525
536
  }
526
537
  }
527
538
  finally {
528
539
  client.release();
529
540
  }
530
- }
541
+ },
531
542
  });
532
543
  }
533
544
  return migrations;
@@ -1,10 +1,10 @@
1
- import { Request, Response } from 'express';
2
- import { IUserContext, ITokenResponse, ILoginResponse, IUser, IPersonModel } from '@loomcore/common/models';
3
- import type { AppIdType } from '@loomcore/common/types';
4
- import { MultiTenantApiService } from './multi-tenant-api.service.js';
5
- import { UpdateResult } from '../databases/models/update-result.js';
6
- import { IRefreshToken } from '../models/refresh-token.model.js';
7
- import { IDatabase } from '../databases/models/index.js';
1
+ import { type ILoginResponse, type IPersonModel, type ITokenResponse, type IUser, type IUserContext } from "@loomcore/common/models";
2
+ import type { AppIdType } from "@loomcore/common/types";
3
+ import type { Request, Response } from "express";
4
+ import type { IDatabase } from "../databases/models/index.js";
5
+ import type { UpdateResult } from "../databases/models/update-result.js";
6
+ import { type IRefreshToken } from "../models/refresh-token.model.js";
7
+ import { MultiTenantApiService } from "./multi-tenant-api.service.js";
8
8
  export declare class AuthService extends MultiTenantApiService<IUser> {
9
9
  private refreshTokenService;
10
10
  private passwordResetTokenService;