@loomcore/api 0.1.91 → 0.1.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +77 -77
  3. package/dist/__tests__/common-test.utils.js +0 -4
  4. package/dist/__tests__/postgres-test-migrations/postgres-test-schema.js +266 -266
  5. package/dist/__tests__/postgres.test-database.js +8 -8
  6. package/dist/config/base-api-config.d.ts +1 -1
  7. package/dist/config/base-api-config.js +12 -7
  8. package/dist/databases/migrations/migration-runner.d.ts +3 -5
  9. package/dist/databases/migrations/migration-runner.js +40 -44
  10. package/dist/databases/mongo-db/migrations/mongo-initial-schema.d.ts +2 -3
  11. package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +102 -81
  12. package/dist/databases/mongo-db/utils/build-mongo-url.util.d.ts +4 -2
  13. package/dist/databases/mongo-db/utils/convert-operations-to-pipeline.util.js +141 -39
  14. package/dist/databases/postgres/commands/postgres-batch-update.command.js +7 -7
  15. package/dist/databases/postgres/commands/postgres-create-many.command.js +4 -4
  16. package/dist/databases/postgres/commands/postgres-create.command.js +4 -4
  17. package/dist/databases/postgres/commands/postgres-full-update-by-id.command.js +13 -13
  18. package/dist/databases/postgres/commands/postgres-partial-update-by-id.command.js +7 -7
  19. package/dist/databases/postgres/commands/postgres-update.command.js +7 -7
  20. package/dist/databases/postgres/migrations/__tests__/test-migration-helper.js +8 -1
  21. package/dist/databases/postgres/migrations/postgres-initial-schema.d.ts +2 -3
  22. package/dist/databases/postgres/migrations/postgres-initial-schema.js +263 -266
  23. package/dist/databases/postgres/postgres.database.js +17 -17
  24. package/dist/databases/postgres/utils/build-join-clauses.js +151 -151
  25. package/dist/databases/postgres/utils/build-postgres-url.util.d.ts +4 -2
  26. package/dist/databases/postgres/utils/build-select-clause.js +6 -6
  27. package/dist/databases/postgres/utils/does-table-exist.util.js +4 -4
  28. package/dist/models/app-config.interface.d.ts +8 -0
  29. package/dist/models/base-api-config.interface.d.ts +4 -17
  30. package/dist/models/database-config.interface.d.ts +7 -0
  31. package/dist/models/index.d.ts +3 -1
  32. package/dist/models/index.js +3 -1
  33. package/dist/models/initial-database-config.interface.d.ts +16 -0
  34. package/dist/models/initial-database-config.interface.js +1 -0
  35. package/package.json +92 -92
  36. package/dist/models/multi-tenant-config.interface.d.ts +0 -4
  37. package/dist/models/reset-api-config.interface.d.ts +0 -6
  38. /package/dist/models/{multi-tenant-config.interface.js → app-config.interface.js} +0 -0
  39. /package/dist/models/{reset-api-config.interface.js → database-config.interface.js} +0 -0
@@ -78,9 +78,9 @@ export class TestPostgresDatabase {
78
78
  }
79
79
  async createIndexes(client) {
80
80
  try {
81
- await client.query(`
82
- CREATE INDEX IF NOT EXISTS email_index ON users (LOWER(email));
83
- CREATE UNIQUE INDEX IF NOT EXISTS email_unique_index ON users (LOWER(email));
81
+ await client.query(`
82
+ CREATE INDEX IF NOT EXISTS email_index ON users (LOWER(email));
83
+ CREATE UNIQUE INDEX IF NOT EXISTS email_unique_index ON users (LOWER(email));
84
84
  `);
85
85
  }
86
86
  catch (error) {
@@ -91,11 +91,11 @@ export class TestPostgresDatabase {
91
91
  if (!this.postgresClient) {
92
92
  throw new Error('Database not initialized');
93
93
  }
94
- const result = await this.postgresClient.query(`
95
- SELECT "table_name"
96
- FROM information_schema.tables
97
- WHERE "table_schema" = 'public'
98
- AND "table_type" = 'BASE TABLE'
94
+ const result = await this.postgresClient.query(`
95
+ SELECT "table_name"
96
+ FROM information_schema.tables
97
+ WHERE "table_schema" = 'public'
98
+ AND "table_type" = 'BASE TABLE'
99
99
  `);
100
100
  result.rows.forEach(async (row) => {
101
101
  await this.postgresClient?.query(`TRUNCATE TABLE "${row.table_name}" RESTART IDENTITY CASCADE`);
@@ -1,5 +1,5 @@
1
1
  import { IBaseApiConfig } from '../models/index.js';
2
2
  import { IDatabase } from '../databases/models/index.js';
3
3
  export declare let config: IBaseApiConfig;
4
- export declare function setBaseApiConfig(apiConfig: IBaseApiConfig): void;
4
+ export declare function setBaseApiConfig(theConfig: IBaseApiConfig): void;
5
5
  export declare function initSystemUserContext(database: IDatabase): Promise<void>;
@@ -2,16 +2,21 @@ import { EmptyUserContext, initializeSystemUserContext } from '@loomcore/common/
2
2
  export let config;
3
3
  let isConfigSet = false;
4
4
  let isSystemUserContextSet = false;
5
- function copyOnlyBaseApiConfigProperties(obj) {
6
- const baseConfig = {};
7
- Object.keys(obj).forEach((key) => {
8
- baseConfig[key] = obj[key];
5
+ const BASE_API_CONFIG_KEYS = [
6
+ 'app', 'auth', 'database', 'debug', 'email', 'env', 'network', 'thirdPartyClients'
7
+ ];
8
+ function copyOnlySpecifiedConfigProperties(obj, allowedKeys) {
9
+ const result = {};
10
+ allowedKeys.forEach((key) => {
11
+ if (key in obj) {
12
+ result[key] = obj[key];
13
+ }
9
14
  });
10
- return baseConfig;
15
+ return result;
11
16
  }
12
- export function setBaseApiConfig(apiConfig) {
17
+ export function setBaseApiConfig(theConfig) {
13
18
  if (!isConfigSet) {
14
- config = copyOnlyBaseApiConfigProperties(apiConfig);
19
+ config = copyOnlySpecifiedConfigProperties(theConfig, BASE_API_CONFIG_KEYS);
15
20
  isConfigSet = true;
16
21
  }
17
22
  else if (config.env !== 'test') {
@@ -1,14 +1,12 @@
1
- import { IBaseApiConfig } from '../../models/base-api-config.interface.js';
2
- import { IResetApiConfig } from '../../models/reset-api-config.interface.js';
1
+ import { IInitialDbMigrationConfig } from '../../models/initial-database-config.interface.js';
3
2
  export declare class MigrationRunner {
4
- private config;
5
- private resetConfig?;
3
+ private dbMigrationConfig;
6
4
  private dbType;
7
5
  private dbUrl;
8
6
  private migrationsDir;
9
7
  private primaryTimezone;
10
8
  private dbConnection;
11
- constructor(config: IBaseApiConfig, resetConfig?: IResetApiConfig);
9
+ constructor(dbMigrationConfig: IInitialDbMigrationConfig, resetConfig?: IInitialDbMigrationConfig);
12
10
  private getTimestamp;
13
11
  private parseSql;
14
12
  private getMigrator;
@@ -1,7 +1,6 @@
1
1
  import { Umzug, MongoDBStorage } from 'umzug';
2
2
  import { Pool } from 'pg';
3
3
  import { MongoClient } from 'mongodb';
4
- import { setBaseApiConfig } from '../../config/index.js';
5
4
  import fs from 'fs';
6
5
  import path from 'path';
7
6
  import { buildMongoUrl } from '../mongo-db/utils/build-mongo-url.util.js';
@@ -9,21 +8,18 @@ import { buildPostgresUrl } from '../postgres/utils/build-postgres-url.util.js';
9
8
  import { getPostgresInitialSchema } from '../postgres/migrations/postgres-initial-schema.js';
10
9
  import { getMongoInitialSchema } from '../mongo-db/migrations/mongo-initial-schema.js';
11
10
  export class MigrationRunner {
12
- config;
13
- resetConfig;
11
+ dbMigrationConfig;
14
12
  dbType;
15
13
  dbUrl;
16
14
  migrationsDir;
17
15
  primaryTimezone;
18
16
  dbConnection;
19
- constructor(config, resetConfig) {
20
- setBaseApiConfig(config);
21
- this.config = config;
22
- this.resetConfig = resetConfig;
23
- this.dbType = config.app.dbType;
24
- this.dbUrl = this.dbType === 'postgres' ? buildPostgresUrl(config) : buildMongoUrl(config);
17
+ constructor(dbMigrationConfig, resetConfig) {
18
+ this.dbMigrationConfig = dbMigrationConfig;
19
+ this.dbType = dbMigrationConfig.app.dbType;
20
+ this.dbUrl = this.dbType === 'postgres' ? buildPostgresUrl(dbMigrationConfig) : buildMongoUrl(dbMigrationConfig);
25
21
  this.migrationsDir = path.join(process.cwd(), 'database', 'migrations');
26
- this.primaryTimezone = config.app.primaryTimezone || 'UTC';
22
+ this.primaryTimezone = dbMigrationConfig.app.primaryTimezone || 'UTC';
27
23
  }
28
24
  getTimestamp() {
29
25
  const now = new Date();
@@ -57,16 +53,16 @@ export class MigrationRunner {
57
53
  }
58
54
  if (this.dbType === 'postgres') {
59
55
  const pool = new Pool({
60
- host: this.config.database.host,
61
- user: this.config.database.username,
62
- password: this.config.database.password,
63
- port: this.config.database.port,
64
- database: this.config.database.name
56
+ host: this.dbMigrationConfig.database.host,
57
+ user: this.dbMigrationConfig.database.username,
58
+ password: this.dbMigrationConfig.database.password,
59
+ port: this.dbMigrationConfig.database.port,
60
+ database: this.dbMigrationConfig.database.name
65
61
  });
66
62
  this.dbConnection = pool;
67
63
  return new Umzug({
68
64
  migrations: async () => {
69
- const initialSchema = getPostgresInitialSchema(this.config, this.resetConfig).map(m => ({
65
+ const initialSchema = getPostgresInitialSchema(this.dbMigrationConfig).map(m => ({
70
66
  name: m.name,
71
67
  up: async () => {
72
68
  console.log(` Running [LIBRARY] ${m.name}...`);
@@ -105,7 +101,7 @@ export class MigrationRunner {
105
101
  console.log(`🔎 Looking for migrations in: ${globPattern}`);
106
102
  return new Umzug({
107
103
  migrations: async () => {
108
- const initialSchema = getMongoInitialSchema(this.config, this.resetConfig).map(m => ({
104
+ const initialSchema = getMongoInitialSchema(this.dbMigrationConfig).map(m => ({
109
105
  name: m.name,
110
106
  up: async () => {
111
107
  console.log(` Running [LIBRARY] ${m.name}...`);
@@ -165,11 +161,11 @@ export class MigrationRunner {
165
161
  console.log(`⚠️ Wiping ${this.dbType} database...`);
166
162
  if (this.dbType === 'postgres') {
167
163
  const pool = new Pool({
168
- host: this.config.database.host,
169
- user: this.config.database.username,
170
- password: this.config.database.password,
171
- port: this.config.database.port,
172
- database: this.config.database.name
164
+ host: this.dbMigrationConfig.database.host,
165
+ user: this.dbMigrationConfig.database.username,
166
+ password: this.dbMigrationConfig.database.password,
167
+ port: this.dbMigrationConfig.database.port,
168
+ database: this.dbMigrationConfig.database.name
173
169
  });
174
170
  try {
175
171
  await pool.query('DROP SCHEMA public CASCADE; CREATE SCHEMA public;');
@@ -188,7 +184,7 @@ export class MigrationRunner {
188
184
  async run(command = 'up', target) {
189
185
  try {
190
186
  if (command === 'reset') {
191
- if (!this.resetConfig) {
187
+ if (!this.dbMigrationConfig) {
192
188
  throw new Error('Reset configuration not found');
193
189
  }
194
190
  await this.wipeDatabase();
@@ -256,31 +252,31 @@ export class MigrationRunner {
256
252
  let content = '';
257
253
  if (this.dbType === 'postgres') {
258
254
  extension = 'sql';
259
- content = `-- Migration: ${safeName}
260
- -- Created: ${new Date().toISOString()}
261
-
262
- -- up
263
- -- Write your CREATE/ALTER statements here...
264
-
265
-
266
- -- down
267
- -- Write your DROP/UNDO statements here...
255
+ content = `-- Migration: ${safeName}
256
+ -- Created: ${new Date().toISOString()}
257
+
258
+ -- up
259
+ -- Write your CREATE/ALTER statements here...
260
+
261
+
262
+ -- down
263
+ -- Write your DROP/UNDO statements here...
268
264
  `;
269
265
  }
270
266
  else {
271
267
  extension = 'ts';
272
- content = `import { Db } from 'mongodb';
273
-
274
- // Migration: ${safeName}
275
- // Created: ${new Date().toISOString()}
276
-
277
- export const up = async ({ context: db }: { context: Db }) => {
278
- // await db.collection('...')....
279
- };
280
-
281
- export const down = async ({ context: db }: { context: Db }) => {
282
- // await db.collection('...')....
283
- };
268
+ content = `import { Db } from 'mongodb';
269
+
270
+ // Migration: ${safeName}
271
+ // Created: ${new Date().toISOString()}
272
+
273
+ export const up = async ({ context: db }: { context: Db }) => {
274
+ // await db.collection('...')....
275
+ };
276
+
277
+ export const down = async ({ context: db }: { context: Db }) => {
278
+ // await db.collection('...')....
279
+ };
284
280
  `;
285
281
  }
286
282
  const fullPath = path.join(this.migrationsDir, `${filename}.${extension}`);
@@ -1,6 +1,5 @@
1
1
  import { Db } from 'mongodb';
2
- import { IBaseApiConfig } from '../../../models/base-api-config.interface.js';
3
- import { IResetApiConfig } from '../../../models/reset-api-config.interface.js';
2
+ import { IInitialDbMigrationConfig } from '../../../models/initial-database-config.interface.js';
4
3
  export interface ISyntheticMigration {
5
4
  name: string;
6
5
  up: (context: {
@@ -10,4 +9,4 @@ export interface ISyntheticMigration {
10
9
  context: Db;
11
10
  }) => Promise<void>;
12
11
  }
13
- export declare const getMongoInitialSchema: (config: IBaseApiConfig, resetConfig?: IResetApiConfig) => ISyntheticMigration[];
12
+ export declare const getMongoInitialSchema: (dbConfig: IInitialDbMigrationConfig) => ISyntheticMigration[];
@@ -1,20 +1,14 @@
1
- import { randomUUID } from 'crypto';
2
- import { initializeSystemUserContext, EmptyUserContext, getSystemUserContext, isSystemUserContextInitialized, personModelSpec } from '@loomcore/common/models';
1
+ import { initializeSystemUserContext, EmptyUserContext, getSystemUserContext, isSystemUserContextInitialized } from '@loomcore/common/models';
3
2
  import { MongoDBDatabase } from '../mongo-db.database.js';
4
- import { AuthService, GenericApiService, OrganizationService } from '../../../services/index.js';
5
- export const getMongoInitialSchema = (config, resetConfig) => {
3
+ import { OrganizationService } from '../../../services/index.js';
4
+ import { passwordUtils } from '../../../utils/index.js';
5
+ export const getMongoInitialSchema = (dbConfig) => {
6
6
  const migrations = [];
7
- const isMultiTenant = config.app.isMultiTenant;
8
- if (isMultiTenant && !config.multiTenant) {
7
+ const isMultiTenant = dbConfig.app.isMultiTenant;
8
+ if (isMultiTenant && !dbConfig.multiTenant) {
9
9
  throw new Error('Multi-tenant configuration is enabled but multi-tenant configuration is not provided');
10
10
  }
11
- const isAuthEnabled = config.app.isAuthEnabled;
12
- if (isAuthEnabled && !config.auth) {
13
- throw new Error('Auth enabled without auth configuration');
14
- }
15
- if (isAuthEnabled && (!config.thirdPartyClients?.emailClient || !config.email)) {
16
- throw new Error('Auth enabled without email client or email configuration');
17
- }
11
+ const isAuthEnabled = dbConfig.app.isAuthEnabled;
18
12
  if (isMultiTenant)
19
13
  migrations.push({
20
14
  name: '00000000000001_schema-organizations',
@@ -30,10 +24,27 @@ export const getMongoInitialSchema = (config, resetConfig) => {
30
24
  });
31
25
  if (isAuthEnabled)
32
26
  migrations.push({
33
- name: '00000000000002_schema-users',
27
+ name: '00000000000002_schema-persons',
28
+ up: async ({ context: db }) => {
29
+ await db.createCollection('persons');
30
+ if (isMultiTenant) {
31
+ await db.collection('persons').createIndex({ _orgId: 1 });
32
+ await db.collection('persons').createIndex({ _orgId: 1, externalId: 1 }, { unique: true, sparse: true });
33
+ }
34
+ else {
35
+ await db.collection('persons').createIndex({ externalId: 1 }, { unique: true, sparse: true });
36
+ }
37
+ },
38
+ down: async ({ context: db }) => {
39
+ await db.collection('persons').drop();
40
+ }
41
+ });
42
+ if (isAuthEnabled)
43
+ migrations.push({
44
+ name: '00000000000003_schema-users',
34
45
  up: async ({ context: db }) => {
35
46
  await db.createCollection('users');
36
- if (config.app.isMultiTenant) {
47
+ if (dbConfig.app.isMultiTenant) {
37
48
  await db.collection('users').createIndex({ _orgId: 1, email: 1 }, { unique: true });
38
49
  await db.collection('users').createIndex({ _orgId: 1 });
39
50
  }
@@ -47,7 +58,7 @@ export const getMongoInitialSchema = (config, resetConfig) => {
47
58
  });
48
59
  if (isAuthEnabled)
49
60
  migrations.push({
50
- name: '00000000000003_schema-refresh-tokens',
61
+ name: '00000000000004_schema-refresh-tokens',
51
62
  up: async ({ context: db }) => {
52
63
  await db.createCollection('refresh_tokens');
53
64
  await db.collection('refresh_tokens').createIndex({ token: 1 }, { unique: true });
@@ -63,7 +74,24 @@ export const getMongoInitialSchema = (config, resetConfig) => {
63
74
  });
64
75
  if (isAuthEnabled)
65
76
  migrations.push({
66
- name: '00000000000004_schema-roles',
77
+ name: '00000000000005_schema-password-reset-tokens',
78
+ up: async ({ context: db }) => {
79
+ await db.createCollection('password_reset_tokens');
80
+ if (isMultiTenant) {
81
+ await db.collection('password_reset_tokens').createIndex({ _orgId: 1, email: 1 }, { unique: true });
82
+ await db.collection('password_reset_tokens').createIndex({ _orgId: 1 });
83
+ }
84
+ else {
85
+ await db.collection('password_reset_tokens').createIndex({ email: 1 }, { unique: true });
86
+ }
87
+ },
88
+ down: async ({ context: db }) => {
89
+ await db.collection('password_reset_tokens').drop();
90
+ }
91
+ });
92
+ if (isAuthEnabled)
93
+ migrations.push({
94
+ name: '00000000000006_schema-roles',
67
95
  up: async ({ context: db }) => {
68
96
  await db.createCollection('roles');
69
97
  if (isMultiTenant) {
@@ -80,7 +108,7 @@ export const getMongoInitialSchema = (config, resetConfig) => {
80
108
  });
81
109
  if (isAuthEnabled)
82
110
  migrations.push({
83
- name: '00000000000005_schema-user-roles',
111
+ name: '00000000000007_schema-user-roles',
84
112
  up: async ({ context: db }) => {
85
113
  await db.createCollection('user_roles');
86
114
  if (isMultiTenant) {
@@ -99,7 +127,7 @@ export const getMongoInitialSchema = (config, resetConfig) => {
99
127
  });
100
128
  if (isAuthEnabled)
101
129
  migrations.push({
102
- name: '00000000000006_schema-features',
130
+ name: '00000000000008_schema-features',
103
131
  up: async ({ context: db }) => {
104
132
  await db.createCollection('features');
105
133
  if (isMultiTenant) {
@@ -116,7 +144,7 @@ export const getMongoInitialSchema = (config, resetConfig) => {
116
144
  });
117
145
  if (isAuthEnabled)
118
146
  migrations.push({
119
- name: '00000000000007_schema-authorizations',
147
+ name: '00000000000009_schema-authorizations',
120
148
  up: async ({ context: db }) => {
121
149
  await db.createCollection('authorizations');
122
150
  if (isMultiTenant) {
@@ -135,13 +163,11 @@ export const getMongoInitialSchema = (config, resetConfig) => {
135
163
  });
136
164
  if (isMultiTenant) {
137
165
  migrations.push({
138
- name: '00000000000008_data-meta-org',
166
+ name: '00000000000010_data-meta-org',
139
167
  up: async ({ context: db }) => {
140
- const _id = randomUUID().toString();
141
- const metaOrg = {
142
- _id,
143
- name: config.multiTenant.metaOrgName,
144
- code: config.multiTenant.metaOrgCode,
168
+ const metaOrgDoc = {
169
+ name: dbConfig.multiTenant.metaOrgName,
170
+ code: dbConfig.multiTenant.metaOrgCode,
145
171
  description: undefined,
146
172
  status: 1,
147
173
  isMetaOrg: true,
@@ -153,36 +179,33 @@ export const getMongoInitialSchema = (config, resetConfig) => {
153
179
  _deleted: undefined,
154
180
  _deletedBy: undefined
155
181
  };
156
- await db.collection('organizations').insertOne(metaOrg);
157
- initializeSystemUserContext(config.email?.systemEmailAddress || 'system@example.com', metaOrg);
182
+ const result = await db.collection('organizations').insertOne(metaOrgDoc);
183
+ const metaOrg = { ...metaOrgDoc, _id: result.insertedId };
184
+ initializeSystemUserContext(dbConfig.email?.systemEmailAddress || 'system@example.com', metaOrg);
158
185
  },
159
186
  down: async ({ context: db }) => {
160
187
  await db.collection('organizations').deleteMany({ isMetaOrg: true });
161
188
  }
162
189
  });
163
190
  }
164
- if (isAuthEnabled && resetConfig) {
191
+ if (isAuthEnabled && dbConfig.adminUser) {
165
192
  migrations.push({
166
- name: '00000000000009_data-admin-user',
193
+ name: '00000000000011_data-admin-user',
167
194
  up: async ({ context: db }) => {
168
- const database = new MongoDBDatabase(db);
169
- const authService = new AuthService(database);
170
- const personService = new GenericApiService(database, 'persons', 'person', personModelSpec);
171
195
  if (!isSystemUserContextInitialized()) {
172
196
  const errorMessage = isMultiTenant
173
- ? 'SystemUserContext has not been initialized. The meta-org migration (00000000000008_data-meta-org) should have run before this migration. ' +
174
- 'This migration only runs if config.app.metaOrgName and config.app.metaOrgCode are provided. ' +
175
- 'Please ensure both values are set in your config.'
197
+ ? 'SystemUserContext has not been initialized. The meta-org migration (00000000000010_data-meta-org) should have run before this migration. ' +
198
+ 'Please ensure metaOrgName and metaOrgCode are provided in your dbConfig.'
176
199
  : 'BUG: SystemUserContext has not been initialized. For non-multi-tenant setups, SystemUserContext should be initialized before migrations run.';
177
200
  console.error('❌ Migration Error:', errorMessage);
178
201
  throw new Error(errorMessage);
179
202
  }
180
203
  const systemUserContext = getSystemUserContext();
181
- const _id = randomUUID().toString();
182
- const personId = randomUUID().toString();
183
- const person = {
184
- _id: personId,
185
- _orgId: systemUserContext.organization?._id,
204
+ const orgDoc = isMultiTenant && systemUserContext.organization?._id ? { _orgId: systemUserContext.organization._id } : {};
205
+ const hashedPassword = await passwordUtils.hashPassword(dbConfig.adminUser.password);
206
+ const email = dbConfig.adminUser.email.toLowerCase();
207
+ const personResult = await db.collection('persons').insertOne({
208
+ ...orgDoc,
186
209
  externalId: 'admin-user-person-external-id',
187
210
  firstName: 'Admin',
188
211
  lastName: 'User',
@@ -193,50 +216,51 @@ export const getMongoInitialSchema = (config, resetConfig) => {
193
216
  _createdBy: 'system',
194
217
  _updated: new Date(),
195
218
  _updatedBy: 'system'
196
- };
197
- await authService.createUser(systemUserContext, {
198
- _id: _id,
199
- _orgId: systemUserContext.organization?._id,
219
+ });
220
+ await db.collection('users').insertOne({
221
+ ...orgDoc,
200
222
  externalId: 'admin-user-external-id',
201
- email: resetConfig.adminUser.email,
202
- password: resetConfig.adminUser.password,
223
+ email,
224
+ password: hashedPassword,
203
225
  displayName: 'Admin User',
204
- }, person);
226
+ personId: personResult.insertedId,
227
+ _created: new Date(),
228
+ _createdBy: 'system',
229
+ _updated: new Date(),
230
+ _updatedBy: 'system'
231
+ });
205
232
  },
206
233
  down: async ({ context: db }) => {
207
- if (!resetConfig?.adminUser?.email)
234
+ if (!dbConfig?.adminUser?.email)
208
235
  return;
209
- await db.collection('users').deleteOne({ email: resetConfig.adminUser.email });
236
+ await db.collection('users').deleteOne({ email: dbConfig.adminUser.email.toLowerCase() });
210
237
  }
211
238
  });
212
239
  }
213
- if (resetConfig?.adminUser?.email && isMultiTenant) {
240
+ if (isAuthEnabled && dbConfig.adminUser) {
214
241
  migrations.push({
215
- name: '00000000000010_data-admin-authorizations',
242
+ name: '00000000000012_data-admin-authorizations',
216
243
  up: async ({ context: db }) => {
217
244
  const database = new MongoDBDatabase(db);
218
245
  const organizationService = new OrganizationService(database);
219
- const authService = new AuthService(database);
220
- const metaOrg = await organizationService.getMetaOrg(EmptyUserContext);
221
- if (!metaOrg) {
246
+ const metaOrg = isMultiTenant ? await organizationService.getMetaOrg(EmptyUserContext) : undefined;
247
+ if (isMultiTenant && !metaOrg) {
222
248
  throw new Error('Meta organization not found. Ensure meta-org migration ran successfully.');
223
249
  }
224
- const adminUser = await authService.getUserByEmail(resetConfig.adminUser.email);
250
+ const email = dbConfig.adminUser.email.toLowerCase();
251
+ const adminUser = await db.collection('users').findOne({ email });
225
252
  if (!adminUser) {
226
253
  throw new Error('Admin user not found. Ensure admin-user migration ran successfully.');
227
254
  }
228
- const roleId = randomUUID().toString();
229
- await db.collection('roles').insertOne({
230
- _id: roleId,
231
- _orgId: metaOrg._id,
255
+ const orgDoc = isMultiTenant && metaOrg ? { _orgId: metaOrg._id } : {};
256
+ const roleResult = await db.collection('roles').insertOne({
257
+ ...orgDoc,
232
258
  name: 'admin'
233
259
  });
234
- const userRoleId = randomUUID().toString();
235
260
  await db.collection('user_roles').insertOne({
236
- _id: userRoleId,
237
- _orgId: metaOrg._id,
261
+ ...orgDoc,
238
262
  userId: adminUser._id,
239
- roleId: roleId,
263
+ roleId: roleResult.insertedId,
240
264
  _created: new Date(),
241
265
  _createdBy: 'system',
242
266
  _updated: new Date(),
@@ -244,18 +268,14 @@ export const getMongoInitialSchema = (config, resetConfig) => {
244
268
  _deleted: undefined,
245
269
  _deletedBy: undefined
246
270
  });
247
- const featureId = randomUUID().toString();
248
- await db.collection('features').insertOne({
249
- _id: featureId,
250
- _orgId: metaOrg._id,
271
+ const featureResult = await db.collection('features').insertOne({
272
+ ...orgDoc,
251
273
  name: 'admin'
252
274
  });
253
- const authorizationId = randomUUID().toString();
254
275
  await db.collection('authorizations').insertOne({
255
- _id: authorizationId,
256
- _orgId: metaOrg._id,
257
- roleId: roleId,
258
- featureId: featureId,
276
+ ...orgDoc,
277
+ roleId: roleResult.insertedId,
278
+ featureId: featureResult.insertedId,
259
279
  startDate: undefined,
260
280
  endDate: undefined,
261
281
  config: undefined,
@@ -270,26 +290,27 @@ export const getMongoInitialSchema = (config, resetConfig) => {
270
290
  down: async ({ context: db }) => {
271
291
  const database = new MongoDBDatabase(db);
272
292
  const organizationService = new OrganizationService(database);
273
- const metaOrg = await organizationService.getMetaOrg(EmptyUserContext);
274
- if (!metaOrg)
293
+ const metaOrg = isMultiTenant ? await organizationService.getMetaOrg(EmptyUserContext) : undefined;
294
+ if (isMultiTenant && !metaOrg)
275
295
  return;
276
- const adminRole = await db.collection('roles').findOne({ _orgId: metaOrg._id, name: 'admin' });
277
- const adminFeature = await db.collection('features').findOne({ _orgId: metaOrg._id, name: 'admin' });
296
+ const orgFilter = isMultiTenant && metaOrg ? { _orgId: metaOrg._id } : {};
297
+ const adminRole = await db.collection('roles').findOne({ ...orgFilter, name: 'admin' });
298
+ const adminFeature = await db.collection('features').findOne({ ...orgFilter, name: 'admin' });
278
299
  if (adminRole && adminFeature) {
279
300
  await db.collection('authorizations').deleteMany({
280
- _orgId: metaOrg._id,
301
+ ...orgFilter,
281
302
  roleId: adminRole._id,
282
303
  featureId: adminFeature._id
283
304
  });
284
305
  }
285
- await db.collection('features').deleteMany({ _orgId: metaOrg._id, name: 'admin' });
306
+ await db.collection('features').deleteMany({ ...orgFilter, name: 'admin' });
286
307
  if (adminRole) {
287
308
  await db.collection('user_roles').deleteMany({
288
- _orgId: metaOrg._id,
309
+ ...orgFilter,
289
310
  roleId: adminRole._id
290
311
  });
291
312
  }
292
- await db.collection('roles').deleteMany({ _orgId: metaOrg._id, name: 'admin' });
313
+ await db.collection('roles').deleteMany({ ...orgFilter, name: 'admin' });
293
314
  }
294
315
  });
295
316
  }
@@ -1,2 +1,4 @@
1
- import { IBaseApiConfig } from "../../../models/base-api-config.interface.js";
2
- export declare function buildMongoUrl(config: IBaseApiConfig): string;
1
+ import { IDatabaseConfig } from "../../../models/database-config.interface.js";
2
+ export declare function buildMongoUrl(config: {
3
+ database: IDatabaseConfig;
4
+ }): string;