@loomcore/api 0.1.86 → 0.1.88

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.
@@ -200,10 +200,6 @@ export function setupTestConfig(isMultiTenant = true, dbType) {
200
200
  dbType: dbType,
201
201
  },
202
202
  auth: {
203
- adminUser: {
204
- email: 'admin@test.com',
205
- password: 'admin-password'
206
- },
207
203
  clientSecret: 'test-secret',
208
204
  saltWorkFactor: 10,
209
205
  jwtExpirationInSeconds: 3600,
@@ -5,7 +5,7 @@ export declare class TestPostgresDatabase implements ITestDatabase {
5
5
  private postgresClient;
6
6
  private postgresPool;
7
7
  private initPromise;
8
- init(adminUsername?: string, adminPassword?: string): Promise<IDatabase>;
8
+ init(): Promise<IDatabase>;
9
9
  getRandomId(): string;
10
10
  private _performInit;
11
11
  private createIndexes;
@@ -10,17 +10,17 @@ export class TestPostgresDatabase {
10
10
  postgresClient = null;
11
11
  postgresPool = null;
12
12
  initPromise = null;
13
- async init(adminUsername, adminPassword) {
13
+ async init() {
14
14
  if (this.initPromise) {
15
15
  return this.initPromise;
16
16
  }
17
- this.initPromise = this._performInit(adminUsername, adminPassword);
17
+ this.initPromise = this._performInit();
18
18
  return this.initPromise;
19
19
  }
20
20
  getRandomId() {
21
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
- async _performInit(adminUsername, adminPassword) {
23
+ async _performInit() {
24
24
  if (!this.database) {
25
25
  let postgresClient;
26
26
  let pool;
@@ -1,6 +1,6 @@
1
1
  import { IDatabase } from "../databases/models/index.js";
2
2
  export type ITestDatabase = {
3
- init(adminUsername?: string, adminPassword?: string): Promise<IDatabase>;
3
+ init(): Promise<IDatabase>;
4
4
  getRandomId(): string;
5
5
  clearCollections(): Promise<void>;
6
6
  cleanup(): Promise<void>;
@@ -36,7 +36,7 @@ export class TestExpressApp {
36
36
  else {
37
37
  const testPostgresDb = new TestPostgresDatabase();
38
38
  this.testDatabase = testPostgresDb;
39
- this.database = await testPostgresDb.init('admin', 'password');
39
+ this.database = await testPostgresDb.init();
40
40
  }
41
41
  }
42
42
  if (!this.app) {
@@ -1,12 +1,14 @@
1
1
  import { IBaseApiConfig } from '../../models/base-api-config.interface.js';
2
+ import { IResetApiConfig } from '../../models/reset-api-config.interface.js';
2
3
  export declare class MigrationRunner {
3
4
  private config;
5
+ private resetConfig?;
4
6
  private dbType;
5
7
  private dbUrl;
6
8
  private migrationsDir;
7
9
  private primaryTimezone;
8
10
  private dbConnection;
9
- constructor(config: IBaseApiConfig);
11
+ constructor(config: IBaseApiConfig, resetConfig?: IResetApiConfig);
10
12
  private getTimestamp;
11
13
  private parseSql;
12
14
  private getMigrator;
@@ -10,14 +10,16 @@ import { getPostgresInitialSchema } from '../postgres/migrations/postgres-initia
10
10
  import { getMongoInitialSchema } from '../mongo-db/migrations/mongo-initial-schema.js';
11
11
  export class MigrationRunner {
12
12
  config;
13
+ resetConfig;
13
14
  dbType;
14
15
  dbUrl;
15
16
  migrationsDir;
16
17
  primaryTimezone;
17
18
  dbConnection;
18
- constructor(config) {
19
+ constructor(config, resetConfig) {
19
20
  setBaseApiConfig(config);
20
21
  this.config = config;
22
+ this.resetConfig = resetConfig;
21
23
  this.dbType = config.app.dbType;
22
24
  this.dbUrl = this.dbType === 'postgres' ? buildPostgresUrl(config) : buildMongoUrl(config);
23
25
  this.migrationsDir = path.join(process.cwd(), 'database', 'migrations');
@@ -186,6 +188,9 @@ export class MigrationRunner {
186
188
  async run(command = 'up', target) {
187
189
  try {
188
190
  if (command === 'reset') {
191
+ if (!this.resetConfig) {
192
+ throw new Error('Reset configuration not found');
193
+ }
189
194
  await this.wipeDatabase();
190
195
  console.log('🚀 Restarting migrations...');
191
196
  const migrator = await this.getMigrator();
@@ -1,5 +1,6 @@
1
1
  import { Db } from 'mongodb';
2
2
  import { IBaseApiConfig } from '../../../models/base-api-config.interface.js';
3
+ import { IResetApiConfig } from '../../../models/reset-api-config.interface.js';
3
4
  export interface ISyntheticMigration {
4
5
  name: string;
5
6
  up: (context: {
@@ -9,4 +10,4 @@ export interface ISyntheticMigration {
9
10
  context: Db;
10
11
  }) => Promise<void>;
11
12
  }
12
- export declare const getMongoInitialSchema: (config: IBaseApiConfig) => ISyntheticMigration[];
13
+ export declare const getMongoInitialSchema: (config: IBaseApiConfig, resetConfig?: IResetApiConfig) => ISyntheticMigration[];
@@ -2,7 +2,7 @@ import { randomUUID } from 'crypto';
2
2
  import { initializeSystemUserContext, EmptyUserContext, getSystemUserContext, isSystemUserContextInitialized, personModelSpec } from '@loomcore/common/models';
3
3
  import { MongoDBDatabase } from '../mongo-db.database.js';
4
4
  import { AuthService, GenericApiService, OrganizationService } from '../../../services/index.js';
5
- export const getMongoInitialSchema = (config) => {
5
+ export const getMongoInitialSchema = (config, resetConfig) => {
6
6
  const migrations = [];
7
7
  const isMultiTenant = config.app.isMultiTenant;
8
8
  if (isMultiTenant && !config.multiTenant) {
@@ -161,7 +161,7 @@ export const getMongoInitialSchema = (config) => {
161
161
  }
162
162
  });
163
163
  }
164
- if (isAuthEnabled) {
164
+ if (isAuthEnabled && resetConfig) {
165
165
  migrations.push({
166
166
  name: '00000000000009_data-admin-user',
167
167
  up: async ({ context: db }) => {
@@ -198,19 +198,19 @@ export const getMongoInitialSchema = (config) => {
198
198
  _id: _id,
199
199
  _orgId: systemUserContext.organization?._id,
200
200
  externalId: 'admin-user-external-id',
201
- email: config.auth?.adminUser?.email,
202
- password: config.auth?.adminUser?.password,
201
+ email: resetConfig.adminUser.email,
202
+ password: resetConfig.adminUser.password,
203
203
  displayName: 'Admin User',
204
204
  }, person);
205
205
  },
206
206
  down: async ({ context: db }) => {
207
- if (!config.auth?.adminUser?.email)
207
+ if (!resetConfig?.adminUser?.email)
208
208
  return;
209
- await db.collection('users').deleteOne({ email: config.auth?.adminUser?.email });
209
+ await db.collection('users').deleteOne({ email: resetConfig.adminUser.email });
210
210
  }
211
211
  });
212
212
  }
213
- if (config.auth?.adminUser?.email && isMultiTenant) {
213
+ if (resetConfig?.adminUser?.email && isMultiTenant) {
214
214
  migrations.push({
215
215
  name: '00000000000010_data-admin-authorizations',
216
216
  up: async ({ context: db }) => {
@@ -221,7 +221,7 @@ export const getMongoInitialSchema = (config) => {
221
221
  if (!metaOrg) {
222
222
  throw new Error('Meta organization not found. Ensure meta-org migration ran successfully.');
223
223
  }
224
- const adminUser = await authService.getUserByEmail(config.auth.adminUser.email);
224
+ const adminUser = await authService.getUserByEmail(resetConfig.adminUser.email);
225
225
  if (!adminUser) {
226
226
  throw new Error('Admin user not found. Ensure admin-user migration ran successfully.');
227
227
  }
@@ -3,10 +3,7 @@ import { buildJoinClauses } from '../utils/build-join-clauses.js';
3
3
  import { columnsAndValuesFromEntity } from '../utils/columns-and-values-from-entity.js';
4
4
  export async function partialUpdateById(client, operations, id, entity, pluralResourceName) {
5
5
  try {
6
- console.log('entity', JSON.stringify(entity, null, 2));
7
6
  const { columns, values } = columnsAndValuesFromEntity(entity);
8
- console.log('columns', JSON.stringify(columns, null, 2));
9
- console.log('values', JSON.stringify(values, null, 2));
10
7
  const updateColumns = columns.filter(col => col !== '"_id"');
11
8
  const updateValues = values.filter((_, index) => columns[index] !== '"_id"');
12
9
  if (updateColumns.length === 0) {
@@ -2,7 +2,7 @@ import { Umzug } from 'umzug';
2
2
  import { getPostgresInitialSchema } from '../postgres-initial-schema.js';
3
3
  import { getPostgresTestSchema } from '../../../../__tests__/postgres-test-migrations/postgres-test-schema.js';
4
4
  export async function runInitialSchemaMigrations(pool, config) {
5
- const initialSchema = getPostgresInitialSchema(config);
5
+ const initialSchema = getPostgresInitialSchema(config, { adminUser: { email: 'admin@test.com', password: 'admin-password' } });
6
6
  const umzug = new Umzug({
7
7
  migrations: async () => {
8
8
  return initialSchema.map(m => ({
@@ -1,5 +1,6 @@
1
1
  import { Pool } from 'pg';
2
2
  import { IBaseApiConfig } from '../../../models/base-api-config.interface.js';
3
+ import { IResetApiConfig } from '../../../models/reset-api-config.interface.js';
3
4
  export interface SyntheticMigration {
4
5
  name: string;
5
6
  up: (context: {
@@ -9,4 +10,4 @@ export interface SyntheticMigration {
9
10
  context: Pool;
10
11
  }) => Promise<void>;
11
12
  }
12
- export declare const getPostgresInitialSchema: (config: IBaseApiConfig) => SyntheticMigration[];
13
+ export declare const getPostgresInitialSchema: (config: IBaseApiConfig, resetConfig?: IResetApiConfig) => SyntheticMigration[];
@@ -1,7 +1,7 @@
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, resetConfig) => {
5
5
  const migrations = [];
6
6
  const isMultiTenant = config.app.isMultiTenant;
7
7
  if (isMultiTenant && !config.multiTenant) {
@@ -285,7 +285,7 @@ export const getPostgresInitialSchema = (config) => {
285
285
  }
286
286
  });
287
287
  }
288
- if (isAuthEnabled) {
288
+ if (isAuthEnabled && resetConfig?.adminUser) {
289
289
  migrations.push({
290
290
  name: '00000000000011_data-admin-user',
291
291
  up: async ({ context: pool }) => {
@@ -304,8 +304,8 @@ export const getPostgresInitialSchema = (config) => {
304
304
  }
305
305
  const systemUserContext = getSystemUserContext();
306
306
  const userData = {
307
- email: config.auth?.adminUser?.email,
308
- password: config.auth?.adminUser?.password,
307
+ email: resetConfig.adminUser.email,
308
+ password: resetConfig.adminUser.password,
309
309
  displayName: 'Admin User',
310
310
  };
311
311
  if (isMultiTenant && systemUserContext.organization?._id) {
@@ -325,13 +325,13 @@ export const getPostgresInitialSchema = (config) => {
325
325
  }
326
326
  },
327
327
  down: async ({ context: pool }) => {
328
- if (!config.auth?.adminUser?.email)
328
+ if (!resetConfig?.adminUser?.email)
329
329
  return;
330
- const result = await pool.query(`DELETE FROM "users" WHERE "email" = $1`, [config.auth?.adminUser?.email]);
330
+ const result = await pool.query(`DELETE FROM "users" WHERE "email" = $1`, [resetConfig.adminUser.email]);
331
331
  }
332
332
  });
333
333
  }
334
- if (config.auth) {
334
+ if (config.auth && resetConfig) {
335
335
  migrations.push({
336
336
  name: '00000000000012_data-admin-authorizations',
337
337
  up: async ({ context: pool }) => {
@@ -344,7 +344,7 @@ export const getPostgresInitialSchema = (config) => {
344
344
  if (isMultiTenant && !metaOrg) {
345
345
  throw new Error('Meta organization not found. Ensure meta-org migration ran successfully.');
346
346
  }
347
- const adminUser = await authService.getUserByEmail(config.auth.adminUser.email);
347
+ const adminUser = await authService.getUserByEmail(resetConfig.adminUser.email);
348
348
  if (!adminUser) {
349
349
  throw new Error('Admin user not found. Ensure admin-user migration ran successfully.');
350
350
  }
@@ -1,8 +1,4 @@
1
1
  export interface IAuthConfig {
2
- adminUser: {
3
- email: string;
4
- password: string;
5
- };
6
2
  clientSecret: string;
7
3
  saltWorkFactor: number;
8
4
  deviceIdCookieMaxAgeInDays: number;
@@ -1,3 +1,4 @@
1
1
  export * from './base-api-config.interface.js';
2
+ export * from './reset-api-config.interface.js';
2
3
  export * from './refresh-token.model.js';
3
4
  export * from './email-client.interface.js';
@@ -1,3 +1,4 @@
1
1
  export * from './base-api-config.interface.js';
2
+ export * from './reset-api-config.interface.js';
2
3
  export * from './refresh-token.model.js';
3
4
  export * from './email-client.interface.js';
@@ -0,0 +1,6 @@
1
+ export interface IResetApiConfig {
2
+ adminUser: {
3
+ email: string;
4
+ password: string;
5
+ };
6
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.1.86",
3
+ "version": "0.1.88",
4
4
  "private": false,
5
5
  "description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb or PostgreSQL",
6
6
  "scripts": {
@@ -57,7 +57,7 @@
57
57
  "qs": "^6.14.1"
58
58
  },
59
59
  "peerDependencies": {
60
- "@loomcore/common": "^0.0.49",
60
+ "@loomcore/common": "^0.0.50",
61
61
  "@sinclair/typebox": "0.34.33",
62
62
  "cookie-parser": "^1.4.6",
63
63
  "cors": "^2.8.5",