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