@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
|
@@ -1,39 +1,34 @@
|
|
|
1
1
|
import { initializeSystemUserContext, EmptyUserContext, getSystemUserContext, isSystemUserContextInitialized } from '@loomcore/common/models';
|
|
2
2
|
import { PostgresDatabase } from '../postgres.database.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { OrganizationService } from '../../../services/index.js';
|
|
4
|
+
import { passwordUtils } from '../../../utils/index.js';
|
|
5
|
+
export const getPostgresInitialSchema = (dbConfig) => {
|
|
5
6
|
const migrations = [];
|
|
6
|
-
const isMultiTenant =
|
|
7
|
-
if (isMultiTenant && !
|
|
7
|
+
const isMultiTenant = dbConfig.app.isMultiTenant;
|
|
8
|
+
if (isMultiTenant && !dbConfig.multiTenant) {
|
|
8
9
|
throw new Error('Multi-tenant configuration is enabled but multi-tenant configuration is not provided');
|
|
9
10
|
}
|
|
10
|
-
const isAuthEnabled =
|
|
11
|
-
if (isAuthEnabled && !config.auth) {
|
|
12
|
-
throw new Error('Auth enabled without auth configuration');
|
|
13
|
-
}
|
|
14
|
-
if (isAuthEnabled && (!config.thirdPartyClients?.emailClient || !config.email)) {
|
|
15
|
-
throw new Error('Auth enabled without email client or email configuration');
|
|
16
|
-
}
|
|
11
|
+
const isAuthEnabled = dbConfig.app.isAuthEnabled;
|
|
17
12
|
if (isMultiTenant) {
|
|
18
13
|
migrations.push({
|
|
19
14
|
name: '00000000000001_schema-organizations',
|
|
20
15
|
up: async ({ context: pool }) => {
|
|
21
|
-
await pool.query(`
|
|
22
|
-
CREATE TABLE IF NOT EXISTS "organizations" (
|
|
23
|
-
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
24
|
-
"name" VARCHAR(255) NOT NULL UNIQUE,
|
|
25
|
-
"code" VARCHAR(255) NOT NULL UNIQUE,
|
|
26
|
-
"description" TEXT,
|
|
27
|
-
"status" INTEGER NOT NULL,
|
|
28
|
-
"is_meta_org" BOOLEAN NOT NULL,
|
|
29
|
-
"auth_token" TEXT,
|
|
30
|
-
"_created" TIMESTAMPTZ NOT NULL,
|
|
31
|
-
"_createdBy" INTEGER NOT NULL,
|
|
32
|
-
"_updated" TIMESTAMPTZ NOT NULL,
|
|
33
|
-
"_updatedBy" INTEGER NOT NULL,
|
|
34
|
-
"_deleted" TIMESTAMPTZ,
|
|
35
|
-
"_deletedBy" INTEGER
|
|
36
|
-
)
|
|
16
|
+
await pool.query(`
|
|
17
|
+
CREATE TABLE IF NOT EXISTS "organizations" (
|
|
18
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
19
|
+
"name" VARCHAR(255) NOT NULL UNIQUE,
|
|
20
|
+
"code" VARCHAR(255) NOT NULL UNIQUE,
|
|
21
|
+
"description" TEXT,
|
|
22
|
+
"status" INTEGER NOT NULL,
|
|
23
|
+
"is_meta_org" BOOLEAN NOT NULL,
|
|
24
|
+
"auth_token" TEXT,
|
|
25
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
26
|
+
"_createdBy" INTEGER NOT NULL,
|
|
27
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
28
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
29
|
+
"_deleted" TIMESTAMPTZ,
|
|
30
|
+
"_deletedBy" INTEGER
|
|
31
|
+
)
|
|
37
32
|
`);
|
|
38
33
|
},
|
|
39
34
|
down: async ({ context: pool }) => {
|
|
@@ -46,26 +41,26 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
|
|
|
46
41
|
name: '00000000000002_schema-persons',
|
|
47
42
|
up: async ({ context: pool }) => {
|
|
48
43
|
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
49
|
-
await pool.query(`
|
|
50
|
-
CREATE TABLE IF NOT EXISTS "persons" (
|
|
51
|
-
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
52
|
-
${orgColumnDef}
|
|
53
|
-
"external_id" VARCHAR(255) UNIQUE,
|
|
54
|
-
"first_name" VARCHAR(255) NOT NULL,
|
|
55
|
-
"middle_name" VARCHAR(255),
|
|
56
|
-
"last_name" VARCHAR(255) NOT NULL,
|
|
57
|
-
"date_of_birth" DATE,
|
|
58
|
-
"is_agent" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
59
|
-
"is_client" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
60
|
-
"is_employee" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
61
|
-
"extended_types" INTEGER,
|
|
62
|
-
"_created" TIMESTAMPTZ NOT NULL,
|
|
63
|
-
"_createdBy" INTEGER NOT NULL,
|
|
64
|
-
"_updated" TIMESTAMPTZ NOT NULL,
|
|
65
|
-
"_updatedBy" INTEGER NOT NULL,
|
|
66
|
-
"_deleted" TIMESTAMPTZ,
|
|
67
|
-
"_deletedBy" INTEGER
|
|
68
|
-
)
|
|
44
|
+
await pool.query(`
|
|
45
|
+
CREATE TABLE IF NOT EXISTS "persons" (
|
|
46
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
47
|
+
${orgColumnDef}
|
|
48
|
+
"external_id" VARCHAR(255) UNIQUE,
|
|
49
|
+
"first_name" VARCHAR(255) NOT NULL,
|
|
50
|
+
"middle_name" VARCHAR(255),
|
|
51
|
+
"last_name" VARCHAR(255) NOT NULL,
|
|
52
|
+
"date_of_birth" DATE,
|
|
53
|
+
"is_agent" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
54
|
+
"is_client" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
55
|
+
"is_employee" BOOLEAN NOT NULL DEFAULT FALSE,
|
|
56
|
+
"extended_types" INTEGER,
|
|
57
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
58
|
+
"_createdBy" INTEGER NOT NULL,
|
|
59
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
60
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
61
|
+
"_deleted" TIMESTAMPTZ,
|
|
62
|
+
"_deletedBy" INTEGER
|
|
63
|
+
)
|
|
69
64
|
`);
|
|
70
65
|
},
|
|
71
66
|
down: async ({ context: pool }) => {
|
|
@@ -80,27 +75,27 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
|
|
|
80
75
|
let uniqueConstraint = isMultiTenant
|
|
81
76
|
? 'CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")'
|
|
82
77
|
: 'CONSTRAINT "uk_users_email" UNIQUE ("email")';
|
|
83
|
-
uniqueConstraint += `,
|
|
78
|
+
uniqueConstraint += `,
|
|
84
79
|
CONSTRAINT "fk_users_person_id" FOREIGN KEY("person_id") REFERENCES "persons"("_id") ON DELETE CASCADE`;
|
|
85
|
-
await pool.query(`
|
|
86
|
-
CREATE TABLE IF NOT EXISTS "users" (
|
|
87
|
-
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
88
|
-
${orgColumnDef}
|
|
89
|
-
"external_id" VARCHAR(255) UNIQUE,
|
|
90
|
-
"email" VARCHAR(255) NOT NULL,
|
|
91
|
-
"display_name" VARCHAR(255),
|
|
92
|
-
"password" VARCHAR(255) NOT NULL,
|
|
93
|
-
"person_id" INTEGER UNIQUE,
|
|
94
|
-
"_lastLoggedIn" TIMESTAMPTZ,
|
|
95
|
-
"_lastPasswordChange" TIMESTAMPTZ,
|
|
96
|
-
"_created" TIMESTAMPTZ NOT NULL,
|
|
97
|
-
"_createdBy" INTEGER NOT NULL,
|
|
98
|
-
"_updated" TIMESTAMPTZ NOT NULL,
|
|
99
|
-
"_updatedBy" INTEGER NOT NULL,
|
|
100
|
-
"_deleted" TIMESTAMPTZ,
|
|
101
|
-
"_deletedBy" INTEGER,
|
|
102
|
-
${uniqueConstraint}
|
|
103
|
-
)
|
|
80
|
+
await pool.query(`
|
|
81
|
+
CREATE TABLE IF NOT EXISTS "users" (
|
|
82
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
83
|
+
${orgColumnDef}
|
|
84
|
+
"external_id" VARCHAR(255) UNIQUE,
|
|
85
|
+
"email" VARCHAR(255) NOT NULL,
|
|
86
|
+
"display_name" VARCHAR(255),
|
|
87
|
+
"password" VARCHAR(255) NOT NULL,
|
|
88
|
+
"person_id" INTEGER UNIQUE,
|
|
89
|
+
"_lastLoggedIn" TIMESTAMPTZ,
|
|
90
|
+
"_lastPasswordChange" TIMESTAMPTZ,
|
|
91
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
92
|
+
"_createdBy" INTEGER NOT NULL,
|
|
93
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
94
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
95
|
+
"_deleted" TIMESTAMPTZ,
|
|
96
|
+
"_deletedBy" INTEGER,
|
|
97
|
+
${uniqueConstraint}
|
|
98
|
+
)
|
|
104
99
|
`);
|
|
105
100
|
},
|
|
106
101
|
down: async ({ context: pool }) => {
|
|
@@ -112,18 +107,18 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
|
|
|
112
107
|
name: '00000000000004_schema-refresh-tokens',
|
|
113
108
|
up: async ({ context: pool }) => {
|
|
114
109
|
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
115
|
-
await pool.query(`
|
|
116
|
-
CREATE TABLE IF NOT EXISTS "refresh_tokens" (
|
|
117
|
-
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
118
|
-
${orgColumnDef}
|
|
119
|
-
"token" VARCHAR(255) NOT NULL,
|
|
120
|
-
"device_id" VARCHAR(255) NOT NULL,
|
|
121
|
-
"user_id" INTEGER NOT NULL,
|
|
122
|
-
"expires_on" BIGINT NOT NULL,
|
|
123
|
-
"created" TIMESTAMPTZ NOT NULL,
|
|
124
|
-
"created_by" INTEGER NOT NULL,
|
|
125
|
-
CONSTRAINT "fk_refresh_tokens_user" FOREIGN KEY ("user_id") REFERENCES "users"("_id") ON DELETE CASCADE
|
|
126
|
-
)
|
|
110
|
+
await pool.query(`
|
|
111
|
+
CREATE TABLE IF NOT EXISTS "refresh_tokens" (
|
|
112
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
113
|
+
${orgColumnDef}
|
|
114
|
+
"token" VARCHAR(255) NOT NULL,
|
|
115
|
+
"device_id" VARCHAR(255) NOT NULL,
|
|
116
|
+
"user_id" INTEGER NOT NULL,
|
|
117
|
+
"expires_on" BIGINT NOT NULL,
|
|
118
|
+
"created" TIMESTAMPTZ NOT NULL,
|
|
119
|
+
"created_by" INTEGER NOT NULL,
|
|
120
|
+
CONSTRAINT "fk_refresh_tokens_user" FOREIGN KEY ("user_id") REFERENCES "users"("_id") ON DELETE CASCADE
|
|
121
|
+
)
|
|
127
122
|
`);
|
|
128
123
|
},
|
|
129
124
|
down: async ({ context: pool }) => {
|
|
@@ -138,21 +133,21 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
|
|
|
138
133
|
const uniqueConstraint = isMultiTenant
|
|
139
134
|
? 'CONSTRAINT "uk_passwordResetTokens_email" UNIQUE ("_orgId", "email")'
|
|
140
135
|
: 'CONSTRAINT "uk_passwordResetTokens_email" UNIQUE ("email")';
|
|
141
|
-
await pool.query(`
|
|
142
|
-
CREATE TABLE IF NOT EXISTS "password_reset_tokens" (
|
|
143
|
-
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
144
|
-
${orgColumnDef}
|
|
145
|
-
"email" VARCHAR(255) NOT NULL,
|
|
146
|
-
"token" VARCHAR(255) NOT NULL,
|
|
147
|
-
"expires_on" BIGINT NOT NULL,
|
|
148
|
-
"_created" TIMESTAMPTZ NOT NULL,
|
|
149
|
-
"_createdBy" INTEGER NOT NULL,
|
|
150
|
-
"_updated" TIMESTAMPTZ NOT NULL,
|
|
151
|
-
"_updatedBy" INTEGER NOT NULL,
|
|
152
|
-
"_deleted" TIMESTAMPTZ,
|
|
153
|
-
"_deletedBy" INTEGER,
|
|
154
|
-
${uniqueConstraint}
|
|
155
|
-
)
|
|
136
|
+
await pool.query(`
|
|
137
|
+
CREATE TABLE IF NOT EXISTS "password_reset_tokens" (
|
|
138
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
139
|
+
${orgColumnDef}
|
|
140
|
+
"email" VARCHAR(255) NOT NULL,
|
|
141
|
+
"token" VARCHAR(255) NOT NULL,
|
|
142
|
+
"expires_on" BIGINT NOT NULL,
|
|
143
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
144
|
+
"_createdBy" INTEGER NOT NULL,
|
|
145
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
146
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
147
|
+
"_deleted" TIMESTAMPTZ,
|
|
148
|
+
"_deletedBy" INTEGER,
|
|
149
|
+
${uniqueConstraint}
|
|
150
|
+
)
|
|
156
151
|
`);
|
|
157
152
|
},
|
|
158
153
|
down: async ({ context: pool }) => {
|
|
@@ -167,14 +162,14 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
|
|
|
167
162
|
const uniqueConstraint = isMultiTenant
|
|
168
163
|
? 'CONSTRAINT "uk_roles_name" UNIQUE ("_orgId", "name")'
|
|
169
164
|
: 'CONSTRAINT "uk_roles_name" UNIQUE ("name")';
|
|
170
|
-
await pool.query(`
|
|
171
|
-
CREATE TABLE IF NOT EXISTS "roles" (
|
|
172
|
-
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
173
|
-
${orgColumnDef}
|
|
174
|
-
"name" VARCHAR(255) NOT NULL,
|
|
175
|
-
"description" TEXT,
|
|
176
|
-
${uniqueConstraint}
|
|
177
|
-
)
|
|
165
|
+
await pool.query(`
|
|
166
|
+
CREATE TABLE IF NOT EXISTS "roles" (
|
|
167
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
168
|
+
${orgColumnDef}
|
|
169
|
+
"name" VARCHAR(255) NOT NULL,
|
|
170
|
+
"description" TEXT,
|
|
171
|
+
${uniqueConstraint}
|
|
172
|
+
)
|
|
178
173
|
`);
|
|
179
174
|
},
|
|
180
175
|
down: async ({ context: pool }) => {
|
|
@@ -189,22 +184,22 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
|
|
|
189
184
|
const uniqueConstraint = isMultiTenant
|
|
190
185
|
? 'CONSTRAINT "uk_user_roles" UNIQUE ("_orgId", "user_id", "role_id")'
|
|
191
186
|
: 'CONSTRAINT "uk_user_roles" UNIQUE ("user_id", "role_id")';
|
|
192
|
-
await pool.query(`
|
|
193
|
-
CREATE TABLE IF NOT EXISTS "user_roles" (
|
|
194
|
-
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
195
|
-
${orgColumnDef}
|
|
196
|
-
"user_id" INTEGER NOT NULL,
|
|
197
|
-
"role_id" INTEGER NOT NULL,
|
|
198
|
-
"_created" TIMESTAMPTZ NOT NULL,
|
|
199
|
-
"_createdBy" INTEGER NOT NULL,
|
|
200
|
-
"_updated" TIMESTAMPTZ NOT NULL,
|
|
201
|
-
"_updatedBy" INTEGER NOT NULL,
|
|
202
|
-
"_deleted" TIMESTAMPTZ,
|
|
203
|
-
"_deletedBy" INTEGER,
|
|
204
|
-
CONSTRAINT "fk_user_roles_user" FOREIGN KEY ("user_id") REFERENCES "users"("_id") ON DELETE CASCADE,
|
|
205
|
-
CONSTRAINT "fk_user_roles_role" FOREIGN KEY ("role_id") REFERENCES "roles"("_id") ON DELETE CASCADE,
|
|
206
|
-
${uniqueConstraint}
|
|
207
|
-
)
|
|
187
|
+
await pool.query(`
|
|
188
|
+
CREATE TABLE IF NOT EXISTS "user_roles" (
|
|
189
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
190
|
+
${orgColumnDef}
|
|
191
|
+
"user_id" INTEGER NOT NULL,
|
|
192
|
+
"role_id" INTEGER NOT NULL,
|
|
193
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
194
|
+
"_createdBy" INTEGER NOT NULL,
|
|
195
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
196
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
197
|
+
"_deleted" TIMESTAMPTZ,
|
|
198
|
+
"_deletedBy" INTEGER,
|
|
199
|
+
CONSTRAINT "fk_user_roles_user" FOREIGN KEY ("user_id") REFERENCES "users"("_id") ON DELETE CASCADE,
|
|
200
|
+
CONSTRAINT "fk_user_roles_role" FOREIGN KEY ("role_id") REFERENCES "roles"("_id") ON DELETE CASCADE,
|
|
201
|
+
${uniqueConstraint}
|
|
202
|
+
)
|
|
208
203
|
`);
|
|
209
204
|
},
|
|
210
205
|
down: async ({ context: pool }) => {
|
|
@@ -219,14 +214,14 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
|
|
|
219
214
|
const uniqueConstraint = isMultiTenant
|
|
220
215
|
? 'CONSTRAINT "uk_features" UNIQUE ("_orgId", "name")'
|
|
221
216
|
: 'CONSTRAINT "uk_features" UNIQUE ("name")';
|
|
222
|
-
await pool.query(`
|
|
223
|
-
CREATE TABLE IF NOT EXISTS "features" (
|
|
224
|
-
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
225
|
-
${orgColumnDef}
|
|
226
|
-
"name" VARCHAR(255) NOT NULL,
|
|
227
|
-
"description" TEXT,
|
|
228
|
-
${uniqueConstraint}
|
|
229
|
-
)
|
|
217
|
+
await pool.query(`
|
|
218
|
+
CREATE TABLE IF NOT EXISTS "features" (
|
|
219
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
220
|
+
${orgColumnDef}
|
|
221
|
+
"name" VARCHAR(255) NOT NULL,
|
|
222
|
+
"description" TEXT,
|
|
223
|
+
${uniqueConstraint}
|
|
224
|
+
)
|
|
230
225
|
`);
|
|
231
226
|
},
|
|
232
227
|
down: async ({ context: pool }) => {
|
|
@@ -241,25 +236,25 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
|
|
|
241
236
|
const uniqueConstraint = isMultiTenant
|
|
242
237
|
? 'CONSTRAINT "uk_authorizations" UNIQUE ("_orgId", "role_id", "feature_id")'
|
|
243
238
|
: 'CONSTRAINT "uk_authorizations" UNIQUE ("role_id", "feature_id")';
|
|
244
|
-
await pool.query(`
|
|
245
|
-
CREATE TABLE IF NOT EXISTS "authorizations" (
|
|
246
|
-
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
247
|
-
${orgColumnDef}
|
|
248
|
-
"role_id" INTEGER NOT NULL,
|
|
249
|
-
"feature_id" INTEGER NOT NULL,
|
|
250
|
-
"start_date" TIMESTAMPTZ,
|
|
251
|
-
"end_date" TIMESTAMPTZ,
|
|
252
|
-
"config" JSONB,
|
|
253
|
-
"_created" TIMESTAMPTZ NOT NULL,
|
|
254
|
-
"_createdBy" INTEGER NOT NULL,
|
|
255
|
-
"_updated" TIMESTAMPTZ NOT NULL,
|
|
256
|
-
"_updatedBy" INTEGER NOT NULL,
|
|
257
|
-
"_deleted" TIMESTAMPTZ,
|
|
258
|
-
"_deletedBy" INTEGER,
|
|
259
|
-
CONSTRAINT "fk_authorizations_role" FOREIGN KEY ("role_id") REFERENCES "roles"("_id") ON DELETE CASCADE,
|
|
260
|
-
CONSTRAINT "fk_authorizations_feature" FOREIGN KEY ("feature_id") REFERENCES "features"("_id") ON DELETE CASCADE,
|
|
261
|
-
${uniqueConstraint}
|
|
262
|
-
)
|
|
239
|
+
await pool.query(`
|
|
240
|
+
CREATE TABLE IF NOT EXISTS "authorizations" (
|
|
241
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
242
|
+
${orgColumnDef}
|
|
243
|
+
"role_id" INTEGER NOT NULL,
|
|
244
|
+
"feature_id" INTEGER NOT NULL,
|
|
245
|
+
"start_date" TIMESTAMPTZ,
|
|
246
|
+
"end_date" TIMESTAMPTZ,
|
|
247
|
+
"config" JSONB,
|
|
248
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
249
|
+
"_createdBy" INTEGER NOT NULL,
|
|
250
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
251
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
252
|
+
"_deleted" TIMESTAMPTZ,
|
|
253
|
+
"_deletedBy" INTEGER,
|
|
254
|
+
CONSTRAINT "fk_authorizations_role" FOREIGN KEY ("role_id") REFERENCES "roles"("_id") ON DELETE CASCADE,
|
|
255
|
+
CONSTRAINT "fk_authorizations_feature" FOREIGN KEY ("feature_id") REFERENCES "features"("_id") ON DELETE CASCADE,
|
|
256
|
+
${uniqueConstraint}
|
|
257
|
+
)
|
|
263
258
|
`);
|
|
264
259
|
},
|
|
265
260
|
down: async ({ context: pool }) => {
|
|
@@ -270,68 +265,68 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
|
|
|
270
265
|
migrations.push({
|
|
271
266
|
name: '00000000000010_data-meta-org',
|
|
272
267
|
up: async ({ context: pool }) => {
|
|
273
|
-
const result = await pool.query(`
|
|
274
|
-
INSERT INTO "organizations" ("name", "code", "status", "is_meta_org", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
275
|
-
VALUES ($1, $2, 1, true, NOW(), 0, NOW(), 0)
|
|
276
|
-
RETURNING "_id", "name", "code", "status", "is_meta_org", "_created", "_createdBy", "_updated", "_updatedBy"
|
|
277
|
-
`, [
|
|
268
|
+
const result = await pool.query(`
|
|
269
|
+
INSERT INTO "organizations" ("name", "code", "status", "is_meta_org", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
270
|
+
VALUES ($1, $2, 1, true, NOW(), 0, NOW(), 0)
|
|
271
|
+
RETURNING "_id", "name", "code", "status", "is_meta_org", "_created", "_createdBy", "_updated", "_updatedBy"
|
|
272
|
+
`, [dbConfig.multiTenant?.metaOrgName, dbConfig.multiTenant?.metaOrgCode]);
|
|
278
273
|
if (result.rowCount === 0) {
|
|
279
274
|
throw new Error('Failed to create meta organization');
|
|
280
275
|
}
|
|
281
|
-
initializeSystemUserContext(
|
|
276
|
+
initializeSystemUserContext(dbConfig.email?.systemEmailAddress || 'system@example.com', result.rows[0]);
|
|
282
277
|
},
|
|
283
278
|
down: async ({ context: pool }) => {
|
|
284
279
|
await pool.query(`DELETE FROM "organizations" WHERE "is_meta_org" = TRUE`);
|
|
285
280
|
}
|
|
286
281
|
});
|
|
287
282
|
}
|
|
288
|
-
if (isAuthEnabled &&
|
|
283
|
+
if (isAuthEnabled && dbConfig.adminUser) {
|
|
289
284
|
migrations.push({
|
|
290
285
|
name: '00000000000011_data-admin-user',
|
|
291
286
|
up: async ({ context: pool }) => {
|
|
287
|
+
if (!isSystemUserContextInitialized()) {
|
|
288
|
+
const errorMessage = isMultiTenant
|
|
289
|
+
? 'SystemUserContext has not been initialized. The meta-org migration (00000000000010_data-meta-org) should have run before this migration. ' +
|
|
290
|
+
'Please ensure metaOrgName and metaOrgCode are provided in your dbConfig.'
|
|
291
|
+
: 'BUG: SystemUserContext has not been initialized. For non-multi-tenant setups, SystemUserContext should be initialized before migrations run.';
|
|
292
|
+
console.error('❌ Migration Error:', errorMessage);
|
|
293
|
+
throw new Error(errorMessage);
|
|
294
|
+
}
|
|
295
|
+
const systemUserContext = getSystemUserContext();
|
|
296
|
+
const orgId = isMultiTenant ? systemUserContext.organization?._id : undefined;
|
|
297
|
+
const hashedPassword = await passwordUtils.hashPassword(dbConfig.adminUser.password);
|
|
298
|
+
const email = dbConfig.adminUser.email.toLowerCase();
|
|
292
299
|
const client = await pool.connect();
|
|
293
300
|
try {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const systemUserContext = getSystemUserContext();
|
|
306
|
-
const userData = {
|
|
307
|
-
email: resetConfig.adminUser.email,
|
|
308
|
-
password: resetConfig.adminUser.password,
|
|
309
|
-
displayName: 'Admin User',
|
|
310
|
-
};
|
|
311
|
-
if (isMultiTenant && systemUserContext.organization?._id) {
|
|
312
|
-
userData._orgId = systemUserContext.organization._id;
|
|
301
|
+
const personResult = isMultiTenant && orgId
|
|
302
|
+
? await client.query(`INSERT INTO "persons" ("_orgId", "first_name", "last_name", "is_agent", "is_client", "is_employee", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
303
|
+
VALUES ($1, 'Admin', 'User', false, false, false, NOW(), 0, NOW(), 0)
|
|
304
|
+
RETURNING "_id"`, [orgId])
|
|
305
|
+
: await client.query(`INSERT INTO "persons" ("first_name", "last_name", "is_agent", "is_client", "is_employee", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
306
|
+
VALUES ('Admin', 'User', false, false, false, NOW(), 0, NOW(), 0)
|
|
307
|
+
RETURNING "_id"`);
|
|
308
|
+
const personId = personResult.rows[0]._id;
|
|
309
|
+
if (isMultiTenant && orgId) {
|
|
310
|
+
await client.query(`INSERT INTO "users" ("_orgId", "email", "display_name", "password", "person_id", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
311
|
+
VALUES ($1, $2, 'Admin User', $3, $4, NOW(), 0, NOW(), 0)`, [orgId, email, hashedPassword, personId]);
|
|
313
312
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
};
|
|
318
|
-
if (isMultiTenant && systemUserContext.organization?._id) {
|
|
319
|
-
personData._orgId = systemUserContext.organization._id;
|
|
313
|
+
else {
|
|
314
|
+
await client.query(`INSERT INTO "users" ("email", "display_name", "password", "person_id", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
315
|
+
VALUES ($1, 'Admin User', $2, $3, NOW(), 0, NOW(), 0)`, [email, hashedPassword, personId]);
|
|
320
316
|
}
|
|
321
|
-
await authService.createUser(systemUserContext, userData, personData);
|
|
322
317
|
}
|
|
323
318
|
finally {
|
|
324
319
|
client.release();
|
|
325
320
|
}
|
|
326
321
|
},
|
|
327
322
|
down: async ({ context: pool }) => {
|
|
328
|
-
if (!
|
|
323
|
+
if (!dbConfig.adminUser?.email)
|
|
329
324
|
return;
|
|
330
|
-
|
|
325
|
+
await pool.query(`DELETE FROM "users" WHERE "email" = $1`, [dbConfig.adminUser.email.toLowerCase()]);
|
|
331
326
|
}
|
|
332
327
|
});
|
|
333
328
|
}
|
|
334
|
-
if (
|
|
329
|
+
if (isAuthEnabled && dbConfig.adminUser) {
|
|
335
330
|
migrations.push({
|
|
336
331
|
name: '00000000000012_data-admin-authorizations',
|
|
337
332
|
up: async ({ context: pool }) => {
|
|
@@ -339,73 +334,75 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
|
|
|
339
334
|
try {
|
|
340
335
|
const database = new PostgresDatabase(client);
|
|
341
336
|
const organizationService = new OrganizationService(database);
|
|
342
|
-
const authService = new AuthService(database);
|
|
343
337
|
const metaOrg = isMultiTenant ? await organizationService.getMetaOrg(EmptyUserContext) : undefined;
|
|
344
338
|
if (isMultiTenant && !metaOrg) {
|
|
345
339
|
throw new Error('Meta organization not found. Ensure meta-org migration ran successfully.');
|
|
346
340
|
}
|
|
347
|
-
const
|
|
348
|
-
|
|
341
|
+
const email = dbConfig.adminUser.email.toLowerCase();
|
|
342
|
+
const userResult = await client.query(`SELECT "_id" FROM "users" WHERE "email" = $1`, [email]);
|
|
343
|
+
const adminUserRow = userResult.rows[0];
|
|
344
|
+
if (!adminUserRow) {
|
|
349
345
|
throw new Error('Admin user not found. Ensure admin-user migration ran successfully.');
|
|
350
346
|
}
|
|
347
|
+
const adminUserId = adminUserRow._id;
|
|
351
348
|
await client.query('BEGIN');
|
|
352
349
|
try {
|
|
353
350
|
const roleResult = isMultiTenant
|
|
354
|
-
? await client.query(`
|
|
355
|
-
INSERT INTO "roles" ("_orgId", "name")
|
|
356
|
-
VALUES ($1, 'admin')
|
|
357
|
-
RETURNING "_id"
|
|
351
|
+
? await client.query(`
|
|
352
|
+
INSERT INTO "roles" ("_orgId", "name")
|
|
353
|
+
VALUES ($1, 'admin')
|
|
354
|
+
RETURNING "_id"
|
|
358
355
|
`, [metaOrg._id])
|
|
359
|
-
: await client.query(`
|
|
360
|
-
INSERT INTO "roles" ("name")
|
|
361
|
-
VALUES ('admin')
|
|
362
|
-
RETURNING "_id"
|
|
356
|
+
: await client.query(`
|
|
357
|
+
INSERT INTO "roles" ("name")
|
|
358
|
+
VALUES ('admin')
|
|
359
|
+
RETURNING "_id"
|
|
363
360
|
`);
|
|
364
361
|
if (roleResult.rowCount === 0) {
|
|
365
362
|
throw new Error('Failed to create admin role');
|
|
366
363
|
}
|
|
367
364
|
const roleId = roleResult.rows[0]._id;
|
|
368
365
|
const userRoleResult = isMultiTenant
|
|
369
|
-
? await client.query(`
|
|
370
|
-
INSERT INTO "user_roles" ("_orgId", "user_id", "role_id", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
371
|
-
VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
|
|
372
|
-
`, [metaOrg._id,
|
|
373
|
-
: await client.query(`
|
|
374
|
-
INSERT INTO "user_roles" ("user_id", "role_id", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
375
|
-
VALUES ($1, $2, NOW(), 0, NOW(), 0)
|
|
376
|
-
`, [
|
|
366
|
+
? await client.query(`
|
|
367
|
+
INSERT INTO "user_roles" ("_orgId", "user_id", "role_id", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
368
|
+
VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
|
|
369
|
+
`, [metaOrg._id, adminUserId, roleId])
|
|
370
|
+
: await client.query(`
|
|
371
|
+
INSERT INTO "user_roles" ("user_id", "role_id", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
372
|
+
VALUES ($1, $2, NOW(), 0, NOW(), 0)
|
|
373
|
+
`, [adminUserId, roleId]);
|
|
377
374
|
if (userRoleResult.rowCount === 0) {
|
|
378
375
|
throw new Error('Failed to create user role');
|
|
379
376
|
}
|
|
380
377
|
const featureResult = isMultiTenant
|
|
381
|
-
? await client.query(`
|
|
382
|
-
INSERT INTO "features" ("_orgId", "name")
|
|
383
|
-
VALUES ($1, 'admin')
|
|
384
|
-
RETURNING "_id"
|
|
378
|
+
? await client.query(`
|
|
379
|
+
INSERT INTO "features" ("_orgId", "name")
|
|
380
|
+
VALUES ($1, 'admin')
|
|
381
|
+
RETURNING "_id"
|
|
385
382
|
`, [metaOrg._id])
|
|
386
|
-
: await client.query(`
|
|
387
|
-
INSERT INTO "features" ("name")
|
|
388
|
-
VALUES ('admin')
|
|
389
|
-
RETURNING "_id"
|
|
383
|
+
: await client.query(`
|
|
384
|
+
INSERT INTO "features" ("name")
|
|
385
|
+
VALUES ('admin')
|
|
386
|
+
RETURNING "_id"
|
|
390
387
|
`);
|
|
391
388
|
if (featureResult.rowCount === 0) {
|
|
392
389
|
throw new Error('Failed to create admin feature');
|
|
393
390
|
}
|
|
394
391
|
const featureId = featureResult.rows[0]._id;
|
|
395
392
|
const authorizationResult = isMultiTenant
|
|
396
|
-
? await client.query(`
|
|
397
|
-
INSERT INTO "authorizations" (
|
|
398
|
-
"_orgId", "role_id", "feature_id",
|
|
399
|
-
"_created", "_createdBy", "_updated", "_updatedBy"
|
|
400
|
-
)
|
|
401
|
-
VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
|
|
393
|
+
? await client.query(`
|
|
394
|
+
INSERT INTO "authorizations" (
|
|
395
|
+
"_orgId", "role_id", "feature_id",
|
|
396
|
+
"_created", "_createdBy", "_updated", "_updatedBy"
|
|
397
|
+
)
|
|
398
|
+
VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
|
|
402
399
|
`, [metaOrg._id, roleId, featureId])
|
|
403
|
-
: await client.query(`
|
|
404
|
-
INSERT INTO "authorizations" (
|
|
405
|
-
"role_id", "feature_id",
|
|
406
|
-
"_created", "_createdBy", "_updated", "_updatedBy"
|
|
407
|
-
)
|
|
408
|
-
VALUES ($1, $2, NOW(), 0, NOW(), 0)
|
|
400
|
+
: await client.query(`
|
|
401
|
+
INSERT INTO "authorizations" (
|
|
402
|
+
"role_id", "feature_id",
|
|
403
|
+
"_created", "_createdBy", "_updated", "_updatedBy"
|
|
404
|
+
)
|
|
405
|
+
VALUES ($1, $2, NOW(), 0, NOW(), 0)
|
|
409
406
|
`, [roleId, featureId]);
|
|
410
407
|
if (authorizationResult.rowCount === 0) {
|
|
411
408
|
throw new Error('Failed to create admin authorization');
|
|
@@ -432,61 +429,61 @@ export const getPostgresInitialSchema = (config, resetConfig) => {
|
|
|
432
429
|
await client.query('BEGIN');
|
|
433
430
|
try {
|
|
434
431
|
if (isMultiTenant) {
|
|
435
|
-
await client.query(`
|
|
436
|
-
DELETE FROM "authorizations"
|
|
437
|
-
WHERE "_orgId" = $1
|
|
438
|
-
AND "feature_id" IN (
|
|
439
|
-
SELECT "_id" FROM "features"
|
|
440
|
-
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
441
|
-
)
|
|
442
|
-
AND "role_id" IN (
|
|
443
|
-
SELECT "_id" FROM "roles"
|
|
444
|
-
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
445
|
-
)
|
|
432
|
+
await client.query(`
|
|
433
|
+
DELETE FROM "authorizations"
|
|
434
|
+
WHERE "_orgId" = $1
|
|
435
|
+
AND "feature_id" IN (
|
|
436
|
+
SELECT "_id" FROM "features"
|
|
437
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
438
|
+
)
|
|
439
|
+
AND "role_id" IN (
|
|
440
|
+
SELECT "_id" FROM "roles"
|
|
441
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
442
|
+
)
|
|
446
443
|
`, [metaOrg._id]);
|
|
447
|
-
await client.query(`
|
|
448
|
-
DELETE FROM "features"
|
|
449
|
-
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
444
|
+
await client.query(`
|
|
445
|
+
DELETE FROM "features"
|
|
446
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
450
447
|
`, [metaOrg._id]);
|
|
451
|
-
await client.query(`
|
|
452
|
-
DELETE FROM "user_roles"
|
|
453
|
-
WHERE "_orgId" = $1
|
|
454
|
-
AND "role_id" IN (
|
|
455
|
-
SELECT "_id" FROM "roles"
|
|
456
|
-
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
457
|
-
)
|
|
448
|
+
await client.query(`
|
|
449
|
+
DELETE FROM "user_roles"
|
|
450
|
+
WHERE "_orgId" = $1
|
|
451
|
+
AND "role_id" IN (
|
|
452
|
+
SELECT "_id" FROM "roles"
|
|
453
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
454
|
+
)
|
|
458
455
|
`, [metaOrg._id]);
|
|
459
|
-
await client.query(`
|
|
460
|
-
DELETE FROM "roles"
|
|
461
|
-
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
456
|
+
await client.query(`
|
|
457
|
+
DELETE FROM "roles"
|
|
458
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
462
459
|
`, [metaOrg._id]);
|
|
463
460
|
}
|
|
464
461
|
else {
|
|
465
|
-
await client.query(`
|
|
466
|
-
DELETE FROM "authorizations"
|
|
467
|
-
WHERE "feature_id" IN (
|
|
468
|
-
SELECT "_id" FROM "features"
|
|
469
|
-
WHERE "name" = 'admin'
|
|
470
|
-
)
|
|
471
|
-
AND "role_id" IN (
|
|
472
|
-
SELECT "_id" FROM "roles"
|
|
473
|
-
WHERE "name" = 'admin'
|
|
474
|
-
)
|
|
462
|
+
await client.query(`
|
|
463
|
+
DELETE FROM "authorizations"
|
|
464
|
+
WHERE "feature_id" IN (
|
|
465
|
+
SELECT "_id" FROM "features"
|
|
466
|
+
WHERE "name" = 'admin'
|
|
467
|
+
)
|
|
468
|
+
AND "role_id" IN (
|
|
469
|
+
SELECT "_id" FROM "roles"
|
|
470
|
+
WHERE "name" = 'admin'
|
|
471
|
+
)
|
|
475
472
|
`);
|
|
476
|
-
await client.query(`
|
|
477
|
-
DELETE FROM "features"
|
|
478
|
-
WHERE "name" = 'admin'
|
|
473
|
+
await client.query(`
|
|
474
|
+
DELETE FROM "features"
|
|
475
|
+
WHERE "name" = 'admin'
|
|
479
476
|
`);
|
|
480
|
-
await client.query(`
|
|
481
|
-
DELETE FROM "user_roles"
|
|
482
|
-
WHERE "role_id" IN (
|
|
483
|
-
SELECT "_id" FROM "roles"
|
|
484
|
-
WHERE "name" = 'admin'
|
|
485
|
-
)
|
|
477
|
+
await client.query(`
|
|
478
|
+
DELETE FROM "user_roles"
|
|
479
|
+
WHERE "role_id" IN (
|
|
480
|
+
SELECT "_id" FROM "roles"
|
|
481
|
+
WHERE "name" = 'admin'
|
|
482
|
+
)
|
|
486
483
|
`);
|
|
487
|
-
await client.query(`
|
|
488
|
-
DELETE FROM "roles"
|
|
489
|
-
WHERE "name" = 'admin'
|
|
484
|
+
await client.query(`
|
|
485
|
+
DELETE FROM "roles"
|
|
486
|
+
WHERE "name" = 'admin'
|
|
490
487
|
`);
|
|
491
488
|
}
|
|
492
489
|
await client.query('COMMIT');
|