@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.
- package/LICENSE +201 -201
- package/README.md +77 -77
- package/dist/__tests__/common-test.utils.js +0 -4
- package/dist/__tests__/postgres-test-migrations/postgres-test-schema.js +266 -266
- package/dist/__tests__/postgres.test-database.js +8 -8
- package/dist/config/base-api-config.d.ts +1 -1
- package/dist/config/base-api-config.js +12 -7
- package/dist/databases/migrations/migration-runner.d.ts +3 -5
- package/dist/databases/migrations/migration-runner.js +40 -44
- package/dist/databases/mongo-db/migrations/mongo-initial-schema.d.ts +2 -3
- package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +102 -81
- package/dist/databases/mongo-db/utils/build-mongo-url.util.d.ts +4 -2
- package/dist/databases/mongo-db/utils/convert-operations-to-pipeline.util.js +141 -39
- package/dist/databases/postgres/commands/postgres-batch-update.command.js +7 -7
- package/dist/databases/postgres/commands/postgres-create-many.command.js +4 -4
- package/dist/databases/postgres/commands/postgres-create.command.js +4 -4
- package/dist/databases/postgres/commands/postgres-full-update-by-id.command.js +13 -13
- package/dist/databases/postgres/commands/postgres-partial-update-by-id.command.js +7 -7
- package/dist/databases/postgres/commands/postgres-update.command.js +7 -7
- package/dist/databases/postgres/migrations/__tests__/test-migration-helper.js +8 -1
- package/dist/databases/postgres/migrations/postgres-initial-schema.d.ts +2 -3
- package/dist/databases/postgres/migrations/postgres-initial-schema.js +263 -266
- package/dist/databases/postgres/postgres.database.js +17 -17
- package/dist/databases/postgres/utils/build-join-clauses.js +151 -151
- package/dist/databases/postgres/utils/build-postgres-url.util.d.ts +4 -2
- package/dist/databases/postgres/utils/build-select-clause.js +6 -6
- package/dist/databases/postgres/utils/does-table-exist.util.js +4 -4
- package/dist/models/app-config.interface.d.ts +8 -0
- package/dist/models/base-api-config.interface.d.ts +4 -17
- package/dist/models/database-config.interface.d.ts +7 -0
- package/dist/models/index.d.ts +3 -1
- package/dist/models/index.js +3 -1
- package/dist/models/initial-database-config.interface.d.ts +16 -0
- package/dist/models/initial-database-config.interface.js +1 -0
- package/package.json +92 -92
- package/dist/models/multi-tenant-config.interface.d.ts +0 -4
- package/dist/models/reset-api-config.interface.d.ts +0 -6
- /package/dist/models/{multi-tenant-config.interface.js → app-config.interface.js} +0 -0
- /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(
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
15
|
+
return result;
|
|
11
16
|
}
|
|
12
|
-
export function setBaseApiConfig(
|
|
17
|
+
export function setBaseApiConfig(theConfig) {
|
|
13
18
|
if (!isConfigSet) {
|
|
14
|
-
config =
|
|
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 {
|
|
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
|
|
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(
|
|
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
|
-
|
|
13
|
-
resetConfig;
|
|
11
|
+
dbMigrationConfig;
|
|
14
12
|
dbType;
|
|
15
13
|
dbUrl;
|
|
16
14
|
migrationsDir;
|
|
17
15
|
primaryTimezone;
|
|
18
16
|
dbConnection;
|
|
19
|
-
constructor(
|
|
20
|
-
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
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 =
|
|
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.
|
|
61
|
-
user: this.
|
|
62
|
-
password: this.
|
|
63
|
-
port: this.
|
|
64
|
-
database: this.
|
|
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.
|
|
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.
|
|
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.
|
|
169
|
-
user: this.
|
|
170
|
-
password: this.
|
|
171
|
-
port: this.
|
|
172
|
-
database: this.
|
|
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.
|
|
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 {
|
|
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: (
|
|
12
|
+
export declare const getMongoInitialSchema: (dbConfig: IInitialDbMigrationConfig) => ISyntheticMigration[];
|
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
5
|
-
|
|
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 =
|
|
8
|
-
if (isMultiTenant && !
|
|
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 =
|
|
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-
|
|
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 (
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
166
|
+
name: '00000000000010_data-meta-org',
|
|
139
167
|
up: async ({ context: db }) => {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
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(
|
|
157
|
-
|
|
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 &&
|
|
191
|
+
if (isAuthEnabled && dbConfig.adminUser) {
|
|
165
192
|
migrations.push({
|
|
166
|
-
name: '
|
|
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 (
|
|
174
|
-
'
|
|
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
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
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
|
|
198
|
-
|
|
199
|
-
_orgId: systemUserContext.organization?._id,
|
|
219
|
+
});
|
|
220
|
+
await db.collection('users').insertOne({
|
|
221
|
+
...orgDoc,
|
|
200
222
|
externalId: 'admin-user-external-id',
|
|
201
|
-
email
|
|
202
|
-
password:
|
|
223
|
+
email,
|
|
224
|
+
password: hashedPassword,
|
|
203
225
|
displayName: 'Admin User',
|
|
204
|
-
|
|
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 (!
|
|
234
|
+
if (!dbConfig?.adminUser?.email)
|
|
208
235
|
return;
|
|
209
|
-
await db.collection('users').deleteOne({ email:
|
|
236
|
+
await db.collection('users').deleteOne({ email: dbConfig.adminUser.email.toLowerCase() });
|
|
210
237
|
}
|
|
211
238
|
});
|
|
212
239
|
}
|
|
213
|
-
if (
|
|
240
|
+
if (isAuthEnabled && dbConfig.adminUser) {
|
|
214
241
|
migrations.push({
|
|
215
|
-
name: '
|
|
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
|
|
220
|
-
|
|
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
|
|
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
|
|
229
|
-
await db.collection('roles').insertOne({
|
|
230
|
-
|
|
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
|
-
|
|
237
|
-
_orgId: metaOrg._id,
|
|
261
|
+
...orgDoc,
|
|
238
262
|
userId: adminUser._id,
|
|
239
|
-
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
|
|
248
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
|
277
|
-
const
|
|
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
|
-
|
|
301
|
+
...orgFilter,
|
|
281
302
|
roleId: adminRole._id,
|
|
282
303
|
featureId: adminFeature._id
|
|
283
304
|
});
|
|
284
305
|
}
|
|
285
|
-
await db.collection('features').deleteMany({
|
|
306
|
+
await db.collection('features').deleteMany({ ...orgFilter, name: 'admin' });
|
|
286
307
|
if (adminRole) {
|
|
287
308
|
await db.collection('user_roles').deleteMany({
|
|
288
|
-
|
|
309
|
+
...orgFilter,
|
|
289
310
|
roleId: adminRole._id
|
|
290
311
|
});
|
|
291
312
|
}
|
|
292
|
-
await db.collection('roles').deleteMany({
|
|
313
|
+
await db.collection('roles').deleteMany({ ...orgFilter, name: 'admin' });
|
|
293
314
|
}
|
|
294
315
|
});
|
|
295
316
|
}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function buildMongoUrl(config:
|
|
1
|
+
import { IDatabaseConfig } from "../../../models/database-config.interface.js";
|
|
2
|
+
export declare function buildMongoUrl(config: {
|
|
3
|
+
database: IDatabaseConfig;
|
|
4
|
+
}): string;
|