@loomcore/api 0.2.4 → 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 (31) hide show
  1. package/dist/__tests__/common-test.utils.d.ts +12 -12
  2. package/dist/__tests__/common-test.utils.js +96 -90
  3. package/dist/__tests__/postgres.test-database.d.ts +2 -2
  4. package/dist/__tests__/postgres.test-database.js +16 -15
  5. package/dist/config/base-api-config.d.ts +2 -2
  6. package/dist/config/base-api-config.js +17 -10
  7. package/dist/databases/mongo-db/migrations/mongo-initial-schema.d.ts +2 -2
  8. package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +185 -128
  9. package/dist/services/auth.service.d.ts +7 -7
  10. package/dist/services/auth.service.js +71 -58
  11. package/dist/services/email.service.js +5 -5
  12. package/dist/services/jwt.service.js +2 -2
  13. package/dist/services/multi-tenant-api.service.d.ts +4 -4
  14. package/dist/services/multi-tenant-api.service.js +8 -8
  15. package/dist/services/organization.service.d.ts +4 -4
  16. package/dist/services/organization.service.js +12 -8
  17. package/dist/services/password-reset-token.service.d.ts +4 -4
  18. package/dist/services/password-reset-token.service.js +13 -9
  19. package/dist/services/person.service.d.ts +4 -4
  20. package/dist/services/person.service.js +3 -3
  21. package/dist/services/tenant-query-decorator.d.ts +1 -1
  22. package/dist/services/tenant-query-decorator.js +17 -11
  23. package/dist/services/user.service.d.ts +6 -6
  24. package/dist/services/user.service.js +7 -7
  25. package/dist/services/utils/audit-for-create.util.d.ts +1 -1
  26. package/dist/services/utils/audit-for-update.util.d.ts +1 -1
  27. package/dist/services/utils/audit-for-update.util.js +0 -1
  28. package/dist/services/utils/getUserContextAuthorizations.util.d.ts +2 -2
  29. package/dist/services/utils/strip-sender-provided-system-properties.util.d.ts +1 -1
  30. package/dist/services/utils/strip-sender-provided-system-properties.util.js +5 -3
  31. package/package.json +1 -1
@@ -1,19 +1,19 @@
1
- import { Request, Response, Application, NextFunction } from 'express';
2
- import { IUserContext, IQueryOptions, IUser } from '@loomcore/common/models';
3
- import { ApiController } from '../controllers/api.controller.js';
4
- import { MultiTenantApiService } from '../services/multi-tenant-api.service.js';
5
- import { Operation } from '../databases/operations/operation.js';
6
- import { GenericApiService } from '../services/index.js';
7
- import { IDatabase } from '../databases/models/index.js';
8
- import { ICategory } from './models/category.model.js';
9
- import { IProduct } from './models/product.model.js';
10
- import { IProductWithCategory } from './models/product-with-category.model.js';
11
- import { DbType } from '../databases/db-type.type.js';
1
+ import { IQueryOptions, IUser, IUserContext } from "@loomcore/common/models";
2
+ import { Application, NextFunction, Request, Response } from "express";
3
+ import { ApiController } from "../controllers/api.controller.js";
4
+ import { IDatabase } from "../databases/models/index.js";
5
+ import { Operation } from "../databases/operations/operation.js";
6
+ import { GenericApiService } from "../services/index.js";
7
+ import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
8
+ import { DbType } from "../databases/db-type.type.js";
9
+ import { ICategory } from "./models/category.model.js";
10
+ import { IProduct } from "./models/product.model.js";
11
+ import { IProductWithCategory } from "./models/product-with-category.model.js";
12
12
  declare function initialize(database: IDatabase): void;
13
13
  declare function getRandomId(): string;
14
14
  declare function isMongoDatabase(database: IDatabase): boolean;
15
15
  declare function isPostgresDatabase(database: IDatabase): boolean;
16
- declare function getExpectedIdType(database: IDatabase): 'string' | 'number';
16
+ declare function getExpectedIdType(database: IDatabase): "string" | "number";
17
17
  declare function createMetaOrg(): Promise<void>;
18
18
  declare function deleteMetaOrg(): Promise<void>;
19
19
  declare function setupTestUsers(): Promise<{
@@ -1,36 +1,36 @@
1
- import crypto from 'crypto';
2
- import jwt from 'jsonwebtoken';
3
- import { EmptyUserContext } from '@loomcore/common/models';
4
- import { Type } from '@sinclair/typebox';
5
- import { Value } from '@sinclair/typebox/value';
6
- import { JwtService } from '../services/jwt.service.js';
7
- import { ApiController } from '../controllers/api.controller.js';
8
- import { MultiTenantApiService } from '../services/multi-tenant-api.service.js';
9
- import { LeftJoin } from '../databases/operations/left-join.operation.js';
10
- import { OrganizationService } from '../services/organization.service.js';
11
- import { AuthService, GenericApiService } from '../services/index.js';
12
- import { ObjectId } from 'mongodb';
13
- import { MongoDBDatabase } from '../databases/mongo-db/mongo-db.database.js';
14
- import { PostgresDatabase } from '../databases/postgres/postgres.database.js';
15
- import * as testObjectsModule from './test-objects.js';
16
- const { getTestMetaOrg, getTestOrg, getTestMetaOrgUser, getTestMetaOrgUserContext, getTestOrgUserContext, setTestOrgId, setTestMetaOrgId, setTestMetaOrgUserId, setTestOrgUserId } = testObjectsModule;
17
- import { CategorySpec } from './models/category.model.js';
18
- import { ProductSpec } from './models/product.model.js';
19
- import { ProductWithCategoryPublicSpec, ProductWithCategorySpec } from './models/product-with-category.model.js';
20
- import { setBaseApiConfig, config } from '../config/index.js';
21
- import { entityUtils } from '@loomcore/common/utils';
22
- import { getTestMetaOrgUserPerson, getTestOrgUser, getTestOrgUserPerson, setTestMetaOrgUserPersonId, setTestOrgUserPersonId } from './test-objects.js';
23
- import { TestEmailClient } from './test-email-client.js';
24
- import { PersonService } from '../services/person.service.js';
25
- import { apiUtils } from '../utils/index.js';
1
+ import { EmptyUserContext, } from "@loomcore/common/models";
2
+ import { Type } from "@sinclair/typebox";
3
+ import { Value } from "@sinclair/typebox/value";
4
+ import crypto from "crypto";
5
+ import jwt from "jsonwebtoken";
6
+ import { ObjectId } from "mongodb";
7
+ import { ApiController } from "../controllers/api.controller.js";
8
+ import { MongoDBDatabase } from "../databases/mongo-db/mongo-db.database.js";
9
+ import { LeftJoin } from "../databases/operations/left-join.operation.js";
10
+ import { PostgresDatabase } from "../databases/postgres/postgres.database.js";
11
+ import { AuthService, GenericApiService } from "../services/index.js";
12
+ import { JwtService } from "../services/jwt.service.js";
13
+ import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
14
+ import { OrganizationService } from "../services/organization.service.js";
15
+ import * as testObjectsModule from "./test-objects.js";
16
+ const { getTestMetaOrg, getTestOrg, getTestMetaOrgUser, getTestMetaOrgUserContext, getTestOrgUserContext, setTestOrgId, setTestMetaOrgId, setTestMetaOrgUserId, setTestOrgUserId, } = testObjectsModule;
17
+ import { entityUtils } from "@loomcore/common/utils";
18
+ import { config, setBaseApiConfig } from "../config/index.js";
19
+ import { PersonService } from "../services/person.service.js";
20
+ import { apiUtils } from "../utils/index.js";
21
+ import { CategorySpec } from "./models/category.model.js";
22
+ import { ProductSpec } from "./models/product.model.js";
23
+ import { ProductWithCategoryPublicSpec, ProductWithCategorySpec, } from "./models/product-with-category.model.js";
24
+ import { TestEmailClient } from "./test-email-client.js";
25
+ import { getTestMetaOrgUserPerson, getTestOrgUser, getTestOrgUserPerson, setTestMetaOrgUserPersonId, setTestOrgUserPersonId, } from "./test-objects.js";
26
26
  let deviceIdCookie;
27
27
  let authService;
28
28
  let organizationService;
29
29
  let personService;
30
- const JWT_SECRET = 'test-secret';
31
- const newUser1Email = 'one@test.com';
32
- const newUser1Password = 'testone';
33
- const constDeviceIdCookie = crypto.randomBytes(16).toString('hex');
30
+ const JWT_SECRET = "test-secret";
31
+ const newUser1Email = "one@test.com";
32
+ const newUser1Password = "testone";
33
+ const constDeviceIdCookie = crypto.randomBytes(16).toString("hex");
34
34
  function initialize(database) {
35
35
  authService = new AuthService(database);
36
36
  personService = new PersonService(database);
@@ -47,14 +47,14 @@ function isPostgresDatabase(database) {
47
47
  return database instanceof PostgresDatabase;
48
48
  }
49
49
  function getExpectedIdType(database) {
50
- return isPostgresDatabase(database) ? 'number' : 'string';
50
+ return isPostgresDatabase(database) ? "number" : "string";
51
51
  }
52
52
  async function createMetaOrg() {
53
53
  if (!config.app.isMultiTenant) {
54
54
  return;
55
55
  }
56
56
  if (!organizationService) {
57
- throw new Error('OrganizationService not initialized. Call initialize() first.');
57
+ throw new Error("OrganizationService not initialized. Call initialize() first.");
58
58
  }
59
59
  try {
60
60
  const existingMetaOrg = await organizationService.getMetaOrg(EmptyUserContext);
@@ -69,7 +69,7 @@ async function createMetaOrg() {
69
69
  }
70
70
  }
71
71
  catch (error) {
72
- console.log('Error in createMetaOrg:', error);
72
+ console.log("Error in createMetaOrg:", error);
73
73
  throw error;
74
74
  }
75
75
  }
@@ -78,10 +78,12 @@ async function deleteMetaOrg() {
78
78
  return Promise.resolve();
79
79
  }
80
80
  try {
81
- await organizationService.deleteMany(getTestMetaOrgUserContext(), { filters: { isMetaOrg: { eq: true } } });
81
+ await organizationService.deleteMany(getTestMetaOrgUserContext(), {
82
+ filters: { isMetaOrg: { eq: true } },
83
+ });
82
84
  }
83
85
  catch (error) {
84
- console.log('Error deleting meta org:', error);
86
+ console.log("Error deleting meta org:", error);
85
87
  }
86
88
  }
87
89
  async function setupTestUsers() {
@@ -97,19 +99,19 @@ async function setupTestUsers() {
97
99
  }
98
100
  async function createTestUsers() {
99
101
  if (!authService || !organizationService || !personService) {
100
- throw new Error('Database not initialized. Call initialize() first.');
102
+ throw new Error("Database not initialized. Call initialize() first.");
101
103
  }
102
104
  try {
103
105
  const existingMetaOrg = await organizationService.getMetaOrg(EmptyUserContext);
104
106
  if (!existingMetaOrg) {
105
- throw new Error('Meta organization does not exist. Test setup is incorrect - meta org should be created by migrations or createMetaOrg().');
107
+ throw new Error("Meta organization does not exist. Test setup is incorrect - meta org should be created by migrations or createMetaOrg().");
106
108
  }
107
109
  setTestMetaOrgId(existingMetaOrg._id);
108
110
  const existingTestOrg = await organizationService.findOne(getTestMetaOrgUserContext(), { filters: { _id: { eq: getTestOrg()._id } } });
109
111
  if (!existingTestOrg) {
110
112
  const createdTestOrg = await organizationService.create(getTestMetaOrgUserContext(), getTestOrg());
111
113
  if (!createdTestOrg) {
112
- throw new Error('Failed to create test organization');
114
+ throw new Error("Failed to create test organization");
113
115
  }
114
116
  setTestOrgId(createdTestOrg._id);
115
117
  }
@@ -118,25 +120,25 @@ async function createTestUsers() {
118
120
  }
119
121
  const createdTestOrgUserPerson = await personService.create(getTestOrgUserContext(), getTestOrgUserPerson());
120
122
  if (!createdTestOrgUserPerson) {
121
- throw new Error('Failed to create test organization user person');
123
+ throw new Error("Failed to create test organization user person");
122
124
  }
123
125
  setTestOrgUserPersonId(createdTestOrgUserPerson._id);
124
126
  const createdMetaOrgUserPerson = await personService.create(getTestMetaOrgUserContext(), getTestMetaOrgUserPerson());
125
127
  if (!createdMetaOrgUserPerson) {
126
- throw new Error('Failed to create meta organization user person');
128
+ throw new Error("Failed to create meta organization user person");
127
129
  }
128
130
  setTestMetaOrgUserPersonId(createdMetaOrgUserPerson._id);
129
131
  const createdTestOrgUser = await authService.createUser(getTestOrgUserContext(), getTestOrgUser());
130
132
  const createdMetaOrgUser = await authService.createUser(getTestMetaOrgUserContext(), getTestMetaOrgUser());
131
133
  if (!createdTestOrgUser || !createdMetaOrgUser) {
132
- throw new Error('Failed to create test user');
134
+ throw new Error("Failed to create test user");
133
135
  }
134
136
  setTestMetaOrgUserId(createdMetaOrgUser._id);
135
137
  setTestOrgUserId(createdTestOrgUser._id);
136
138
  return { metaOrgUser: createdMetaOrgUser, testOrgUser: createdTestOrgUser };
137
139
  }
138
140
  catch (error) {
139
- console.log('Error in createTestUser:', error);
141
+ console.log("Error in createTestUser:", error);
140
142
  throw error;
141
143
  }
142
144
  }
@@ -144,34 +146,38 @@ async function deleteTestUser() {
144
146
  if (!authService || !organizationService) {
145
147
  return;
146
148
  }
147
- await authService.deleteById(getTestMetaOrgUserContext(), getTestMetaOrgUser()._id).catch((error) => {
149
+ await authService
150
+ .deleteById(getTestMetaOrgUserContext(), getTestMetaOrgUser()._id)
151
+ .catch((error) => {
148
152
  return null;
149
153
  });
150
- await organizationService.deleteById(getTestMetaOrgUserContext(), getTestOrg()._id).catch((error) => {
154
+ await organizationService
155
+ .deleteById(getTestMetaOrgUserContext(), getTestOrg()._id)
156
+ .catch((error) => {
151
157
  return null;
152
158
  });
153
159
  }
154
160
  async function simulateloginWithTestUser() {
155
161
  const req = {
156
- cookies: {}
162
+ cookies: {},
157
163
  };
158
164
  if (deviceIdCookie) {
159
- req.cookies['deviceId'] = deviceIdCookie;
165
+ req.cookies["deviceId"] = deviceIdCookie;
160
166
  }
161
167
  const res = {
162
168
  cookie: function (name, value) {
163
- if (name === 'deviceId') {
169
+ if (name === "deviceId") {
164
170
  deviceIdCookie = value;
165
171
  }
166
172
  return res;
167
- }
173
+ },
168
174
  };
169
175
  if (!authService) {
170
- throw new Error('AuthService not initialized. Call initialize() first.');
176
+ throw new Error("AuthService not initialized. Call initialize() first.");
171
177
  }
172
178
  const loginResponse = await authService.attemptLogin(req, res, getTestMetaOrgUser().email, testObjectsModule.TEST_META_ORG_USER_PASSWORD);
173
179
  if (!loginResponse?.tokens?.accessToken) {
174
- throw new Error('Failed to login with test user');
180
+ throw new Error("Failed to login with test user");
175
181
  }
176
182
  return `Bearer ${loginResponse.tokens.accessToken}`;
177
183
  }
@@ -185,13 +191,13 @@ function verifyToken(token) {
185
191
  }
186
192
  export class CategoryService extends GenericApiService {
187
193
  constructor(database) {
188
- super(database, 'categories', 'category', CategorySpec);
194
+ super(database, "categories", "category", CategorySpec);
189
195
  }
190
196
  }
191
197
  export class CategoryController extends ApiController {
192
198
  constructor(app, database) {
193
199
  const categoryService = new CategoryService(database);
194
- super('categories', app, categoryService, 'category', CategorySpec);
200
+ super("categories", app, categoryService, "category", CategorySpec);
195
201
  }
196
202
  }
197
203
  export function setupTestConfig(isMultiTenant = true, dbType) {
@@ -199,37 +205,37 @@ export function setupTestConfig(isMultiTenant = true, dbType) {
199
205
  app: {
200
206
  isMultiTenant: isMultiTenant,
201
207
  isAuthEnabled: true,
202
- name: 'test-app',
208
+ name: "test-app",
203
209
  dbType: dbType,
204
210
  },
205
211
  auth: {
206
- clientSecret: 'test-secret',
212
+ clientSecret: "test-secret",
207
213
  saltWorkFactor: 10,
208
214
  jwtExpirationInSeconds: 3600,
209
215
  refreshTokenExpirationInDays: 7,
210
216
  deviceIdCookieMaxAgeInDays: 730,
211
- passwordResetTokenExpirationInMinutes: 20
217
+ passwordResetTokenExpirationInMinutes: 20,
212
218
  },
213
219
  database: {
214
- name: 'test-db',
215
- host: 'localhost',
216
- password: 'test-password',
220
+ name: "test-db",
221
+ host: "localhost",
222
+ password: "test-password",
217
223
  port: 27017,
218
- username: 'test-user'
224
+ username: "test-user",
219
225
  },
220
- env: 'test',
226
+ env: "test",
221
227
  email: {
222
- fromAddress: 'test@test.com',
223
- systemEmailAddress: 'system@test.com'
228
+ fromAddress: "test@test.com",
229
+ systemEmailAddress: "system@test.com",
224
230
  },
225
231
  thirdPartyClients: {
226
- emailClient: new TestEmailClient()
232
+ emailClient: new TestEmailClient(),
227
233
  },
228
234
  network: {
229
- hostName: 'localhost',
235
+ hostName: "localhost",
230
236
  internalPort: 8083,
231
237
  externalPort: 4000,
232
- corsAllowedOrigins: ['*']
238
+ corsAllowedOrigins: ["*"],
233
239
  },
234
240
  });
235
241
  }
@@ -238,54 +244,54 @@ const prepareQueryCustom = (userContext, queryObject, operations) => {
238
244
  queryObject: queryObject,
239
245
  operations: [
240
246
  ...operations,
241
- new LeftJoin('categories', 'category_id', '_id', 'category')
242
- ]
247
+ new LeftJoin("categories", "category_id", "_id", "category"),
248
+ ],
243
249
  };
244
250
  };
245
251
  const postProcessEntityCustom = (userContext, entity) => {
246
252
  return {
247
253
  ...entity,
248
- category: entity._joinData?.category
254
+ category: entity._joinData?.category,
249
255
  };
250
256
  };
251
257
  export class ProductService extends GenericApiService {
252
258
  constructor(database) {
253
- super(database, 'products', 'product', ProductSpec);
259
+ super(database, "products", "product", ProductSpec);
254
260
  }
255
261
  }
256
262
  export class ProductsController extends ApiController {
257
263
  constructor(app, database) {
258
264
  const productService = new ProductService(database);
259
- super('products', app, productService, 'product', ProductSpec);
265
+ super("products", app, productService, "product", ProductSpec);
260
266
  }
261
267
  async get(req, res, next) {
262
- res.set('Content-Type', 'application/json');
268
+ res.set("Content-Type", "application/json");
263
269
  const userContext = req.userContext;
264
270
  if (!userContext) {
265
- throw new Error('User context not found');
271
+ throw new Error("User context not found");
266
272
  }
267
273
  const queryOptions = apiUtils.getQueryOptionsFromRequest(req);
268
274
  const pagedResult = await this.service.get(userContext, queryOptions, prepareQueryCustom, postProcessEntityCustom);
269
275
  apiUtils.apiResponse(res, 200, { data: pagedResult }, ProductWithCategorySpec, ProductWithCategoryPublicSpec);
270
276
  }
271
277
  async getAll(req, res, next) {
272
- res.set('Content-Type', 'application/json');
278
+ res.set("Content-Type", "application/json");
273
279
  const userContext = req.userContext;
274
280
  if (!userContext) {
275
- throw new Error('User context not found');
281
+ throw new Error("User context not found");
276
282
  }
277
283
  const entities = await this.service.getAll(userContext, prepareQueryCustom, postProcessEntityCustom);
278
284
  apiUtils.apiResponse(res, 200, { data: entities }, ProductWithCategorySpec, ProductWithCategoryPublicSpec);
279
285
  }
280
286
  async getById(req, res, next) {
281
- res.set('Content-Type', 'application/json');
287
+ res.set("Content-Type", "application/json");
282
288
  const userContext = req.userContext;
283
289
  if (!userContext) {
284
- throw new Error('User context not found');
290
+ throw new Error("User context not found");
285
291
  }
286
292
  const idParam = req.params?.id;
287
293
  if (!idParam) {
288
- throw new Error('ID parameter is required');
294
+ throw new Error("ID parameter is required");
289
295
  }
290
296
  try {
291
297
  const id = Value.Convert(this.idSchema, idParam);
@@ -300,13 +306,13 @@ export class ProductsController extends ApiController {
300
306
  export class MultiTenantProductService extends MultiTenantApiService {
301
307
  db;
302
308
  constructor(database) {
303
- super(database, 'products', 'product', ProductSpec);
309
+ super(database, "products", "product", ProductSpec);
304
310
  this.db = database;
305
311
  }
306
312
  prepareQuery(userContext, queryObject, operations) {
307
313
  const newOperations = [
308
314
  ...operations,
309
- new LeftJoin('categories', 'categoryId', '_id', 'category')
315
+ new LeftJoin("categories", "categoryId", "_id", "category"),
310
316
  ];
311
317
  return super.prepareQuery(userContext, queryObject, newOperations);
312
318
  }
@@ -324,12 +330,14 @@ export class MultiTenantProductsController extends ApiController {
324
330
  const AggregatedProductSchema = Type.Intersect([
325
331
  ProductSpec.fullSchema,
326
332
  Type.Partial(Type.Object({
327
- category: CategorySpec.fullSchema
328
- }))
333
+ category: CategorySpec.fullSchema,
334
+ })),
335
+ ]);
336
+ const PublicAggregatedProductSchema = Type.Omit(AggregatedProductSchema, [
337
+ "internalNumber",
329
338
  ]);
330
- const PublicAggregatedProductSchema = Type.Omit(AggregatedProductSchema, ['internalNumber']);
331
339
  const PublicAggregatedProductSpec = entityUtils.getModelSpec(PublicAggregatedProductSchema);
332
- super('multi-tenant-products', app, productService, 'product', ProductSpec, PublicAggregatedProductSpec);
340
+ super("multi-tenant-products", app, productService, "product", ProductSpec, PublicAggregatedProductSpec);
333
341
  }
334
342
  }
335
343
  function configureJwtSecret() {
@@ -339,17 +347,15 @@ function configureJwtSecret() {
339
347
  };
340
348
  }
341
349
  async function loginWithTestUser(agent) {
342
- agent.set('Cookie', [`deviceId=${deviceIdCookie}`]);
350
+ agent.set("Cookie", [`deviceId=${deviceIdCookie}`]);
343
351
  const testUser = getTestMetaOrgUser();
344
- const response = await agent
345
- .post('/api/auth/login')
346
- .send({
352
+ const response = await agent.post("/api/auth/login").send({
347
353
  email: testUser.email,
348
354
  password: testUser.password,
349
355
  });
350
356
  if (!response.body?.data?.tokens?.accessToken) {
351
- console.error('Login failed:', response.body);
352
- throw new Error('Failed to login with test user');
357
+ console.error("Login failed:", response.body);
358
+ throw new Error("Failed to login with test user");
353
359
  }
354
360
  const authorizationHeaderValue = `Bearer ${response.body?.data?.tokens?.accessToken}`;
355
361
  return authorizationHeaderValue;
@@ -360,7 +366,7 @@ async function cleanup() {
360
366
  await deleteMetaOrg();
361
367
  }
362
368
  catch (error) {
363
- console.log('Error during cleanup:', error);
369
+ console.log("Error during cleanup:", error);
364
370
  }
365
371
  }
366
372
  const testUtils = {
@@ -382,6 +388,6 @@ const testUtils = {
382
388
  verifyToken,
383
389
  isMongoDatabase,
384
390
  isPostgresDatabase,
385
- getExpectedIdType
391
+ getExpectedIdType,
386
392
  };
387
393
  export default testUtils;
@@ -1,5 +1,5 @@
1
- import { ITestDatabase } from './test-database.interface.js';
2
- import { IDatabase } from '../databases/models/index.js';
1
+ import type { IDatabase } from "../databases/models/index.js";
2
+ import type { ITestDatabase } from "./test-database.interface.js";
3
3
  export declare class TestPostgresDatabase implements ITestDatabase {
4
4
  private database;
5
5
  private postgresClient;
@@ -1,10 +1,10 @@
1
- import testUtils from './common-test.utils.js';
1
+ import { Pool } from "pg";
2
2
  import { newDb } from "pg-mem";
3
- import { Pool } from 'pg';
4
- import { PostgresDatabase } from '../databases/postgres/postgres.database.js';
5
- import { config } from '../config/base-api-config.js';
6
- import { runInitialSchemaMigrations, runTestSchemaMigrations } from '../databases/postgres/migrations/__tests__/test-migration-helper.js';
7
- const USE_REAL_POSTGRES = process.env.USE_REAL_POSTGRES === 'true';
3
+ import { config } from "../config/base-api-config.js";
4
+ import { runInitialSchemaMigrations, runTestSchemaMigrations, } from "../databases/postgres/migrations/__tests__/test-migration-helper.js";
5
+ import { PostgresDatabase } from "../databases/postgres/postgres.database.js";
6
+ import testUtils from "./common-test.utils.js";
7
+ const USE_REAL_POSTGRES = process.env.USE_REAL_POSTGRES === "true";
8
8
  export class TestPostgresDatabase {
9
9
  database = null;
10
10
  postgresClient = null;
@@ -18,7 +18,7 @@ export class TestPostgresDatabase {
18
18
  return this.initPromise;
19
19
  }
20
20
  getRandomId() {
21
- throw new Error('getRandomId() should not be used for PostgreSQL _id fields. PostgreSQL uses auto-generated integer IDs. Remove _id from entities and let the database generate it.');
21
+ throw new Error("getRandomId() should not be used for PostgreSQL _id fields. PostgreSQL uses auto-generated integer IDs. Remove _id from entities and let the database generate it.");
22
22
  }
23
23
  async _performInit() {
24
24
  if (!this.database) {
@@ -32,13 +32,14 @@ export class TestPostgresDatabase {
32
32
  });
33
33
  this.migrationPool = pool;
34
34
  try {
35
- await pool.query('SELECT now(), current_database()');
35
+ await pool.query("SELECT now(), current_database()");
36
36
  }
37
37
  catch (error) {
38
38
  await pool.end();
39
39
  this.migrationPool = null;
40
40
  const errorMessage = error.message || String(error);
41
- const isPermissionError = errorMessage.includes('permission denied') || errorMessage.includes('operation not permitted');
41
+ const isPermissionError = errorMessage.includes("permission denied") ||
42
+ errorMessage.includes("operation not permitted");
42
43
  if (isPermissionError) {
43
44
  throw new Error(`Docker permission error. Please ensure:\n` +
44
45
  `1. Docker Desktop is running\n` +
@@ -64,9 +65,9 @@ export class TestPostgresDatabase {
64
65
  }
65
66
  this.database = new PostgresDatabase(connection);
66
67
  this.postgresClient = connection;
67
- const { initializeSystemUserContext, isSystemUserContextInitialized } = await import('@loomcore/common/models');
68
+ const { initializeSystemUserContext, isSystemUserContextInitialized } = await import("@loomcore/common/models");
68
69
  if (!isSystemUserContextInitialized()) {
69
- initializeSystemUserContext(config.email?.systemEmailAddress || 'system@test.com', undefined);
70
+ initializeSystemUserContext(config.email?.systemEmailAddress || "system@test.com", undefined);
70
71
  }
71
72
  await runInitialSchemaMigrations(pool, config);
72
73
  await runTestSchemaMigrations(pool, config);
@@ -84,12 +85,12 @@ export class TestPostgresDatabase {
84
85
  `);
85
86
  }
86
87
  catch (error) {
87
- console.log('Note: Indexes may be created later when tables are initialized:', error.message);
88
+ console.log("Note: Indexes may be created later when tables are initialized:", error.message);
88
89
  }
89
90
  }
90
91
  async clearCollections() {
91
92
  if (!this.postgresClient) {
92
- throw new Error('Database not initialized');
93
+ throw new Error("Database not initialized");
93
94
  }
94
95
  const result = await this.postgresClient.query(`
95
96
  SELECT "table_name"
@@ -98,7 +99,7 @@ export class TestPostgresDatabase {
98
99
  AND "table_type" = 'BASE TABLE'
99
100
  `);
100
101
  if (USE_REAL_POSTGRES) {
101
- await this.postgresClient.query(`TRUNCATE TABLE ${result.rows.map(row => `"${row.table_name}"`).join(', ')} RESTART IDENTITY CASCADE`);
102
+ await this.postgresClient.query(`TRUNCATE TABLE ${result.rows.map((row) => `"${row.table_name}"`).join(", ")} RESTART IDENTITY CASCADE`);
102
103
  }
103
104
  else {
104
105
  result.rows.forEach(async (row) => {
@@ -120,7 +121,7 @@ export class TestPostgresDatabase {
120
121
  }
121
122
  }
122
123
  catch (error) {
123
- console.warn('Error closing PostgreSQL connection:', error);
124
+ console.warn("Error closing PostgreSQL connection:", error);
124
125
  }
125
126
  }
126
127
  this.migrationPool = null;
@@ -1,5 +1,5 @@
1
- import { IBaseApiConfig } from '../models/index.js';
2
- import { IDatabase } from '../databases/models/index.js';
1
+ import { IDatabase } from "../databases/models/index.js";
2
+ import { IBaseApiConfig } from "../models/index.js";
3
3
  export declare let config: IBaseApiConfig;
4
4
  export declare function setBaseApiConfig(theConfig: IBaseApiConfig): void;
5
5
  export declare function initSystemUserContext(database: IDatabase): Promise<void>;
@@ -1,9 +1,16 @@
1
- import { EmptyUserContext, initializeSystemUserContext } from '@loomcore/common/models';
1
+ import { EmptyUserContext, initializeSystemUserContext, } from "@loomcore/common/models";
2
2
  export let config;
3
3
  let isConfigSet = false;
4
4
  let isSystemUserContextSet = false;
5
5
  const BASE_API_CONFIG_KEYS = [
6
- 'app', 'auth', 'database', 'debug', 'email', 'env', 'network', 'thirdPartyClients'
6
+ "app",
7
+ "auth",
8
+ "database",
9
+ "debug",
10
+ "email",
11
+ "env",
12
+ "network",
13
+ "thirdPartyClients",
7
14
  ];
8
15
  function copyOnlySpecifiedConfigProperties(obj, allowedKeys) {
9
16
  const result = {};
@@ -19,29 +26,29 @@ export function setBaseApiConfig(theConfig) {
19
26
  config = copyOnlySpecifiedConfigProperties(theConfig, BASE_API_CONFIG_KEYS);
20
27
  isConfigSet = true;
21
28
  }
22
- else if (config.env !== 'test') {
23
- console.warn('BaseApiConfig data has already been set. Ignoring subsequent calls to setBaseApiConfig.');
29
+ else if (config.env !== "test") {
30
+ console.warn("BaseApiConfig data has already been set. Ignoring subsequent calls to setBaseApiConfig.");
24
31
  }
25
32
  }
26
33
  export async function initSystemUserContext(database) {
27
34
  if (!isConfigSet) {
28
- throw new Error('BaseApiConfig has not been set. Call setBaseApiConfig first.');
35
+ throw new Error("BaseApiConfig has not been set. Call setBaseApiConfig first.");
29
36
  }
30
37
  if (!isSystemUserContextSet) {
31
- const systemEmail = config.email?.systemEmailAddress || 'system@example.com';
38
+ const systemEmail = config.email?.systemEmailAddress || "system@example.com";
32
39
  let metaOrg = undefined;
33
40
  if (config.app.isMultiTenant) {
34
- const { OrganizationService } = await import('../services/organization.service.js');
41
+ const { OrganizationService } = await import("../services/organization.service.js");
35
42
  const organizationService = new OrganizationService(database);
36
43
  metaOrg = await organizationService.getMetaOrg(EmptyUserContext);
37
44
  if (!metaOrg) {
38
- throw new Error('Meta organization not found. Please create an organization with isMetaOrg=true before starting the API.');
45
+ throw new Error("Meta organization not found. Please create an organization with isMetaOrg=true before starting the API.");
39
46
  }
40
47
  }
41
48
  initializeSystemUserContext(systemEmail, metaOrg);
42
49
  isSystemUserContextSet = true;
43
50
  }
44
- else if (config.env !== 'test') {
45
- console.warn('SystemUserContext has already been set. Ignoring subsequent calls to initSystemUserContext.');
51
+ else if (config.env !== "test") {
52
+ console.warn("SystemUserContext has already been set. Ignoring subsequent calls to initSystemUserContext.");
46
53
  }
47
54
  }
@@ -1,5 +1,5 @@
1
- import { Db } from 'mongodb';
2
- import { IInitialDbMigrationConfig } from '../../../models/initial-database-config.interface.js';
1
+ import { Db } from "mongodb";
2
+ import { IInitialDbMigrationConfig } from "../../../models/initial-database-config.interface.js";
3
3
  export interface ISyntheticMigration {
4
4
  name: string;
5
5
  up: (context: {