@loomcore/api 0.1.53 → 0.1.57
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/dist/__tests__/common-test.utils.d.ts +8 -2
- package/dist/__tests__/common-test.utils.js +50 -16
- package/dist/__tests__/index.d.ts +0 -1
- package/dist/__tests__/index.js +0 -1
- package/dist/__tests__/postgres-test-migrations/postgres-test-schema.d.ts +3 -0
- package/dist/__tests__/postgres-test-migrations/postgres-test-schema.js +97 -0
- package/dist/__tests__/postgres.test-database.js +9 -8
- package/dist/__tests__/setup/vitest-setup.js +15 -0
- package/dist/__tests__/test-objects.d.ts +6 -3
- package/dist/__tests__/test-objects.js +10 -2
- package/dist/controllers/api.controller.d.ts +2 -0
- package/dist/controllers/api.controller.js +66 -11
- package/dist/databases/migrations/migration-runner.js +6 -6
- package/dist/databases/models/database.interface.d.ts +9 -8
- package/dist/databases/mongo-db/commands/mongo-delete-by-id.command.d.ts +2 -1
- package/dist/databases/mongo-db/commands/mongo-full-updateby-id.command.d.ts +2 -1
- package/dist/databases/mongo-db/commands/mongo-partial-update-by-id.command.d.ts +2 -1
- package/dist/databases/mongo-db/migrations/{mongo-foundational.d.ts → mongo-initial-schema.d.ts} +1 -1
- package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +264 -0
- package/dist/databases/mongo-db/mongo-db.database.d.ts +9 -8
- package/dist/databases/mongo-db/mongo-db.database.js +2 -2
- package/dist/databases/mongo-db/queries/mongo-get-by-id.query.d.ts +2 -1
- package/dist/databases/postgres/commands/postgres-batch-update.command.js +1 -1
- package/dist/databases/postgres/commands/postgres-create-many.command.d.ts +2 -1
- package/dist/databases/postgres/commands/postgres-create-many.command.js +23 -28
- package/dist/databases/postgres/commands/postgres-create.command.d.ts +2 -1
- package/dist/databases/postgres/commands/postgres-create.command.js +6 -5
- package/dist/databases/postgres/commands/postgres-delete-by-id.command.d.ts +2 -1
- package/dist/databases/postgres/commands/postgres-full-update-by-id.command.d.ts +2 -1
- package/dist/databases/postgres/commands/postgres-partial-update-by-id.command.d.ts +2 -1
- package/dist/databases/postgres/migrations/__tests__/test-migration-helper.d.ts +4 -0
- package/dist/databases/postgres/migrations/__tests__/test-migration-helper.js +67 -0
- package/dist/databases/postgres/migrations/index.d.ts +1 -2
- package/dist/databases/postgres/migrations/index.js +1 -2
- package/dist/databases/postgres/migrations/{postgres-foundational.d.ts → postgres-initial-schema.d.ts} +1 -1
- package/dist/databases/postgres/migrations/postgres-initial-schema.js +436 -0
- package/dist/databases/postgres/postgres.database.d.ts +10 -9
- package/dist/databases/postgres/postgres.database.js +2 -2
- package/dist/databases/postgres/queries/postgres-get-by-id.query.d.ts +2 -1
- package/dist/models/refresh-token.model.d.ts +5 -4
- package/dist/models/refresh-token.model.js +3 -3
- package/dist/services/auth.service.d.ts +3 -2
- package/dist/services/auth.service.js +4 -4
- package/dist/services/generic-api-service/generic-api-service.interface.d.ts +8 -7
- package/dist/services/generic-api-service/generic-api.service.d.ts +8 -7
- package/dist/services/generic-api-service/generic-api.service.js +23 -23
- package/dist/services/multi-tenant-api.service.d.ts +1 -1
- package/dist/services/multi-tenant-api.service.js +3 -3
- package/dist/services/organization.service.d.ts +4 -3
- package/dist/services/organization.service.js +3 -3
- package/dist/services/user.service.d.ts +3 -2
- package/dist/services/user.service.js +2 -2
- package/dist/services/utils/audit-for-create.util.js +2 -1
- package/dist/services/utils/audit-for-update.util.js +2 -1
- package/dist/services/utils/strip-sender-provided-system-properties.util.js +2 -1
- package/package.json +2 -2
- package/dist/__tests__/postgres-test-migrations/100-create-test-entities-table.migration.d.ts +0 -21
- package/dist/__tests__/postgres-test-migrations/100-create-test-entities-table.migration.js +0 -61
- package/dist/__tests__/postgres-test-migrations/101-create-categories-table.migration.d.ts +0 -21
- package/dist/__tests__/postgres-test-migrations/101-create-categories-table.migration.js +0 -51
- package/dist/__tests__/postgres-test-migrations/102-create-products-table.migration.d.ts +0 -21
- package/dist/__tests__/postgres-test-migrations/102-create-products-table.migration.js +0 -60
- package/dist/__tests__/postgres-test-migrations/103-create-test-items-table.migration.d.ts +0 -21
- package/dist/__tests__/postgres-test-migrations/103-create-test-items-table.migration.js +0 -59
- package/dist/__tests__/postgres-test-migrations/test-migrations.d.ts +0 -3
- package/dist/__tests__/postgres-test-migrations/test-migrations.js +0 -12
- package/dist/databases/mongo-db/migrations/mongo-foundational.js +0 -30
- package/dist/databases/postgres/migrations/001-create-migrations-table.migration.d.ts +0 -21
- package/dist/databases/postgres/migrations/001-create-migrations-table.migration.js +0 -51
- package/dist/databases/postgres/migrations/002-create-organizations-table.migration.d.ts +0 -21
- package/dist/databases/postgres/migrations/002-create-organizations-table.migration.js +0 -76
- package/dist/databases/postgres/migrations/003-create-users-table.migration.d.ts +0 -21
- package/dist/databases/postgres/migrations/003-create-users-table.migration.js +0 -77
- package/dist/databases/postgres/migrations/004-create-refresh-tokens-table.migration.d.ts +0 -21
- package/dist/databases/postgres/migrations/004-create-refresh-tokens-table.migration.js +0 -72
- package/dist/databases/postgres/migrations/005-create-meta-org.migration.d.ts +0 -22
- package/dist/databases/postgres/migrations/005-create-meta-org.migration.js +0 -64
- package/dist/databases/postgres/migrations/006-create-admin-user.migration.d.ts +0 -19
- package/dist/databases/postgres/migrations/006-create-admin-user.migration.js +0 -52
- package/dist/databases/postgres/migrations/007-create-roles-table.migration.d.ts +0 -21
- package/dist/databases/postgres/migrations/007-create-roles-table.migration.js +0 -65
- package/dist/databases/postgres/migrations/008-create-user-roles-table.migration.d.ts +0 -21
- package/dist/databases/postgres/migrations/008-create-user-roles-table.migration.js +0 -75
- package/dist/databases/postgres/migrations/009-create-features-table.migration.d.ts +0 -21
- package/dist/databases/postgres/migrations/009-create-features-table.migration.js +0 -65
- package/dist/databases/postgres/migrations/010-create-authorizations-table.migration.d.ts +0 -21
- package/dist/databases/postgres/migrations/010-create-authorizations-table.migration.js +0 -77
- package/dist/databases/postgres/migrations/011-create-admin-authorization.migration.d.ts +0 -23
- package/dist/databases/postgres/migrations/011-create-admin-authorization.migration.js +0 -130
- package/dist/databases/postgres/migrations/database-builder.d.ts +0 -15
- package/dist/databases/postgres/migrations/database-builder.interface.d.ts +0 -10
- package/dist/databases/postgres/migrations/database-builder.js +0 -65
- package/dist/databases/postgres/migrations/migration.interface.d.ts +0 -11
- package/dist/databases/postgres/migrations/migration.interface.js +0 -1
- package/dist/databases/postgres/migrations/postgres-foundational.js +0 -47
- /package/dist/{databases/postgres/migrations/database-builder.interface.js → __tests__/setup/vitest-setup.d.ts} +0 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { initializeSystemUserContext, EmptyUserContext, getSystemUserContext } from '@loomcore/common/models';
|
|
2
|
+
import { PostgresDatabase } from '../postgres.database.js';
|
|
3
|
+
import { AuthService, OrganizationService } from '../../../services/index.js';
|
|
4
|
+
export const getPostgresInitialSchema = (config) => {
|
|
5
|
+
const migrations = [];
|
|
6
|
+
const isMultiTenant = config.app.isMultiTenant === true;
|
|
7
|
+
if (isMultiTenant) {
|
|
8
|
+
migrations.push({
|
|
9
|
+
name: '00000000000001_schema-organizations',
|
|
10
|
+
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
|
+
)
|
|
27
|
+
`);
|
|
28
|
+
},
|
|
29
|
+
down: async ({ context: pool }) => {
|
|
30
|
+
await pool.query('DROP TABLE IF EXISTS "organizations"');
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
migrations.push({
|
|
35
|
+
name: '00000000000002_schema-users',
|
|
36
|
+
up: async ({ context: pool }) => {
|
|
37
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
38
|
+
const uniqueConstraint = isMultiTenant
|
|
39
|
+
? 'CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")'
|
|
40
|
+
: 'CONSTRAINT "uk_users_email" UNIQUE ("email")';
|
|
41
|
+
await pool.query(`
|
|
42
|
+
CREATE TABLE IF NOT EXISTS "users" (
|
|
43
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
44
|
+
${orgColumnDef}
|
|
45
|
+
"email" VARCHAR(255) NOT NULL,
|
|
46
|
+
"firstName" VARCHAR(255),
|
|
47
|
+
"lastName" VARCHAR(255),
|
|
48
|
+
"displayName" VARCHAR(255),
|
|
49
|
+
"password" VARCHAR(255) NOT NULL,
|
|
50
|
+
"_lastLoggedIn" TIMESTAMPTZ,
|
|
51
|
+
"_lastPasswordChange" TIMESTAMPTZ,
|
|
52
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
53
|
+
"_createdBy" INTEGER NOT NULL,
|
|
54
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
55
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
56
|
+
"_deleted" TIMESTAMPTZ,
|
|
57
|
+
"_deletedBy" INTEGER,
|
|
58
|
+
${uniqueConstraint}
|
|
59
|
+
)
|
|
60
|
+
`);
|
|
61
|
+
},
|
|
62
|
+
down: async ({ context: pool }) => {
|
|
63
|
+
await pool.query('DROP TABLE IF EXISTS "users"');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
migrations.push({
|
|
67
|
+
name: '00000000000003_schema-refresh-tokens',
|
|
68
|
+
up: async ({ context: pool }) => {
|
|
69
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
70
|
+
await pool.query(`
|
|
71
|
+
CREATE TABLE IF NOT EXISTS "refreshTokens" (
|
|
72
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
73
|
+
${orgColumnDef}
|
|
74
|
+
"token" VARCHAR(255) NOT NULL,
|
|
75
|
+
"deviceId" VARCHAR(255) NOT NULL,
|
|
76
|
+
"userId" INTEGER NOT NULL,
|
|
77
|
+
"expiresOn" BIGINT NOT NULL,
|
|
78
|
+
"created" TIMESTAMPTZ NOT NULL,
|
|
79
|
+
"createdBy" INTEGER NOT NULL,
|
|
80
|
+
CONSTRAINT "fk_refreshTokens_user" FOREIGN KEY ("userId") REFERENCES "users"("_id") ON DELETE CASCADE
|
|
81
|
+
)
|
|
82
|
+
`);
|
|
83
|
+
},
|
|
84
|
+
down: async ({ context: pool }) => {
|
|
85
|
+
await pool.query('DROP TABLE IF EXISTS "refreshTokens"');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
migrations.push({
|
|
89
|
+
name: '00000000000004_schema-roles',
|
|
90
|
+
up: async ({ context: pool }) => {
|
|
91
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
92
|
+
const uniqueConstraint = isMultiTenant
|
|
93
|
+
? 'CONSTRAINT "uk_roles_name" UNIQUE ("_orgId", "name")'
|
|
94
|
+
: 'CONSTRAINT "uk_roles_name" UNIQUE ("name")';
|
|
95
|
+
await pool.query(`
|
|
96
|
+
CREATE TABLE IF NOT EXISTS "roles" (
|
|
97
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
98
|
+
${orgColumnDef}
|
|
99
|
+
"name" VARCHAR(255) NOT NULL,
|
|
100
|
+
"description" TEXT,
|
|
101
|
+
${uniqueConstraint}
|
|
102
|
+
)
|
|
103
|
+
`);
|
|
104
|
+
},
|
|
105
|
+
down: async ({ context: pool }) => {
|
|
106
|
+
await pool.query('DROP TABLE IF EXISTS "roles"');
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
migrations.push({
|
|
110
|
+
name: '00000000000005_schema-user-roles',
|
|
111
|
+
up: async ({ context: pool }) => {
|
|
112
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
113
|
+
const uniqueConstraint = isMultiTenant
|
|
114
|
+
? 'CONSTRAINT "uk_user_roles" UNIQUE ("_orgId", "userId", "roleId")'
|
|
115
|
+
: 'CONSTRAINT "uk_user_roles" UNIQUE ("userId", "roleId")';
|
|
116
|
+
await pool.query(`
|
|
117
|
+
CREATE TABLE IF NOT EXISTS "user_roles" (
|
|
118
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
119
|
+
${orgColumnDef}
|
|
120
|
+
"userId" INTEGER NOT NULL,
|
|
121
|
+
"roleId" INTEGER NOT NULL,
|
|
122
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
123
|
+
"_createdBy" INTEGER NOT NULL,
|
|
124
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
125
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
126
|
+
"_deleted" TIMESTAMPTZ,
|
|
127
|
+
"_deletedBy" INTEGER,
|
|
128
|
+
CONSTRAINT "fk_user_roles_user" FOREIGN KEY ("userId") REFERENCES "users"("_id") ON DELETE CASCADE,
|
|
129
|
+
CONSTRAINT "fk_user_roles_role" FOREIGN KEY ("roleId") REFERENCES "roles"("_id") ON DELETE CASCADE,
|
|
130
|
+
${uniqueConstraint}
|
|
131
|
+
)
|
|
132
|
+
`);
|
|
133
|
+
},
|
|
134
|
+
down: async ({ context: pool }) => {
|
|
135
|
+
await pool.query('DROP TABLE IF EXISTS "user_roles"');
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
migrations.push({
|
|
139
|
+
name: '00000000000006_schema-features',
|
|
140
|
+
up: async ({ context: pool }) => {
|
|
141
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
142
|
+
const uniqueConstraint = isMultiTenant
|
|
143
|
+
? 'CONSTRAINT "uk_features" UNIQUE ("_orgId", "name")'
|
|
144
|
+
: 'CONSTRAINT "uk_features" UNIQUE ("name")';
|
|
145
|
+
await pool.query(`
|
|
146
|
+
CREATE TABLE IF NOT EXISTS "features" (
|
|
147
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
148
|
+
${orgColumnDef}
|
|
149
|
+
"name" VARCHAR(255) NOT NULL,
|
|
150
|
+
"description" TEXT,
|
|
151
|
+
${uniqueConstraint}
|
|
152
|
+
)
|
|
153
|
+
`);
|
|
154
|
+
},
|
|
155
|
+
down: async ({ context: pool }) => {
|
|
156
|
+
await pool.query('DROP TABLE IF EXISTS "features"');
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
migrations.push({
|
|
160
|
+
name: '00000000000007_schema-authorizations',
|
|
161
|
+
up: async ({ context: pool }) => {
|
|
162
|
+
const orgColumnDef = isMultiTenant ? '"_orgId" INTEGER,' : '';
|
|
163
|
+
const uniqueConstraint = isMultiTenant
|
|
164
|
+
? 'CONSTRAINT "uk_authorizations" UNIQUE ("_orgId", "roleId", "featureId")'
|
|
165
|
+
: 'CONSTRAINT "uk_authorizations" UNIQUE ("roleId", "featureId")';
|
|
166
|
+
await pool.query(`
|
|
167
|
+
CREATE TABLE IF NOT EXISTS "authorizations" (
|
|
168
|
+
"_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
169
|
+
${orgColumnDef}
|
|
170
|
+
"roleId" INTEGER NOT NULL,
|
|
171
|
+
"featureId" INTEGER NOT NULL,
|
|
172
|
+
"startDate" TIMESTAMPTZ,
|
|
173
|
+
"endDate" TIMESTAMPTZ,
|
|
174
|
+
"config" JSONB,
|
|
175
|
+
"_created" TIMESTAMPTZ NOT NULL,
|
|
176
|
+
"_createdBy" INTEGER NOT NULL,
|
|
177
|
+
"_updated" TIMESTAMPTZ NOT NULL,
|
|
178
|
+
"_updatedBy" INTEGER NOT NULL,
|
|
179
|
+
"_deleted" TIMESTAMPTZ,
|
|
180
|
+
"_deletedBy" INTEGER,
|
|
181
|
+
CONSTRAINT "fk_authorizations_role" FOREIGN KEY ("roleId") REFERENCES "roles"("_id") ON DELETE CASCADE,
|
|
182
|
+
CONSTRAINT "fk_authorizations_feature" FOREIGN KEY ("featureId") REFERENCES "features"("_id") ON DELETE CASCADE,
|
|
183
|
+
${uniqueConstraint}
|
|
184
|
+
)
|
|
185
|
+
`);
|
|
186
|
+
},
|
|
187
|
+
down: async ({ context: pool }) => {
|
|
188
|
+
await pool.query('DROP TABLE IF EXISTS "authorizations"');
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
if (isMultiTenant && config.app.metaOrgName && config.app.metaOrgCode) {
|
|
192
|
+
migrations.push({
|
|
193
|
+
name: '00000000000008_data-meta-org',
|
|
194
|
+
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.app.metaOrgName, config.app.metaOrgCode]);
|
|
200
|
+
if (result.rowCount === 0) {
|
|
201
|
+
throw new Error('Failed to create meta organization');
|
|
202
|
+
}
|
|
203
|
+
initializeSystemUserContext(config.email?.systemEmailAddress || 'system@example.com', result.rows[0]);
|
|
204
|
+
},
|
|
205
|
+
down: async ({ context: pool }) => {
|
|
206
|
+
await pool.query(`DELETE FROM "organizations" WHERE "isMetaOrg" = TRUE`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (config.adminUser?.email && config.adminUser?.password) {
|
|
211
|
+
migrations.push({
|
|
212
|
+
name: '00000000000009_data-admin-user',
|
|
213
|
+
up: async ({ context: pool }) => {
|
|
214
|
+
if (!config.adminUser?.email || !config.adminUser?.password) {
|
|
215
|
+
throw new Error('Admin user email and password must be provided in config');
|
|
216
|
+
}
|
|
217
|
+
const client = await pool.connect();
|
|
218
|
+
try {
|
|
219
|
+
const database = new PostgresDatabase(client);
|
|
220
|
+
const authService = new AuthService(database);
|
|
221
|
+
let systemUserContext = getSystemUserContext();
|
|
222
|
+
if (!systemUserContext) {
|
|
223
|
+
throw new Error('SystemUserContext has not been initialized. For non-multi-tenant setups, initialize it before running migrations.');
|
|
224
|
+
}
|
|
225
|
+
if (isMultiTenant) {
|
|
226
|
+
if (!systemUserContext.organization?._id) {
|
|
227
|
+
const organizationService = new OrganizationService(database);
|
|
228
|
+
const metaOrg = await organizationService.getMetaOrg(EmptyUserContext);
|
|
229
|
+
if (metaOrg) {
|
|
230
|
+
initializeSystemUserContext(config.email?.systemEmailAddress || 'system@example.com', metaOrg);
|
|
231
|
+
systemUserContext = getSystemUserContext();
|
|
232
|
+
}
|
|
233
|
+
if (!systemUserContext?.organization?._id) {
|
|
234
|
+
throw new Error('Cannot create admin user: Multi-tenant mode is enabled but meta-org does not exist. Ensure metaOrgName and metaOrgCode are provided in config so the meta-org migration runs before the admin-user migration.');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const userData = {
|
|
239
|
+
email: config.adminUser.email,
|
|
240
|
+
password: config.adminUser.password,
|
|
241
|
+
firstName: 'Admin',
|
|
242
|
+
lastName: 'User',
|
|
243
|
+
displayName: 'Admin User',
|
|
244
|
+
};
|
|
245
|
+
if (isMultiTenant && systemUserContext.organization?._id) {
|
|
246
|
+
userData._orgId = systemUserContext.organization._id;
|
|
247
|
+
}
|
|
248
|
+
await authService.createUser(systemUserContext, userData);
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
client.release();
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
down: async ({ context: pool }) => {
|
|
255
|
+
if (!config.adminUser?.email)
|
|
256
|
+
return;
|
|
257
|
+
const result = await pool.query(`DELETE FROM "users" WHERE "email" = $1`, [config.adminUser.email]);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
if (config.adminUser?.email) {
|
|
262
|
+
migrations.push({
|
|
263
|
+
name: '00000000000010_data-admin-authorizations',
|
|
264
|
+
up: async ({ context: pool }) => {
|
|
265
|
+
if (!config.adminUser?.email) {
|
|
266
|
+
throw new Error('Admin user email not found in config');
|
|
267
|
+
}
|
|
268
|
+
const client = await pool.connect();
|
|
269
|
+
try {
|
|
270
|
+
const database = new PostgresDatabase(client);
|
|
271
|
+
const organizationService = new OrganizationService(database);
|
|
272
|
+
const authService = new AuthService(database);
|
|
273
|
+
const metaOrg = isMultiTenant ? await organizationService.getMetaOrg(EmptyUserContext) : undefined;
|
|
274
|
+
if (isMultiTenant && !metaOrg) {
|
|
275
|
+
throw new Error('Meta organization not found. Ensure meta-org migration ran successfully.');
|
|
276
|
+
}
|
|
277
|
+
const adminUser = await authService.getUserByEmail(config.adminUser.email);
|
|
278
|
+
if (!adminUser) {
|
|
279
|
+
throw new Error('Admin user not found. Ensure admin-user migration ran successfully.');
|
|
280
|
+
}
|
|
281
|
+
await client.query('BEGIN');
|
|
282
|
+
try {
|
|
283
|
+
const roleResult = isMultiTenant
|
|
284
|
+
? await client.query(`
|
|
285
|
+
INSERT INTO "roles" ("_orgId", "name")
|
|
286
|
+
VALUES ($1, 'admin')
|
|
287
|
+
RETURNING "_id"
|
|
288
|
+
`, [metaOrg._id])
|
|
289
|
+
: await client.query(`
|
|
290
|
+
INSERT INTO "roles" ("name")
|
|
291
|
+
VALUES ('admin')
|
|
292
|
+
RETURNING "_id"
|
|
293
|
+
`);
|
|
294
|
+
if (roleResult.rowCount === 0) {
|
|
295
|
+
throw new Error('Failed to create admin role');
|
|
296
|
+
}
|
|
297
|
+
const roleId = roleResult.rows[0]._id;
|
|
298
|
+
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
|
+
`, [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
|
+
`, [adminUser._id, roleId]);
|
|
307
|
+
if (userRoleResult.rowCount === 0) {
|
|
308
|
+
throw new Error('Failed to create user role');
|
|
309
|
+
}
|
|
310
|
+
const featureResult = isMultiTenant
|
|
311
|
+
? await client.query(`
|
|
312
|
+
INSERT INTO "features" ("_orgId", "name")
|
|
313
|
+
VALUES ($1, 'admin')
|
|
314
|
+
RETURNING "_id"
|
|
315
|
+
`, [metaOrg._id])
|
|
316
|
+
: await client.query(`
|
|
317
|
+
INSERT INTO "features" ("name")
|
|
318
|
+
VALUES ('admin')
|
|
319
|
+
RETURNING "_id"
|
|
320
|
+
`);
|
|
321
|
+
if (featureResult.rowCount === 0) {
|
|
322
|
+
throw new Error('Failed to create admin feature');
|
|
323
|
+
}
|
|
324
|
+
const featureId = featureResult.rows[0]._id;
|
|
325
|
+
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)
|
|
332
|
+
`, [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)
|
|
339
|
+
`, [roleId, featureId]);
|
|
340
|
+
if (authorizationResult.rowCount === 0) {
|
|
341
|
+
throw new Error('Failed to create admin authorization');
|
|
342
|
+
}
|
|
343
|
+
await client.query('COMMIT');
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
await client.query('ROLLBACK');
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
client.release();
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
down: async ({ context: pool }) => {
|
|
355
|
+
const client = await pool.connect();
|
|
356
|
+
try {
|
|
357
|
+
const database = new PostgresDatabase(client);
|
|
358
|
+
const organizationService = new OrganizationService(database);
|
|
359
|
+
const metaOrg = isMultiTenant ? await organizationService.getMetaOrg(EmptyUserContext) : undefined;
|
|
360
|
+
if (isMultiTenant && !metaOrg)
|
|
361
|
+
return;
|
|
362
|
+
await client.query('BEGIN');
|
|
363
|
+
try {
|
|
364
|
+
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
|
+
)
|
|
376
|
+
`, [metaOrg._id]);
|
|
377
|
+
await client.query(`
|
|
378
|
+
DELETE FROM "features"
|
|
379
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
380
|
+
`, [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
|
+
)
|
|
388
|
+
`, [metaOrg._id]);
|
|
389
|
+
await client.query(`
|
|
390
|
+
DELETE FROM "roles"
|
|
391
|
+
WHERE "_orgId" = $1 AND "name" = 'admin'
|
|
392
|
+
`, [metaOrg._id]);
|
|
393
|
+
}
|
|
394
|
+
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
|
+
)
|
|
405
|
+
`);
|
|
406
|
+
await client.query(`
|
|
407
|
+
DELETE FROM "features"
|
|
408
|
+
WHERE "name" = 'admin'
|
|
409
|
+
`);
|
|
410
|
+
await client.query(`
|
|
411
|
+
DELETE FROM "user_roles"
|
|
412
|
+
WHERE "roleId" IN (
|
|
413
|
+
SELECT "_id" FROM "roles"
|
|
414
|
+
WHERE "name" = 'admin'
|
|
415
|
+
)
|
|
416
|
+
`);
|
|
417
|
+
await client.query(`
|
|
418
|
+
DELETE FROM "roles"
|
|
419
|
+
WHERE "name" = 'admin'
|
|
420
|
+
`);
|
|
421
|
+
}
|
|
422
|
+
await client.query('COMMIT');
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
await client.query('ROLLBACK');
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
finally {
|
|
430
|
+
client.release();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
return migrations;
|
|
436
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IQueryOptions, IModelSpec, IPagedResult, IEntity, IUserContextAuthorization } from "@loomcore/common/models";
|
|
2
|
+
import type { AppIdType } from "@loomcore/common/types";
|
|
2
3
|
import { TSchema } from "@sinclair/typebox";
|
|
3
4
|
import { DeleteResult, IDatabase } from "../models/index.js";
|
|
4
5
|
import { Operation } from "../operations/operation.js";
|
|
@@ -6,27 +7,27 @@ import { Client } from 'pg';
|
|
|
6
7
|
export declare class PostgresDatabase implements IDatabase {
|
|
7
8
|
private client;
|
|
8
9
|
constructor(client: Client);
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
preProcessEntity<T extends IEntity>(entity: Partial<T>, modelSpec: TSchema): Partial<T>;
|
|
11
|
+
postProcessEntity<T extends IEntity>(entity: T, modelSpec: TSchema): T;
|
|
11
12
|
getAll<T extends IEntity>(operations: Operation[], pluralResourceName: string): Promise<T[]>;
|
|
12
13
|
get<T extends IEntity>(operations: Operation[], queryOptions: IQueryOptions, modelSpec: IModelSpec, pluralResourceName: string): Promise<IPagedResult<T>>;
|
|
13
|
-
getById<T extends IEntity>(operations: Operation[], queryObject: IQueryOptions, id:
|
|
14
|
+
getById<T extends IEntity>(operations: Operation[], queryObject: IQueryOptions, id: AppIdType, pluralResourceName: string): Promise<T | null>;
|
|
14
15
|
getCount(pluralResourceName: string): Promise<number>;
|
|
15
16
|
create<T extends IEntity>(entity: Partial<T>, pluralResourceName: string): Promise<{
|
|
16
|
-
insertedId:
|
|
17
|
+
insertedId: AppIdType;
|
|
17
18
|
entity: T;
|
|
18
19
|
}>;
|
|
19
20
|
createMany<T extends IEntity>(entities: Partial<T>[], pluralResourceName: string): Promise<{
|
|
20
|
-
insertedIds:
|
|
21
|
+
insertedIds: AppIdType[];
|
|
21
22
|
entities: T[];
|
|
22
23
|
}>;
|
|
23
24
|
batchUpdate<T extends IEntity>(entities: Partial<T>[], operations: Operation[], queryObject: IQueryOptions, pluralResourceName: string): Promise<T[]>;
|
|
24
|
-
fullUpdateById<T extends IEntity>(operations: Operation[], id:
|
|
25
|
-
partialUpdateById<T extends IEntity>(operations: Operation[], id:
|
|
25
|
+
fullUpdateById<T extends IEntity>(operations: Operation[], id: AppIdType, entity: Partial<T>, pluralResourceName: string): Promise<T>;
|
|
26
|
+
partialUpdateById<T extends IEntity>(operations: Operation[], id: AppIdType, entity: Partial<T>, pluralResourceName: string): Promise<T>;
|
|
26
27
|
update<T extends IEntity>(queryObject: IQueryOptions, entity: Partial<T>, operations: Operation[], pluralResourceName: string): Promise<T[]>;
|
|
27
|
-
deleteById(id:
|
|
28
|
+
deleteById(id: AppIdType, pluralResourceName: string): Promise<DeleteResult>;
|
|
28
29
|
deleteMany(queryObject: IQueryOptions, pluralResourceName: string): Promise<DeleteResult>;
|
|
29
30
|
find<T extends IEntity>(queryObject: IQueryOptions, pluralResourceName: string): Promise<T[]>;
|
|
30
31
|
findOne<T extends IEntity>(queryObject: IQueryOptions, pluralResourceName: string): Promise<T | null>;
|
|
31
|
-
getUserAuthorizations(userId:
|
|
32
|
+
getUserAuthorizations(userId: AppIdType, orgId?: AppIdType): Promise<IUserContextAuthorization[]>;
|
|
32
33
|
}
|
|
@@ -18,10 +18,10 @@ export class PostgresDatabase {
|
|
|
18
18
|
constructor(client) {
|
|
19
19
|
this.client = client;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
preProcessEntity(entity, modelSpec) {
|
|
22
22
|
return entity;
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
postProcessEntity(entity, modelSpec) {
|
|
25
25
|
return convertNullToUndefined(entity, modelSpec);
|
|
26
26
|
}
|
|
27
27
|
async getAll(operations, pluralResourceName) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Client } from 'pg';
|
|
2
2
|
import { Operation } from "../../operations/operation.js";
|
|
3
3
|
import { IQueryOptions } from '@loomcore/common/models';
|
|
4
|
-
|
|
4
|
+
import type { AppIdType } from '@loomcore/common/types';
|
|
5
|
+
export declare function getById<T>(client: Client, operations: Operation[], queryObject: IQueryOptions, id: AppIdType, pluralResourceName: string): Promise<T | null>;
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { IEntity } from "@loomcore/common/models";
|
|
2
|
+
import type { AppIdType } from "@loomcore/common/types";
|
|
2
3
|
export interface IRefreshToken extends IEntity {
|
|
3
4
|
token: string;
|
|
4
5
|
deviceId: string;
|
|
5
|
-
userId:
|
|
6
|
+
userId: AppIdType;
|
|
6
7
|
expiresOn: number;
|
|
7
8
|
created: Date;
|
|
8
|
-
createdBy:
|
|
9
|
+
createdBy: AppIdType;
|
|
9
10
|
}
|
|
10
11
|
export declare const refreshTokenSchema: import("@sinclair/typebox").TObject<{
|
|
11
12
|
token: import("@sinclair/typebox").TString;
|
|
12
13
|
deviceId: import("@sinclair/typebox").TString;
|
|
13
|
-
userId: import("@sinclair/typebox").
|
|
14
|
+
userId: import("@sinclair/typebox").TSchema;
|
|
14
15
|
expiresOn: import("@sinclair/typebox").TNumber;
|
|
15
16
|
created: import("@sinclair/typebox").TTransform<import("@sinclair/typebox").TString, Date>;
|
|
16
|
-
createdBy: import("@sinclair/typebox").
|
|
17
|
+
createdBy: import("@sinclair/typebox").TSchema;
|
|
17
18
|
}>;
|
|
18
19
|
export declare const refreshTokenModelSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { entityUtils } from "@loomcore/common/utils";
|
|
2
|
-
import { TypeboxIsoDate } from "@loomcore/common/validation";
|
|
2
|
+
import { TypeboxIsoDate, getIdSchema } from "@loomcore/common/validation";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
4
|
;
|
|
5
5
|
export const refreshTokenSchema = Type.Object({
|
|
6
6
|
token: Type.String({ minLength: 1 }),
|
|
7
7
|
deviceId: Type.String({ minLength: 1 }),
|
|
8
|
-
userId:
|
|
8
|
+
userId: getIdSchema(),
|
|
9
9
|
expiresOn: Type.Number(),
|
|
10
10
|
created: TypeboxIsoDate({ title: 'Created Date' }),
|
|
11
|
-
createdBy:
|
|
11
|
+
createdBy: getIdSchema()
|
|
12
12
|
});
|
|
13
13
|
export const refreshTokenModelSpec = entityUtils.getModelSpec(refreshTokenSchema);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Request, Response } from 'express';
|
|
2
2
|
import { IUserContext, ITokenResponse, ILoginResponse, IUser } from '@loomcore/common/models';
|
|
3
|
+
import type { AppIdType } from '@loomcore/common/types';
|
|
3
4
|
import { MultiTenantApiService } from './multi-tenant-api.service.js';
|
|
4
5
|
import { UpdateResult } from '../databases/models/update-result.js';
|
|
5
6
|
import { IRefreshToken } from '../models/refresh-token.model.js';
|
|
@@ -30,7 +31,7 @@ export declare class AuthService extends MultiTenantApiService<IUser> {
|
|
|
30
31
|
expiresOn: number;
|
|
31
32
|
}>;
|
|
32
33
|
getActiveRefreshToken(refreshToken: string, deviceId: string): Promise<IRefreshToken | null>;
|
|
33
|
-
createNewRefreshToken(userId:
|
|
34
|
+
createNewRefreshToken(userId: AppIdType, deviceId: string, orgId?: AppIdType): Promise<IRefreshToken | null>;
|
|
34
35
|
sendResetPasswordEmail(emailAddress: string): Promise<void>;
|
|
35
36
|
resetPassword(email: string, passwordResetToken: string, password: string): Promise<UpdateResult>;
|
|
36
37
|
deleteRefreshTokensForDevice(deviceId: string): Promise<import("../databases/models/delete-result.js").DeleteResult>;
|
|
@@ -42,6 +43,6 @@ export declare class AuthService extends MultiTenantApiService<IUser> {
|
|
|
42
43
|
getExpiresOnFromSeconds(expiresInSeconds: number): number;
|
|
43
44
|
getExpiresOnFromMinutes(expiresInMinutes: number): number;
|
|
44
45
|
getExpiresOnFromDays(expiresInDays: number): number;
|
|
45
|
-
|
|
46
|
+
preProcessEntity(userContext: IUserContext, entity: Partial<IUser>, isCreate: boolean, allowId: boolean): Promise<Partial<IUser>>;
|
|
46
47
|
private updateLastLoggedIn;
|
|
47
48
|
}
|
|
@@ -59,7 +59,7 @@ export class AuthService extends MultiTenantApiService {
|
|
|
59
59
|
};
|
|
60
60
|
this.updateLastLoggedIn(userContext.user._id)
|
|
61
61
|
.catch(err => console.log(`Error updating lastLoggedIn: ${err}`));
|
|
62
|
-
userContext.user = this.
|
|
62
|
+
userContext.user = this.postProcessEntity(userContext, userContext.user);
|
|
63
63
|
loginResponse = { tokens: tokenResponse, userContext };
|
|
64
64
|
}
|
|
65
65
|
return loginResponse;
|
|
@@ -70,7 +70,7 @@ export class AuthService extends MultiTenantApiService {
|
|
|
70
70
|
if (!rawUser) {
|
|
71
71
|
return null;
|
|
72
72
|
}
|
|
73
|
-
return this.database.
|
|
73
|
+
return this.database.postProcessEntity(rawUser, this.modelSpec.fullSchema);
|
|
74
74
|
}
|
|
75
75
|
async createUser(userContext, user) {
|
|
76
76
|
if (userContext.user._id === 'system') {
|
|
@@ -241,7 +241,7 @@ export class AuthService extends MultiTenantApiService {
|
|
|
241
241
|
getExpiresOnFromDays(expiresInDays) {
|
|
242
242
|
return Date.now() + expiresInDays * 24 * 60 * 60 * 1000;
|
|
243
243
|
}
|
|
244
|
-
async
|
|
244
|
+
async preProcessEntity(userContext, entity, isCreate, allowId) {
|
|
245
245
|
if (entity.email) {
|
|
246
246
|
entity.email = entity.email.toLowerCase();
|
|
247
247
|
}
|
|
@@ -249,7 +249,7 @@ export class AuthService extends MultiTenantApiService {
|
|
|
249
249
|
const hash = await passwordUtils.hashPassword(entity.password);
|
|
250
250
|
entity.password = hash;
|
|
251
251
|
}
|
|
252
|
-
const preparedEntity = await super.
|
|
252
|
+
const preparedEntity = await super.preProcessEntity(userContext, entity, isCreate, allowId);
|
|
253
253
|
return preparedEntity;
|
|
254
254
|
}
|
|
255
255
|
async updateLastLoggedIn(userId) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ValueError } from '@sinclair/typebox/errors';
|
|
2
2
|
import { IUserContext, IEntity, IPagedResult, IQueryOptions } from '@loomcore/common/models';
|
|
3
|
+
import type { AppIdType } from '@loomcore/common/types';
|
|
3
4
|
import { DeleteResult } from '../../databases/models/delete-result.js';
|
|
4
5
|
import { Operation } from '../../databases/operations/operation.js';
|
|
5
6
|
export interface IGenericApiService<T extends IEntity> {
|
|
@@ -9,20 +10,20 @@ export interface IGenericApiService<T extends IEntity> {
|
|
|
9
10
|
queryObject: IQueryOptions;
|
|
10
11
|
operations: Operation[];
|
|
11
12
|
};
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
preProcessEntity(userContext: IUserContext, entity: Partial<T>, isCreate: boolean, allowId: boolean): Promise<Partial<T>>;
|
|
14
|
+
postProcessEntity(userContext: IUserContext, entity: T): T;
|
|
14
15
|
getAll(userContext: IUserContext): Promise<T[]>;
|
|
15
16
|
get(userContext: IUserContext, queryOptions: IQueryOptions): Promise<IPagedResult<T>>;
|
|
16
|
-
getById(userContext: IUserContext, id:
|
|
17
|
+
getById(userContext: IUserContext, id: AppIdType): Promise<T>;
|
|
17
18
|
getCount(userContext: IUserContext): Promise<number>;
|
|
18
19
|
create(userContext: IUserContext, entity: Partial<T>): Promise<T | null>;
|
|
19
20
|
createMany(userContext: IUserContext, entities: Partial<T>[]): Promise<T[]>;
|
|
20
21
|
batchUpdate(userContext: IUserContext, entities: Partial<T>[]): Promise<T[]>;
|
|
21
|
-
fullUpdateById(userContext: IUserContext, id:
|
|
22
|
-
partialUpdateById(userContext: IUserContext, id:
|
|
23
|
-
partialUpdateByIdWithoutPreAndPostProcessing(userContext: IUserContext, id:
|
|
22
|
+
fullUpdateById(userContext: IUserContext, id: AppIdType, entity: T): Promise<T>;
|
|
23
|
+
partialUpdateById(userContext: IUserContext, id: AppIdType, entity: Partial<T>): Promise<T>;
|
|
24
|
+
partialUpdateByIdWithoutPreAndPostProcessing(userContext: IUserContext, id: AppIdType, entity: T): Promise<T>;
|
|
24
25
|
update(userContext: IUserContext, queryObject: IQueryOptions, entity: Partial<T>): Promise<T[]>;
|
|
25
|
-
deleteById(userContext: IUserContext, id:
|
|
26
|
+
deleteById(userContext: IUserContext, id: AppIdType): Promise<DeleteResult>;
|
|
26
27
|
deleteMany(userContext: IUserContext, queryObject: IQueryOptions): Promise<DeleteResult>;
|
|
27
28
|
find(userContext: IUserContext, queryObject: IQueryOptions, options?: any): Promise<T[]>;
|
|
28
29
|
findOne(userContext: IUserContext, queryObject: IQueryOptions, options?: any): Promise<T | null>;
|