@loomcore/api 0.1.59 → 0.1.62
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 +49 -49
- package/dist/__tests__/common-test.utils.js +10 -8
- package/dist/__tests__/postgres-test-migrations/postgres-test-schema.js +52 -52
- package/dist/__tests__/postgres.test-database.js +8 -8
- package/dist/__tests__/test-email-client.d.ts +4 -0
- package/dist/__tests__/test-email-client.js +6 -0
- package/dist/controllers/auth.controller.d.ts +2 -1
- package/dist/controllers/auth.controller.js +2 -3
- package/dist/databases/migrations/migration-runner.d.ts +3 -1
- package/dist/databases/migrations/migration-runner.js +27 -25
- package/dist/databases/mongo-db/migrations/mongo-initial-schema.d.ts +3 -2
- package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +127 -125
- 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 +2 -1
- package/dist/databases/postgres/migrations/postgres-initial-schema.d.ts +2 -1
- package/dist/databases/postgres/migrations/postgres-initial-schema.js +284 -281
- package/dist/databases/postgres/postgres.database.js +17 -17
- 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/base-api-config.interface.d.ts +7 -10
- package/dist/models/email-client.interface.d.ts +3 -0
- package/dist/models/email-client.interface.js +1 -0
- package/dist/models/email-config.interface.d.ts +4 -0
- package/dist/models/email-config.interface.js +1 -0
- package/dist/models/multi-tenant-config.interface.d.ts +4 -0
- package/dist/models/multi-tenant-config.interface.js +1 -0
- package/dist/services/auth.service.d.ts +2 -1
- package/dist/services/auth.service.js +2 -2
- package/dist/services/email.service.d.ts +5 -4
- package/dist/services/email.service.js +12 -20
- package/package.json +88 -88
|
@@ -1,37 +1,39 @@
|
|
|
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, emailClient) => {
|
|
5
5
|
const migrations = [];
|
|
6
|
-
const isMultiTenant = config.app.isMultiTenant
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
const isMultiTenant = config.app.isMultiTenant;
|
|
7
|
+
if (isMultiTenant && !config.multiTenant) {
|
|
8
|
+
throw new Error('Multi-tenant configuration is enabled but multi-tenant configuration is not provided');
|
|
9
|
+
}
|
|
10
|
+
const isAuthEnabled = config.app.isAuthEnabled;
|
|
11
|
+
if (isAuthEnabled && !config.auth) {
|
|
12
|
+
throw new Error('Auth enabled without auth configuration');
|
|
13
|
+
}
|
|
14
|
+
if (isAuthEnabled && (!emailClient || !config.email)) {
|
|
15
|
+
throw new Error('Auth enabled without email client or email configuration');
|
|
16
|
+
}
|
|
15
17
|
if (isMultiTenant) {
|
|
16
18
|
migrations.push({
|
|
17
19
|
name: '00000000000001_schema-organizations',
|
|
18
20
|
up: async ({ context: pool }) => {
|
|
19
|
-
await pool.query(`
|
|
20
|
-
CREATE TABLE IF NOT EXISTS "organizations" (
|
|
21
|
-
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
22
|
-
"name" VARCHAR(255) NOT NULL UNIQUE,
|
|
23
|
-
"code" VARCHAR(255) NOT NULL UNIQUE,
|
|
24
|
-
"description" TEXT,
|
|
25
|
-
"status" INTEGER NOT NULL,
|
|
26
|
-
"isMetaOrg" BOOLEAN NOT NULL,
|
|
27
|
-
"authToken" TEXT,
|
|
28
|
-
"_created" TIMESTAMPTZ NOT NULL,
|
|
29
|
-
"_createdBy" INTEGER NOT NULL,
|
|
30
|
-
"_updated" TIMESTAMPTZ NOT NULL,
|
|
31
|
-
"_updatedBy" INTEGER NOT NULL,
|
|
32
|
-
"_deleted" TIMESTAMPTZ,
|
|
33
|
-
"_deletedBy" INTEGER
|
|
34
|
-
)
|
|
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
|
+
"isMetaOrg" BOOLEAN NOT NULL,
|
|
29
|
+
"authToken" 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
|
+
)
|
|
35
37
|
`);
|
|
36
38
|
},
|
|
37
39
|
down: async ({ context: pool }) => {
|
|
@@ -39,172 +41,178 @@ export const getPostgresInitialSchema = (config) => {
|
|
|
39
41
|
}
|
|
40
42
|
});
|
|
41
43
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
44
|
+
if (isAuthEnabled)
|
|
45
|
+
migrations.push({
|
|
46
|
+
name: '00000000000002_schema-users',
|
|
47
|
+
up: async ({ context: pool }) => {
|
|
48
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
49
|
+
const uniqueConstraint = isMultiTenant
|
|
50
|
+
? 'CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")'
|
|
51
|
+
: 'CONSTRAINT "uk_users_email" UNIQUE ("email")';
|
|
52
|
+
await pool.query(`
|
|
53
|
+
CREATE TABLE IF NOT EXISTS "users" (
|
|
54
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
55
|
+
${orgColumnDef}
|
|
56
|
+
"email" VARCHAR(255) NOT NULL,
|
|
57
|
+
"firstName" VARCHAR(255),
|
|
58
|
+
"lastName" VARCHAR(255),
|
|
59
|
+
"displayName" VARCHAR(255),
|
|
60
|
+
"password" VARCHAR(255) NOT NULL,
|
|
61
|
+
"_lastLoggedIn" TIMESTAMPTZ,
|
|
62
|
+
"_lastPasswordChange" TIMESTAMPTZ,
|
|
63
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
64
|
+
"_createdBy" INTEGER NOT NULL,
|
|
65
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
66
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
67
|
+
"_deleted" TIMESTAMPTZ,
|
|
68
|
+
"_deletedBy" INTEGER,
|
|
69
|
+
${uniqueConstraint}
|
|
70
|
+
)
|
|
68
71
|
`);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
|
|
89
|
-
|
|
72
|
+
},
|
|
73
|
+
down: async ({ context: pool }) => {
|
|
74
|
+
await pool.query('DROP TABLE IF EXISTS "users"');
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
if (isAuthEnabled)
|
|
78
|
+
migrations.push({
|
|
79
|
+
name: '00000000000003_schema-refresh-tokens',
|
|
80
|
+
up: async ({ context: pool }) => {
|
|
81
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
82
|
+
await pool.query(`
|
|
83
|
+
CREATE TABLE IF NOT EXISTS "refreshTokens" (
|
|
84
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
85
|
+
${orgColumnDef}
|
|
86
|
+
"token" VARCHAR(255) NOT NULL,
|
|
87
|
+
"deviceId" VARCHAR(255) NOT NULL,
|
|
88
|
+
"userId" INTEGER NOT NULL,
|
|
89
|
+
"expiresOn" BIGINT NOT NULL,
|
|
90
|
+
"created" TIMESTAMPTZ NOT NULL,
|
|
91
|
+
"createdBy" INTEGER NOT NULL,
|
|
92
|
+
CONSTRAINT "fk_refreshTokens_user" FOREIGN KEY ("userId") REFERENCES "users"("_id") ON DELETE CASCADE
|
|
93
|
+
)
|
|
90
94
|
`);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
|
|
95
|
+
},
|
|
96
|
+
down: async ({ context: pool }) => {
|
|
97
|
+
await pool.query('DROP TABLE IF EXISTS "refreshTokens"');
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
if (isAuthEnabled)
|
|
101
|
+
migrations.push({
|
|
102
|
+
name: '00000000000004_schema-roles',
|
|
103
|
+
up: async ({ context: pool }) => {
|
|
104
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
105
|
+
const uniqueConstraint = isMultiTenant
|
|
106
|
+
? 'CONSTRAINT "uk_roles_name" UNIQUE ("_orgId", "name")'
|
|
107
|
+
: 'CONSTRAINT "uk_roles_name" UNIQUE ("name")';
|
|
108
|
+
await pool.query(`
|
|
109
|
+
CREATE TABLE IF NOT EXISTS "roles" (
|
|
110
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
111
|
+
${orgColumnDef}
|
|
112
|
+
"name" VARCHAR(255) NOT NULL,
|
|
113
|
+
"description" TEXT,
|
|
114
|
+
${uniqueConstraint}
|
|
115
|
+
)
|
|
111
116
|
`);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
|
|
137
|
-
CONSTRAINT "
|
|
138
|
-
|
|
139
|
-
|
|
117
|
+
},
|
|
118
|
+
down: async ({ context: pool }) => {
|
|
119
|
+
await pool.query('DROP TABLE IF EXISTS "roles"');
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
if (isAuthEnabled)
|
|
123
|
+
migrations.push({
|
|
124
|
+
name: '00000000000005_schema-user-roles',
|
|
125
|
+
up: async ({ context: pool }) => {
|
|
126
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
127
|
+
const uniqueConstraint = isMultiTenant
|
|
128
|
+
? 'CONSTRAINT "uk_user_roles" UNIQUE ("_orgId", "userId", "roleId")'
|
|
129
|
+
: 'CONSTRAINT "uk_user_roles" UNIQUE ("userId", "roleId")';
|
|
130
|
+
await pool.query(`
|
|
131
|
+
CREATE TABLE IF NOT EXISTS "user_roles" (
|
|
132
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
133
|
+
${orgColumnDef}
|
|
134
|
+
"userId" INTEGER NOT NULL,
|
|
135
|
+
"roleId" INTEGER NOT NULL,
|
|
136
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
137
|
+
"_createdBy" INTEGER NOT NULL,
|
|
138
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
139
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
140
|
+
"_deleted" TIMESTAMPTZ,
|
|
141
|
+
"_deletedBy" INTEGER,
|
|
142
|
+
CONSTRAINT "fk_user_roles_user" FOREIGN KEY ("userId") REFERENCES "users"("_id") ON DELETE CASCADE,
|
|
143
|
+
CONSTRAINT "fk_user_roles_role" FOREIGN KEY ("roleId") REFERENCES "roles"("_id") ON DELETE CASCADE,
|
|
144
|
+
${uniqueConstraint}
|
|
145
|
+
)
|
|
140
146
|
`);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
"
|
|
159
|
-
|
|
160
|
-
|
|
147
|
+
},
|
|
148
|
+
down: async ({ context: pool }) => {
|
|
149
|
+
await pool.query('DROP TABLE IF EXISTS "user_roles"');
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
if (isAuthEnabled)
|
|
153
|
+
migrations.push({
|
|
154
|
+
name: '00000000000006_schema-features',
|
|
155
|
+
up: async ({ context: pool }) => {
|
|
156
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
157
|
+
const uniqueConstraint = isMultiTenant
|
|
158
|
+
? 'CONSTRAINT "uk_features" UNIQUE ("_orgId", "name")'
|
|
159
|
+
: 'CONSTRAINT "uk_features" UNIQUE ("name")';
|
|
160
|
+
await pool.query(`
|
|
161
|
+
CREATE TABLE IF NOT EXISTS "features" (
|
|
162
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
163
|
+
${orgColumnDef}
|
|
164
|
+
"name" VARCHAR(255) NOT NULL,
|
|
165
|
+
"description" TEXT,
|
|
166
|
+
${uniqueConstraint}
|
|
167
|
+
)
|
|
161
168
|
`);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
"
|
|
184
|
-
"
|
|
185
|
-
"
|
|
186
|
-
"
|
|
187
|
-
"
|
|
188
|
-
"
|
|
189
|
-
|
|
190
|
-
CONSTRAINT "
|
|
191
|
-
|
|
192
|
-
|
|
169
|
+
},
|
|
170
|
+
down: async ({ context: pool }) => {
|
|
171
|
+
await pool.query('DROP TABLE IF EXISTS "features"');
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
if (isAuthEnabled)
|
|
175
|
+
migrations.push({
|
|
176
|
+
name: '00000000000007_schema-authorizations',
|
|
177
|
+
up: async ({ context: pool }) => {
|
|
178
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
179
|
+
const uniqueConstraint = isMultiTenant
|
|
180
|
+
? 'CONSTRAINT "uk_authorizations" UNIQUE ("_orgId", "roleId", "featureId")'
|
|
181
|
+
: 'CONSTRAINT "uk_authorizations" UNIQUE ("roleId", "featureId")';
|
|
182
|
+
await pool.query(`
|
|
183
|
+
CREATE TABLE IF NOT EXISTS "authorizations" (
|
|
184
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
185
|
+
${orgColumnDef}
|
|
186
|
+
"roleId" INTEGER NOT NULL,
|
|
187
|
+
"featureId" INTEGER NOT NULL,
|
|
188
|
+
"startDate" TIMESTAMPTZ,
|
|
189
|
+
"endDate" TIMESTAMPTZ,
|
|
190
|
+
"config" JSONB,
|
|
191
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
192
|
+
"_createdBy" INTEGER NOT NULL,
|
|
193
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
194
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
195
|
+
"_deleted" TIMESTAMPTZ,
|
|
196
|
+
"_deletedBy" INTEGER,
|
|
197
|
+
CONSTRAINT "fk_authorizations_role" FOREIGN KEY ("roleId") REFERENCES "roles"("_id") ON DELETE CASCADE,
|
|
198
|
+
CONSTRAINT "fk_authorizations_feature" FOREIGN KEY ("featureId") REFERENCES "features"("_id") ON DELETE CASCADE,
|
|
199
|
+
${uniqueConstraint}
|
|
200
|
+
)
|
|
193
201
|
`);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (isMultiTenant
|
|
202
|
+
},
|
|
203
|
+
down: async ({ context: pool }) => {
|
|
204
|
+
await pool.query('DROP TABLE IF EXISTS "authorizations"');
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
if (isMultiTenant) {
|
|
200
208
|
migrations.push({
|
|
201
209
|
name: '00000000000008_data-meta-org',
|
|
202
210
|
up: async ({ context: pool }) => {
|
|
203
|
-
const result = await pool.query(`
|
|
204
|
-
INSERT INTO "organizations" ("name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
205
|
-
VALUES ($1, $2, 1, true, NOW(), 0, NOW(), 0)
|
|
206
|
-
RETURNING "_id", "name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy"
|
|
207
|
-
`, [config.
|
|
211
|
+
const result = await pool.query(`
|
|
212
|
+
INSERT INTO "organizations" ("name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
213
|
+
VALUES ($1, $2, 1, true, NOW(), 0, NOW(), 0)
|
|
214
|
+
RETURNING "_id", "name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy"
|
|
215
|
+
`, [config.multiTenant?.metaOrgName, config.multiTenant?.metaOrgCode]);
|
|
208
216
|
if (result.rowCount === 0) {
|
|
209
217
|
throw new Error('Failed to create meta organization');
|
|
210
218
|
}
|
|
@@ -215,24 +223,22 @@ export const getPostgresInitialSchema = (config) => {
|
|
|
215
223
|
}
|
|
216
224
|
});
|
|
217
225
|
}
|
|
218
|
-
if (
|
|
226
|
+
if (isAuthEnabled) {
|
|
219
227
|
migrations.push({
|
|
220
228
|
name: '00000000000009_data-admin-user',
|
|
221
229
|
up: async ({ context: pool }) => {
|
|
222
|
-
if (!config.auth?.adminUser?.email || !config.auth?.adminUser?.password) {
|
|
223
|
-
throw new Error('Admin user email and password must be provided in config');
|
|
224
|
-
}
|
|
225
230
|
const client = await pool.connect();
|
|
226
231
|
try {
|
|
227
232
|
const database = new PostgresDatabase(client);
|
|
228
|
-
const authService = new AuthService(database);
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
'
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
const authService = new AuthService(database, emailClient);
|
|
234
|
+
if (!isSystemUserContextInitialized()) {
|
|
235
|
+
const errorMessage = isMultiTenant
|
|
236
|
+
? 'SystemUserContext has not been initialized. The meta-org migration (00000000000008_data-meta-org) should have run before this migration. ' +
|
|
237
|
+
'This migration only runs if config.app.metaOrgName and config.app.metaOrgCode are provided. ' +
|
|
238
|
+
'Please ensure both values are set in your config.'
|
|
239
|
+
: 'BUG: SystemUserContext has not been initialized. For non-multi-tenant setups, SystemUserContext should be initialized before migrations run.';
|
|
240
|
+
console.error('❌ Migration Error:', errorMessage);
|
|
241
|
+
throw new Error(errorMessage);
|
|
236
242
|
}
|
|
237
243
|
const systemUserContext = getSystemUserContext();
|
|
238
244
|
const userData = {
|
|
@@ -258,84 +264,81 @@ export const getPostgresInitialSchema = (config) => {
|
|
|
258
264
|
}
|
|
259
265
|
});
|
|
260
266
|
}
|
|
261
|
-
if (config.auth
|
|
267
|
+
if (config.auth) {
|
|
262
268
|
migrations.push({
|
|
263
269
|
name: '00000000000010_data-admin-authorizations',
|
|
264
270
|
up: async ({ context: pool }) => {
|
|
265
|
-
if (!config.auth?.adminUser?.email) {
|
|
266
|
-
throw new Error('Admin user email not found in config');
|
|
267
|
-
}
|
|
268
271
|
const client = await pool.connect();
|
|
269
272
|
try {
|
|
270
273
|
const database = new PostgresDatabase(client);
|
|
271
274
|
const organizationService = new OrganizationService(database);
|
|
272
|
-
const authService = new AuthService(database);
|
|
275
|
+
const authService = new AuthService(database, emailClient);
|
|
273
276
|
const metaOrg = isMultiTenant ? await organizationService.getMetaOrg(EmptyUserContext) : undefined;
|
|
274
277
|
if (isMultiTenant && !metaOrg) {
|
|
275
278
|
throw new Error('Meta organization not found. Ensure meta-org migration ran successfully.');
|
|
276
279
|
}
|
|
277
|
-
const adminUser = await authService.getUserByEmail(config.auth
|
|
280
|
+
const adminUser = await authService.getUserByEmail(config.auth.adminUser.email);
|
|
278
281
|
if (!adminUser) {
|
|
279
282
|
throw new Error('Admin user not found. Ensure admin-user migration ran successfully.');
|
|
280
283
|
}
|
|
281
284
|
await client.query('BEGIN');
|
|
282
285
|
try {
|
|
283
286
|
const roleResult = isMultiTenant
|
|
284
|
-
? await client.query(`
|
|
285
|
-
INSERT INTO "roles" ("_orgId", "name")
|
|
286
|
-
VALUES ($1, 'admin')
|
|
287
|
-
RETURNING "_id"
|
|
287
|
+
? await client.query(`
|
|
288
|
+
INSERT INTO "roles" ("_orgId", "name")
|
|
289
|
+
VALUES ($1, 'admin')
|
|
290
|
+
RETURNING "_id"
|
|
288
291
|
`, [metaOrg._id])
|
|
289
|
-
: await client.query(`
|
|
290
|
-
INSERT INTO "roles" ("name")
|
|
291
|
-
VALUES ('admin')
|
|
292
|
-
RETURNING "_id"
|
|
292
|
+
: await client.query(`
|
|
293
|
+
INSERT INTO "roles" ("name")
|
|
294
|
+
VALUES ('admin')
|
|
295
|
+
RETURNING "_id"
|
|
293
296
|
`);
|
|
294
297
|
if (roleResult.rowCount === 0) {
|
|
295
298
|
throw new Error('Failed to create admin role');
|
|
296
299
|
}
|
|
297
300
|
const roleId = roleResult.rows[0]._id;
|
|
298
301
|
const userRoleResult = isMultiTenant
|
|
299
|
-
? await client.query(`
|
|
300
|
-
INSERT INTO "user_roles" ("_orgId", "userId", "roleId", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
301
|
-
VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
|
|
302
|
+
? await client.query(`
|
|
303
|
+
INSERT INTO "user_roles" ("_orgId", "userId", "roleId", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
304
|
+
VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
|
|
302
305
|
`, [metaOrg._id, adminUser._id, roleId])
|
|
303
|
-
: await client.query(`
|
|
304
|
-
INSERT INTO "user_roles" ("userId", "roleId", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
305
|
-
VALUES ($1, $2, NOW(), 0, NOW(), 0)
|
|
306
|
+
: await client.query(`
|
|
307
|
+
INSERT INTO "user_roles" ("userId", "roleId", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
308
|
+
VALUES ($1, $2, NOW(), 0, NOW(), 0)
|
|
306
309
|
`, [adminUser._id, roleId]);
|
|
307
310
|
if (userRoleResult.rowCount === 0) {
|
|
308
311
|
throw new Error('Failed to create user role');
|
|
309
312
|
}
|
|
310
313
|
const featureResult = isMultiTenant
|
|
311
|
-
? await client.query(`
|
|
312
|
-
INSERT INTO "features" ("_orgId", "name")
|
|
313
|
-
VALUES ($1, 'admin')
|
|
314
|
-
RETURNING "_id"
|
|
314
|
+
? await client.query(`
|
|
315
|
+
INSERT INTO "features" ("_orgId", "name")
|
|
316
|
+
VALUES ($1, 'admin')
|
|
317
|
+
RETURNING "_id"
|
|
315
318
|
`, [metaOrg._id])
|
|
316
|
-
: await client.query(`
|
|
317
|
-
INSERT INTO "features" ("name")
|
|
318
|
-
VALUES ('admin')
|
|
319
|
-
RETURNING "_id"
|
|
319
|
+
: await client.query(`
|
|
320
|
+
INSERT INTO "features" ("name")
|
|
321
|
+
VALUES ('admin')
|
|
322
|
+
RETURNING "_id"
|
|
320
323
|
`);
|
|
321
324
|
if (featureResult.rowCount === 0) {
|
|
322
325
|
throw new Error('Failed to create admin feature');
|
|
323
326
|
}
|
|
324
327
|
const featureId = featureResult.rows[0]._id;
|
|
325
328
|
const authorizationResult = isMultiTenant
|
|
326
|
-
? await client.query(`
|
|
327
|
-
INSERT INTO "authorizations" (
|
|
328
|
-
"_orgId", "roleId", "featureId",
|
|
329
|
-
"_created", "_createdBy", "_updated", "_updatedBy"
|
|
330
|
-
)
|
|
331
|
-
VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
|
|
329
|
+
? await client.query(`
|
|
330
|
+
INSERT INTO "authorizations" (
|
|
331
|
+
"_orgId", "roleId", "featureId",
|
|
332
|
+
"_created", "_createdBy", "_updated", "_updatedBy"
|
|
333
|
+
)
|
|
334
|
+
VALUES ($1, $2, $3, NOW(), 0, NOW(), 0)
|
|
332
335
|
`, [metaOrg._id, roleId, featureId])
|
|
333
|
-
: await client.query(`
|
|
334
|
-
INSERT INTO "authorizations" (
|
|
335
|
-
"roleId", "featureId",
|
|
336
|
-
"_created", "_createdBy", "_updated", "_updatedBy"
|
|
337
|
-
)
|
|
338
|
-
VALUES ($1, $2, NOW(), 0, NOW(), 0)
|
|
336
|
+
: await client.query(`
|
|
337
|
+
INSERT INTO "authorizations" (
|
|
338
|
+
"roleId", "featureId",
|
|
339
|
+
"_created", "_createdBy", "_updated", "_updatedBy"
|
|
340
|
+
)
|
|
341
|
+
VALUES ($1, $2, NOW(), 0, NOW(), 0)
|
|
339
342
|
`, [roleId, featureId]);
|
|
340
343
|
if (authorizationResult.rowCount === 0) {
|
|
341
344
|
throw new Error('Failed to create admin authorization');
|
|
@@ -362,61 +365,61 @@ export const getPostgresInitialSchema = (config) => {
|
|
|
362
365
|
await client.query('BEGIN');
|
|
363
366
|
try {
|
|
364
367
|
if (isMultiTenant) {
|
|
365
|
-
await client.query(`
|
|
366
|
-
DELETE FROM "authorizations"
|
|
367
|
-
WHERE "_orgId" = $1
|
|
368
|
-
AND "featureId" IN (
|
|
369
|
-
SELECT "_id" FROM "features"
|
|
370
|
-
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
371
|
-
)
|
|
372
|
-
AND "roleId" IN (
|
|
373
|
-
SELECT "_id" FROM "roles"
|
|
374
|
-
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
375
|
-
)
|
|
368
|
+
await client.query(`
|
|
369
|
+
DELETE FROM "authorizations"
|
|
370
|
+
WHERE "_orgId" = $1
|
|
371
|
+
AND "featureId" IN (
|
|
372
|
+
SELECT "_id" FROM "features"
|
|
373
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
374
|
+
)
|
|
375
|
+
AND "roleId" IN (
|
|
376
|
+
SELECT "_id" FROM "roles"
|
|
377
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
378
|
+
)
|
|
376
379
|
`, [metaOrg._id]);
|
|
377
|
-
await client.query(`
|
|
378
|
-
DELETE FROM "features"
|
|
379
|
-
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
380
|
+
await client.query(`
|
|
381
|
+
DELETE FROM "features"
|
|
382
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
380
383
|
`, [metaOrg._id]);
|
|
381
|
-
await client.query(`
|
|
382
|
-
DELETE FROM "user_roles"
|
|
383
|
-
WHERE "_orgId" = $1
|
|
384
|
-
AND "roleId" IN (
|
|
385
|
-
SELECT "_id" FROM "roles"
|
|
386
|
-
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
387
|
-
)
|
|
384
|
+
await client.query(`
|
|
385
|
+
DELETE FROM "user_roles"
|
|
386
|
+
WHERE "_orgId" = $1
|
|
387
|
+
AND "roleId" IN (
|
|
388
|
+
SELECT "_id" FROM "roles"
|
|
389
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
390
|
+
)
|
|
388
391
|
`, [metaOrg._id]);
|
|
389
|
-
await client.query(`
|
|
390
|
-
DELETE FROM "roles"
|
|
391
|
-
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
392
|
+
await client.query(`
|
|
393
|
+
DELETE FROM "roles"
|
|
394
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
392
395
|
`, [metaOrg._id]);
|
|
393
396
|
}
|
|
394
397
|
else {
|
|
395
|
-
await client.query(`
|
|
396
|
-
DELETE FROM "authorizations"
|
|
397
|
-
WHERE "featureId" IN (
|
|
398
|
-
SELECT "_id" FROM "features"
|
|
399
|
-
WHERE "name" = 'admin'
|
|
400
|
-
)
|
|
401
|
-
AND "roleId" IN (
|
|
402
|
-
SELECT "_id" FROM "roles"
|
|
403
|
-
WHERE "name" = 'admin'
|
|
404
|
-
)
|
|
398
|
+
await client.query(`
|
|
399
|
+
DELETE FROM "authorizations"
|
|
400
|
+
WHERE "featureId" IN (
|
|
401
|
+
SELECT "_id" FROM "features"
|
|
402
|
+
WHERE "name" = 'admin'
|
|
403
|
+
)
|
|
404
|
+
AND "roleId" IN (
|
|
405
|
+
SELECT "_id" FROM "roles"
|
|
406
|
+
WHERE "name" = 'admin'
|
|
407
|
+
)
|
|
405
408
|
`);
|
|
406
|
-
await client.query(`
|
|
407
|
-
DELETE FROM "features"
|
|
408
|
-
WHERE "name" = 'admin'
|
|
409
|
+
await client.query(`
|
|
410
|
+
DELETE FROM "features"
|
|
411
|
+
WHERE "name" = 'admin'
|
|
409
412
|
`);
|
|
410
|
-
await client.query(`
|
|
411
|
-
DELETE FROM "user_roles"
|
|
412
|
-
WHERE "roleId" IN (
|
|
413
|
-
SELECT "_id" FROM "roles"
|
|
414
|
-
WHERE "name" = 'admin'
|
|
415
|
-
)
|
|
413
|
+
await client.query(`
|
|
414
|
+
DELETE FROM "user_roles"
|
|
415
|
+
WHERE "roleId" IN (
|
|
416
|
+
SELECT "_id" FROM "roles"
|
|
417
|
+
WHERE "name" = 'admin'
|
|
418
|
+
)
|
|
416
419
|
`);
|
|
417
|
-
await client.query(`
|
|
418
|
-
DELETE FROM "roles"
|
|
419
|
-
WHERE "name" = 'admin'
|
|
420
|
+
await client.query(`
|
|
421
|
+
DELETE FROM "roles"
|
|
422
|
+
WHERE "name" = 'admin'
|
|
420
423
|
`);
|
|
421
424
|
}
|
|
422
425
|
await client.query('COMMIT');
|