@loomcore/api 0.1.27 → 0.1.29

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 (44) hide show
  1. package/dist/__tests__/common-test.utils.d.ts +7 -2
  2. package/dist/__tests__/common-test.utils.js +65 -11
  3. package/dist/__tests__/postgres-test-migrations/run-test-migrations.js +0 -2
  4. package/dist/__tests__/postgres.test-database.js +7 -6
  5. package/dist/__tests__/test-express-app.js +2 -31
  6. package/dist/__tests__/test-objects.d.ts +1 -0
  7. package/dist/__tests__/test-objects.js +5 -1
  8. package/dist/config/base-api-config.js +1 -1
  9. package/dist/controllers/api.controller.d.ts +2 -3
  10. package/dist/controllers/api.controller.js +12 -12
  11. package/dist/controllers/auth.controller.js +2 -2
  12. package/dist/controllers/users.controller.d.ts +2 -1
  13. package/dist/controllers/users.controller.js +2 -2
  14. package/dist/databases/postgres/migrations/003-create-users-table.migration.js +5 -2
  15. package/dist/databases/postgres/migrations/004-create-refresh-tokens-table.migration.js +5 -1
  16. package/dist/databases/postgres/migrations/005-create-meta-org.migration.d.ts +1 -5
  17. package/dist/databases/postgres/migrations/005-create-meta-org.migration.js +7 -7
  18. package/dist/databases/postgres/migrations/006-create-admin-user.migration.d.ts +6 -3
  19. package/dist/databases/postgres/migrations/006-create-admin-user.migration.js +16 -13
  20. package/dist/databases/postgres/migrations/007-create-roles-table.migration.d.ts +1 -1
  21. package/dist/databases/postgres/migrations/007-create-roles-table.migration.js +6 -3
  22. package/dist/databases/postgres/migrations/008-create-user-roles-table.migration.d.ts +1 -1
  23. package/dist/databases/postgres/migrations/008-create-user-roles-table.migration.js +6 -2
  24. package/dist/databases/postgres/migrations/009-create-features-table.migration.d.ts +1 -1
  25. package/dist/databases/postgres/migrations/009-create-features-table.migration.js +6 -3
  26. package/dist/databases/postgres/migrations/010-create-authorizations-table.migration.d.ts +1 -1
  27. package/dist/databases/postgres/migrations/010-create-authorizations-table.migration.js +6 -3
  28. package/dist/databases/postgres/migrations/011-create-admin-authorization.migration.d.ts +3 -3
  29. package/dist/databases/postgres/migrations/011-create-admin-authorization.migration.js +22 -9
  30. package/dist/databases/postgres/migrations/database-builder.d.ts +15 -0
  31. package/dist/databases/postgres/migrations/database-builder.interface.d.ts +10 -0
  32. package/dist/databases/postgres/migrations/database-builder.interface.js +1 -0
  33. package/dist/databases/postgres/migrations/database-builder.js +62 -0
  34. package/dist/databases/postgres/migrations/setup-for-auth.migration.d.ts +5 -2
  35. package/dist/databases/postgres/migrations/setup-for-auth.migration.js +12 -18
  36. package/dist/databases/postgres/migrations/setup-for-multitenant.migration.d.ts +5 -3
  37. package/dist/databases/postgres/migrations/setup-for-multitenant.migration.js +7 -9
  38. package/dist/models/base-api-config.interface.d.ts +11 -5
  39. package/dist/services/email.service.js +6 -3
  40. package/dist/utils/api.utils.d.ts +1 -2
  41. package/dist/utils/api.utils.js +2 -3
  42. package/package.json +2 -2
  43. package/dist/__tests__/postgres-test-migrations/004-create-test-users-table.migration.d.ts +0 -21
  44. package/dist/__tests__/postgres-test-migrations/004-create-test-users-table.migration.js +0 -75
@@ -11,7 +11,10 @@ declare function initialize(database: IDatabase): void;
11
11
  declare function getRandomId(): string;
12
12
  declare function createMetaOrg(): Promise<void>;
13
13
  declare function deleteMetaOrg(): Promise<void>;
14
- declare function setupTestUser(): Promise<IUser>;
14
+ declare function setupTestUsers(): Promise<{
15
+ metaOrgUser: IUser;
16
+ testOrgUser: IUser;
17
+ }>;
15
18
  declare function deleteTestUser(): Promise<void>;
16
19
  declare function simulateloginWithTestUser(): Promise<string>;
17
20
  declare function getAuthToken(): string;
@@ -22,6 +25,7 @@ export declare class CategoryService extends GenericApiService<ICategory> {
22
25
  export declare class CategoryController extends ApiController<ICategory> {
23
26
  constructor(app: Application, database: IDatabase);
24
27
  }
28
+ export declare function setupTestConfig(isMultiTenant?: boolean): void;
25
29
  export declare class ProductService extends GenericApiService<IProduct> {
26
30
  private db;
27
31
  constructor(database: IDatabase);
@@ -59,10 +63,11 @@ declare const testUtils: {
59
63
  deleteTestUser: typeof deleteTestUser;
60
64
  getAuthToken: typeof getAuthToken;
61
65
  initialize: typeof initialize;
66
+ SetupTestConfig: typeof setupTestConfig;
62
67
  loginWithTestUser: typeof loginWithTestUser;
63
68
  newUser1Email: string;
64
69
  newUser1Password: string;
65
- setupTestUser: typeof setupTestUser;
70
+ setupTestUsers: typeof setupTestUsers;
66
71
  simulateloginWithTestUser: typeof simulateloginWithTestUser;
67
72
  verifyToken: typeof verifyToken;
68
73
  };
@@ -8,9 +8,12 @@ import { Join } from '../databases/operations/join.operation.js';
8
8
  import { OrganizationService } from '../services/organization.service.js';
9
9
  import { AuthService, GenericApiService } from '../services/index.js';
10
10
  import { ObjectId } from 'mongodb';
11
- import { getTestMetaOrg, getTestOrg, getTestMetaOrgUser, getTestMetaOrgUserContext } from './test-objects.js';
11
+ import * as testObjectsModule from './test-objects.js';
12
+ const { getTestMetaOrg, getTestOrg, getTestMetaOrgUser, getTestMetaOrgUserContext, getTestOrgUser, getTestOrgUserContext, setTestOrgId, setTestMetaOrgId } = testObjectsModule;
12
13
  import { CategorySpec } from './models/category.model.js';
13
14
  import { ProductSpec } from './models/product.model.js';
15
+ import { setBaseApiConfig } from '../config/index.js';
16
+ import { entityUtils } from '@loomcore/common/utils';
14
17
  let deviceIdCookie;
15
18
  let authService;
16
19
  let organizationService;
@@ -52,17 +55,17 @@ async function deleteMetaOrg() {
52
55
  console.log('Error deleting meta org:', error);
53
56
  }
54
57
  }
55
- async function setupTestUser() {
58
+ async function setupTestUsers() {
56
59
  try {
57
60
  await deleteTestUser();
58
- return createTestUser();
61
+ return createTestUsers();
59
62
  }
60
63
  catch (error) {
61
64
  console.log(error);
62
65
  throw error;
63
66
  }
64
67
  }
65
- async function createTestUser() {
68
+ async function createTestUsers() {
66
69
  if (!authService || !organizationService) {
67
70
  throw new Error('Database not initialized. Call initialize() first.');
68
71
  }
@@ -71,15 +74,26 @@ async function createTestUser() {
71
74
  if (!existingMetaOrg) {
72
75
  await organizationService.create(getTestMetaOrgUserContext(), getTestMetaOrg());
73
76
  }
77
+ else {
78
+ setTestMetaOrgId(existingMetaOrg._id);
79
+ }
74
80
  const existingTestOrg = await organizationService.findOne(getTestMetaOrgUserContext(), { filters: { _id: { eq: getTestOrg()._id } } });
75
81
  if (!existingTestOrg) {
76
- await organizationService.create(getTestMetaOrgUserContext(), getTestOrg());
82
+ const createdTestOrg = await organizationService.create(getTestMetaOrgUserContext(), getTestOrg());
83
+ if (!createdTestOrg) {
84
+ throw new Error('Failed to create test organization');
85
+ }
86
+ setTestOrgId(createdTestOrg._id);
87
+ }
88
+ else {
89
+ setTestOrgId(existingTestOrg._id);
77
90
  }
78
- const createdUser = await authService.createUser(getTestMetaOrgUserContext(), getTestMetaOrgUser());
79
- if (!createdUser) {
91
+ const createdTestOrgUser = await authService.createUser(getTestOrgUserContext(), getTestOrgUser());
92
+ const createdMetaOrgUser = await authService.createUser(getTestMetaOrgUserContext(), getTestMetaOrgUser());
93
+ if (!createdTestOrgUser || !createdMetaOrgUser) {
80
94
  throw new Error('Failed to create test user');
81
95
  }
82
- return createdUser;
96
+ return { metaOrgUser: createdMetaOrgUser, testOrgUser: createdTestOrgUser };
83
97
  }
84
98
  catch (error) {
85
99
  console.log('Error in createTestUser:', error);
@@ -141,6 +155,43 @@ export class CategoryController extends ApiController {
141
155
  super('categories', app, categoryService, 'category', CategorySpec);
142
156
  }
143
157
  }
158
+ export function setupTestConfig(isMultiTenant = true) {
159
+ setBaseApiConfig({
160
+ env: 'test',
161
+ hostName: 'localhost',
162
+ appName: 'test-app',
163
+ clientSecret: 'test-secret',
164
+ database: {
165
+ name: 'test-db',
166
+ },
167
+ externalPort: 4000,
168
+ internalPort: 8083,
169
+ corsAllowedOrigins: ['*'],
170
+ saltWorkFactor: 10,
171
+ jobTypes: '',
172
+ deployedBranch: '',
173
+ debug: {
174
+ showErrors: false
175
+ },
176
+ app: { isMultiTenant: isMultiTenant },
177
+ auth: {
178
+ jwtExpirationInSeconds: 3600,
179
+ refreshTokenExpirationInDays: 7,
180
+ deviceIdCookieMaxAgeInDays: 730,
181
+ passwordResetTokenExpirationInMinutes: 20
182
+ },
183
+ email: {
184
+ emailApiKey: 'WeDontHaveAKeyYet',
185
+ emailApiSecret: 'WeDontHaveASecretYet',
186
+ fromAddress: 'test@test.com',
187
+ systemEmailAddress: 'system@test.com'
188
+ },
189
+ adminUser: {
190
+ email: 'admin@test.com',
191
+ password: 'admin-password'
192
+ }
193
+ });
194
+ }
144
195
  export class ProductService extends GenericApiService {
145
196
  db;
146
197
  constructor(database) {
@@ -172,7 +223,8 @@ export class ProductsController extends ApiController {
172
223
  }))
173
224
  ]);
174
225
  const PublicAggregatedProductSchema = Type.Omit(AggregatedProductSchema, ['internalNumber']);
175
- super('products', app, productService, 'product', ProductSpec, PublicAggregatedProductSchema);
226
+ const PublicAggregatedProductSpec = entityUtils.getModelSpec(PublicAggregatedProductSchema);
227
+ super('products', app, productService, 'product', ProductSpec, PublicAggregatedProductSpec);
176
228
  }
177
229
  }
178
230
  export class MultiTenantProductService extends MultiTenantApiService {
@@ -206,7 +258,8 @@ export class MultiTenantProductsController extends ApiController {
206
258
  }))
207
259
  ]);
208
260
  const PublicAggregatedProductSchema = Type.Omit(AggregatedProductSchema, ['internalNumber']);
209
- super('multi-tenant-products', app, productService, 'product', ProductSpec, PublicAggregatedProductSchema);
261
+ const PublicAggregatedProductSpec = entityUtils.getModelSpec(PublicAggregatedProductSchema);
262
+ super('multi-tenant-products', app, productService, 'product', ProductSpec, PublicAggregatedProductSpec);
210
263
  }
211
264
  }
212
265
  function configureJwtSecret() {
@@ -250,10 +303,11 @@ const testUtils = {
250
303
  deleteTestUser,
251
304
  getAuthToken,
252
305
  initialize,
306
+ SetupTestConfig: setupTestConfig,
253
307
  loginWithTestUser,
254
308
  newUser1Email,
255
309
  newUser1Password,
256
- setupTestUser,
310
+ setupTestUsers,
257
311
  simulateloginWithTestUser,
258
312
  verifyToken
259
313
  };
@@ -1,14 +1,12 @@
1
1
  import { CreateTestEntitiesTableMigration } from "./001-create-test-entities-table.migration.js";
2
2
  import { CreateCategoriesTableMigration } from "./002-create-categories-table.migration.js";
3
3
  import { CreateProductsTableMigration } from "./003-create-products-table.migration.js";
4
- import { CreateTestUsersTableMigration } from "./004-create-test-users-table.migration.js";
5
4
  import { CreateTestItemsTableMigration } from "./005-create-test-items-table.migration.js";
6
5
  export async function runTestMigrations(client, _orgId) {
7
6
  const migrations = [
8
7
  new CreateTestEntitiesTableMigration(client),
9
8
  new CreateCategoriesTableMigration(client),
10
9
  new CreateProductsTableMigration(client),
11
- new CreateTestUsersTableMigration(client),
12
10
  new CreateTestItemsTableMigration(client),
13
11
  ];
14
12
  try {
@@ -6,8 +6,8 @@ import { setupDatabaseForMultitenant } from '../databases/postgres/migrations/se
6
6
  import { setupDatabaseForAuth } from '../databases/postgres/migrations/setup-for-auth.migration.js';
7
7
  import { runTestMigrations } from './postgres-test-migrations/run-test-migrations.js';
8
8
  import { PostgresDatabase } from '../databases/postgres/postgres.database.js';
9
- import { getTestMetaOrg, setTestMetaOrgId } from './test-objects.js';
10
9
  import { getSystemUserContext } from '@loomcore/common/models';
10
+ import { setTestMetaOrgId } from './test-objects.js';
11
11
  export class TestPostgresDatabase {
12
12
  database = null;
13
13
  postgresClient = null;
@@ -30,19 +30,20 @@ export class TestPostgresDatabase {
30
30
  const testDatabase = new PostgresDatabase(postgresClient);
31
31
  this.database = testDatabase;
32
32
  this.postgresClient = postgresClient;
33
- const metaOrg = getTestMetaOrg();
34
- const multitenantResult = await setupDatabaseForMultitenant(postgresClient, metaOrg.name, metaOrg.code);
33
+ const multitenantResult = await setupDatabaseForMultitenant(postgresClient);
35
34
  let success = multitenantResult.success;
36
- if (!success || !multitenantResult.metaOrgId) {
35
+ if (!success) {
37
36
  throw new Error('Failed to setup for multitenant');
38
37
  }
39
- setTestMetaOrgId(multitenantResult.metaOrgId);
40
38
  await initSystemUserContext(this.database);
41
- success = (await setupDatabaseForAuth(postgresClient, adminUsername || 'admin', adminPassword || 'password')).success;
39
+ success = (await setupDatabaseForAuth(postgresClient)).success;
42
40
  if (!success) {
43
41
  throw new Error('Failed to setup for auth');
44
42
  }
45
43
  const systemUserContext = getSystemUserContext();
44
+ if (systemUserContext._orgId) {
45
+ setTestMetaOrgId(systemUserContext._orgId);
46
+ }
46
47
  success = (await runTestMigrations(postgresClient, systemUserContext._orgId)).success;
47
48
  if (!success) {
48
49
  throw new Error('Failed to run test migrations');
@@ -3,11 +3,11 @@ import bodyParser from 'body-parser';
3
3
  import cookieParser from 'cookie-parser';
4
4
  import supertest from 'supertest';
5
5
  import { initializeTypeBox } from '@loomcore/common/validation';
6
- import { setBaseApiConfig } from '../config/base-api-config.js';
7
6
  import { errorHandler } from '../middleware/error-handler.js';
8
7
  import { ensureUserContext } from '../middleware/ensure-user-context.js';
9
8
  import { TestMongoDatabase } from './mongo-db.test-database.js';
10
9
  import { TestPostgresDatabase } from './postgres.test-database.js';
10
+ import { setupTestConfig } from './common-test.utils.js';
11
11
  export class TestExpressApp {
12
12
  static app;
13
13
  static database;
@@ -26,36 +26,7 @@ export class TestExpressApp {
26
26
  return this.initPromise;
27
27
  }
28
28
  static async _performInit(useMongoDb) {
29
- setBaseApiConfig({
30
- env: 'test',
31
- hostName: 'localhost',
32
- appName: 'test-app',
33
- clientSecret: 'test-secret',
34
- database: {
35
- name: this.databaseName,
36
- },
37
- externalPort: 4000,
38
- internalPort: 8083,
39
- corsAllowedOrigins: ['*'],
40
- saltWorkFactor: 10,
41
- jobTypes: '',
42
- deployedBranch: '',
43
- debug: {
44
- showErrors: false
45
- },
46
- app: { isMultiTenant: true },
47
- auth: {
48
- jwtExpirationInSeconds: 3600,
49
- refreshTokenExpirationInDays: 7,
50
- deviceIdCookieMaxAgeInDays: 730,
51
- passwordResetTokenExpirationInMinutes: 20
52
- },
53
- email: {
54
- emailApiKey: 'WeDontHaveAKeyYet',
55
- emailApiSecret: 'WeDontHaveASecretYet',
56
- fromAddress: undefined
57
- }
58
- });
29
+ setupTestConfig();
59
30
  initializeTypeBox();
60
31
  if (!this.database) {
61
32
  if (useMongoDb) {
@@ -4,6 +4,7 @@ export declare function setTestMetaOrgId(metaOrgId: string): void;
4
4
  export declare function getTestMetaOrg(): IOrganization;
5
5
  export declare function getTestMetaOrgUser(): IUser;
6
6
  export declare function getTestMetaOrgUserContext(): IUserContext;
7
+ export declare function setTestOrgId(orgId: string): void;
7
8
  export declare function getTestOrg(): IOrganization;
8
9
  export declare function getTestOrgUser(): IUser;
9
10
  export declare function getTestOrgUserContext(): IUserContext;
@@ -51,9 +51,13 @@ export function getTestMetaOrgUserContext() {
51
51
  };
52
52
  }
53
53
  ;
54
+ let TEST_ORG_ID = '6926167d06c0073a778a124f';
55
+ export function setTestOrgId(orgId) {
56
+ TEST_ORG_ID = orgId;
57
+ }
54
58
  export function getTestOrg() {
55
59
  return {
56
- _id: '6926167d06c0073a778a124f',
60
+ _id: TEST_ORG_ID,
57
61
  name: 'Test Organization',
58
62
  code: 'test-org',
59
63
  status: 1,
@@ -23,7 +23,7 @@ export async function initSystemUserContext(database) {
23
23
  throw new Error('BaseApiConfig has not been set. Call setBaseApiConfig first.');
24
24
  }
25
25
  if (!isSystemUserContextSet) {
26
- const systemEmail = config.email.systemEmailAddress || 'system@example.com';
26
+ const systemEmail = config.email?.systemEmailAddress || 'system@example.com';
27
27
  let metaOrgId = undefined;
28
28
  if (config.app.isMultiTenant) {
29
29
  const { OrganizationService } = await import('../services/organization.service.js');
@@ -1,5 +1,4 @@
1
1
  import { Application, NextFunction, Request, Response } from 'express';
2
- import { TSchema } from '@sinclair/typebox';
3
2
  import { IEntity, IModelSpec } from '@loomcore/common/models';
4
3
  import { IGenericApiService } from '../services/index.js';
5
4
  export declare abstract class ApiController<T extends IEntity> {
@@ -8,8 +7,8 @@ export declare abstract class ApiController<T extends IEntity> {
8
7
  protected slug: string;
9
8
  protected apiResourceName: string;
10
9
  protected modelSpec?: IModelSpec;
11
- protected publicSchema?: TSchema;
12
- protected constructor(slug: string, app: Application, service: IGenericApiService<T>, resourceName?: string, modelSpec?: IModelSpec, publicSchema?: TSchema);
10
+ protected publicSpec?: IModelSpec;
11
+ protected constructor(slug: string, app: Application, service: IGenericApiService<T>, resourceName?: string, modelSpec?: IModelSpec, publicSpec?: IModelSpec);
13
12
  mapRoutes(app: Application): void;
14
13
  protected validate(entity: any, isPartial?: boolean): void;
15
14
  protected validateMany(entities: any[], isPartial?: boolean): void;
@@ -8,14 +8,14 @@ export class ApiController {
8
8
  slug;
9
9
  apiResourceName;
10
10
  modelSpec;
11
- publicSchema;
12
- constructor(slug, app, service, resourceName = '', modelSpec, publicSchema) {
11
+ publicSpec;
12
+ constructor(slug, app, service, resourceName = '', modelSpec, publicSpec) {
13
13
  this.slug = slug;
14
14
  this.app = app;
15
15
  this.service = service;
16
16
  this.apiResourceName = resourceName;
17
17
  this.modelSpec = modelSpec;
18
- this.publicSchema = publicSchema;
18
+ this.publicSpec = publicSpec;
19
19
  this.mapRoutes(app);
20
20
  }
21
21
  mapRoutes(app) {
@@ -40,30 +40,30 @@ export class ApiController {
40
40
  async getAll(req, res, next) {
41
41
  res.set('Content-Type', 'application/json');
42
42
  const entities = await this.service.getAll(req.userContext);
43
- apiUtils.apiResponse(res, 200, { data: entities }, this.modelSpec, this.publicSchema);
43
+ apiUtils.apiResponse(res, 200, { data: entities }, this.modelSpec, this.publicSpec);
44
44
  }
45
45
  async get(req, res, next) {
46
46
  res.set('Content-Type', 'application/json');
47
47
  const queryOptions = apiUtils.getQueryOptionsFromRequest(req);
48
48
  const pagedResult = await this.service.get(req.userContext, queryOptions);
49
- apiUtils.apiResponse(res, 200, { data: pagedResult }, this.modelSpec, this.publicSchema);
49
+ apiUtils.apiResponse(res, 200, { data: pagedResult }, this.modelSpec, this.publicSpec);
50
50
  }
51
51
  async getById(req, res, next) {
52
52
  let id = req.params?.id;
53
53
  res.set('Content-Type', 'application/json');
54
54
  const entity = await this.service.getById(req.userContext, id);
55
- apiUtils.apiResponse(res, 200, { data: entity }, this.modelSpec, this.publicSchema);
55
+ apiUtils.apiResponse(res, 200, { data: entity }, this.modelSpec, this.publicSpec);
56
56
  }
57
57
  async getCount(req, res, next) {
58
58
  res.set('Content-Type', 'application/json');
59
59
  const count = await this.service.getCount(req.userContext);
60
- apiUtils.apiResponse(res, 200, { data: count }, this.modelSpec, this.publicSchema);
60
+ apiUtils.apiResponse(res, 200, { data: count }, this.modelSpec, this.publicSpec);
61
61
  }
62
62
  async create(req, res, next) {
63
63
  res.set('Content-Type', 'application/json');
64
64
  this.validate(req.body);
65
65
  const entity = await this.service.create(req.userContext, req.body);
66
- apiUtils.apiResponse(res, 201, { data: entity || undefined }, this.modelSpec, this.publicSchema);
66
+ apiUtils.apiResponse(res, 201, { data: entity || undefined }, this.modelSpec, this.publicSpec);
67
67
  }
68
68
  async batchUpdate(req, res, next) {
69
69
  res.set('Content-Type', 'application/json');
@@ -73,23 +73,23 @@ export class ApiController {
73
73
  }
74
74
  this.validateMany(entities, true);
75
75
  const updatedEntities = await this.service.batchUpdate(req.userContext, entities);
76
- apiUtils.apiResponse(res, 200, { data: updatedEntities }, this.modelSpec, this.publicSchema);
76
+ apiUtils.apiResponse(res, 200, { data: updatedEntities }, this.modelSpec, this.publicSpec);
77
77
  }
78
78
  async fullUpdateById(req, res, next) {
79
79
  res.set('Content-Type', 'application/json');
80
80
  this.validate(req.body);
81
81
  const updateResult = await this.service.fullUpdateById(req.userContext, req.params.id, req.body);
82
- apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.publicSchema);
82
+ apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.publicSpec);
83
83
  }
84
84
  async partialUpdateById(req, res, next) {
85
85
  res.set('Content-Type', 'application/json');
86
86
  this.validate(req.body, true);
87
87
  const updateResult = await this.service.partialUpdateById(req.userContext, req.params.id, req.body);
88
- apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.publicSchema);
88
+ apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.publicSpec);
89
89
  }
90
90
  async deleteById(req, res, next) {
91
91
  res.set('Content-Type', 'application/json');
92
92
  const deleteResult = await this.service.deleteById(req.userContext, req.params.id);
93
- apiUtils.apiResponse(res, 200, { data: deleteResult }, this.modelSpec, this.publicSchema);
93
+ apiUtils.apiResponse(res, 200, { data: deleteResult }, this.modelSpec, this.publicSpec);
94
94
  }
95
95
  }
@@ -1,4 +1,4 @@
1
- import { LoginResponseSpec, TokenResponseSpec, UserSpec, PublicUserSchema, passwordValidator, PublicUserContextSpec, } from '@loomcore/common/models';
1
+ import { LoginResponseSpec, TokenResponseSpec, UserSpec, PublicUserSpec, passwordValidator, PublicUserContextSpec, } from '@loomcore/common/models';
2
2
  import { entityUtils } from '@loomcore/common/utils';
3
3
  import { BadRequestError, UnauthenticatedError } from '../errors/index.js';
4
4
  import { isAuthenticated } from '../middleware/index.js';
@@ -35,7 +35,7 @@ export class AuthController {
35
35
  throw new BadRequestError('Missing required fields: userContext is required.');
36
36
  }
37
37
  const user = await this.authService.createUser(userContext, body);
38
- apiUtils.apiResponse(res, 201, { data: user || undefined }, UserSpec, PublicUserSchema);
38
+ apiUtils.apiResponse(res, 201, { data: user || undefined }, UserSpec, PublicUserSpec);
39
39
  }
40
40
  async requestTokenUsingRefreshToken(req, res, next) {
41
41
  const refreshToken = req.query.refreshToken;
@@ -1,9 +1,10 @@
1
1
  import { Application } from 'express';
2
2
  import { IUser } from '@loomcore/common/models';
3
3
  import { ApiController } from './api.controller.js';
4
+ import { UserService } from '../services/index.js';
4
5
  import { IDatabase } from '../databases/models/index.js';
5
6
  export declare class UsersController extends ApiController<IUser> {
6
- private userService;
7
+ userService: UserService;
7
8
  constructor(app: Application, database: IDatabase);
8
9
  mapRoutes(app: Application): void;
9
10
  }
@@ -1,4 +1,4 @@
1
- import { UserSpec, PublicUserSchema } from '@loomcore/common/models';
1
+ import { UserSpec, PublicUserSpec } from '@loomcore/common/models';
2
2
  import { ApiController } from './api.controller.js';
3
3
  import { isAuthenticated } from '../middleware/index.js';
4
4
  import { UserService } from '../services/index.js';
@@ -6,7 +6,7 @@ export class UsersController extends ApiController {
6
6
  userService;
7
7
  constructor(app, database) {
8
8
  const userService = new UserService(database);
9
- super('users', app, userService, 'user', UserSpec, PublicUserSchema);
9
+ super('users', app, userService, 'user', UserSpec, PublicUserSpec);
10
10
  this.userService = userService;
11
11
  }
12
12
  mapRoutes(app) {
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "crypto";
2
2
  import { doesTableExist } from "../utils/does-table-exist.util.js";
3
+ import { config } from "../../../config/index.js";
3
4
  export class CreateUsersTableMigration {
4
5
  client;
5
6
  constructor(client) {
@@ -12,6 +13,9 @@ export class CreateUsersTableMigration {
12
13
  await this.client.query('BEGIN');
13
14
  const tableExists = await doesTableExist(this.client, 'users');
14
15
  if (!tableExists) {
16
+ const fkConstraint = config.app.isMultiTenant
17
+ ? ',\n CONSTRAINT "fk_users_organization" FOREIGN KEY ("_orgId") REFERENCES "organizations"("_id") ON DELETE CASCADE'
18
+ : '';
15
19
  await this.client.query(`
16
20
  CREATE TABLE "users" (
17
21
  "_id" VARCHAR(255) PRIMARY KEY,
@@ -29,8 +33,7 @@ export class CreateUsersTableMigration {
29
33
  "_updatedBy" VARCHAR(255) NOT NULL,
30
34
  "_deleted" TIMESTAMP,
31
35
  "_deletedBy" VARCHAR(255),
32
- CONSTRAINT "fk_users_organization" FOREIGN KEY ("_orgId") REFERENCES "organizations"("_id") ON DELETE CASCADE,
33
- CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")
36
+ CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")${fkConstraint}
34
37
  )
35
38
  `);
36
39
  }
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "crypto";
2
2
  import { doesTableExist } from "../utils/does-table-exist.util.js";
3
+ import { config } from "../../../config/index.js";
3
4
  export class CreateRefreshTokenTableMigration {
4
5
  client;
5
6
  constructor(client) {
@@ -12,6 +13,9 @@ export class CreateRefreshTokenTableMigration {
12
13
  await this.client.query('BEGIN');
13
14
  const tableExists = await doesTableExist(this.client, 'refreshTokens');
14
15
  if (!tableExists) {
16
+ const fkConstraint = config.app.isMultiTenant
17
+ ? ',\n CONSTRAINT "fk_refreshTokens_organization" FOREIGN KEY ("_orgId") REFERENCES "organizations"("_id") ON DELETE CASCADE'
18
+ : '';
15
19
  await this.client.query(`
16
20
  CREATE TABLE "refreshTokens" (
17
21
  "_id" VARCHAR(255) PRIMARY KEY,
@@ -21,7 +25,7 @@ export class CreateRefreshTokenTableMigration {
21
25
  "userId" VARCHAR(255) NOT NULL,
22
26
  "expiresOn" BIGINT NOT NULL,
23
27
  "created" TIMESTAMP NOT NULL,
24
- "createdBy" VARCHAR(255) NOT NULL
28
+ "createdBy" VARCHAR(255) NOT NULL${fkConstraint}
25
29
  )
26
30
  `);
27
31
  }
@@ -2,17 +2,13 @@ import { Client } from "pg";
2
2
  import { IMigration } from "./migration.interface.js";
3
3
  export declare class CreateMetaOrgMigration implements IMigration {
4
4
  private readonly client;
5
- private readonly orgName;
6
- private readonly orgCode;
7
- constructor(client: Client, orgName: string, orgCode: string);
5
+ constructor(client: Client);
8
6
  index: number;
9
7
  execute(): Promise<{
10
8
  success: boolean;
11
9
  error: Error;
12
- metaOrgId?: undefined;
13
10
  } | {
14
11
  success: boolean;
15
- metaOrgId: string;
16
12
  error: null;
17
13
  }>;
18
14
  revert(): Promise<{
@@ -1,12 +1,10 @@
1
1
  import { randomUUID } from "crypto";
2
+ import { config } from "../../../config/index.js";
3
+ import { initializeSystemUserContext } from "@loomcore/common/models";
2
4
  export class CreateMetaOrgMigration {
3
5
  client;
4
- orgName;
5
- orgCode;
6
- constructor(client, orgName, orgCode) {
6
+ constructor(client) {
7
7
  this.client = client;
8
- this.orgName = orgName;
9
- this.orgCode = orgCode;
10
8
  }
11
9
  index = 5;
12
10
  async execute() {
@@ -15,12 +13,14 @@ export class CreateMetaOrgMigration {
15
13
  await this.client.query('BEGIN');
16
14
  const orgResult = await this.client.query(`
17
15
  INSERT INTO "organizations" ("_id", "name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy")
18
- VALUES ('${_id}', '${this.orgName}', '${this.orgCode}', 1, true, NOW(), 'system', NOW(), 'system');
16
+ VALUES ('${_id}', '${config.app.metaOrgName}', '${config.app.metaOrgCode}', 1, true, NOW(), 'system', NOW(), 'system')
17
+ RETURNING "_id";
19
18
  `);
20
19
  if (orgResult.rowCount === 0) {
21
20
  await this.client.query('ROLLBACK');
22
21
  return { success: false, error: new Error(`Error creating meta org: No row returned`) };
23
22
  }
23
+ initializeSystemUserContext(config.email?.systemEmailAddress || 'system@example.com', orgResult.rows[0]._id);
24
24
  const migrationResult = await this.client.query(`
25
25
  INSERT INTO "migrations" ("_id", "index", "hasRun", "reverted")
26
26
  VALUES ('${_id}', ${this.index}, TRUE, FALSE);
@@ -30,7 +30,7 @@ export class CreateMetaOrgMigration {
30
30
  return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
31
31
  }
32
32
  await this.client.query('COMMIT');
33
- return { success: true, metaOrgId: _id, error: null };
33
+ return { success: true, error: null };
34
34
  }
35
35
  catch (error) {
36
36
  await this.client.query('ROLLBACK');
@@ -3,11 +3,14 @@ import { IMigration } from "../index.js";
3
3
  export declare class CreateAdminUserMigration implements IMigration {
4
4
  private readonly client;
5
5
  constructor(client: Client);
6
+ private authService;
6
7
  index: number;
7
- execute(adminEmail?: string, adminPassword?: string, metaOrgId?: string): Promise<{
8
+ execute(): Promise<{
8
9
  success: boolean;
9
- adminUserId: string | undefined;
10
- error: Error | null;
10
+ error: Error;
11
+ } | {
12
+ success: boolean;
13
+ error: null;
11
14
  }>;
12
15
  revert(): Promise<{
13
16
  success: boolean;