@loomcore/api 0.1.27 → 0.1.29
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 +7 -2
- package/dist/__tests__/common-test.utils.js +65 -11
- package/dist/__tests__/postgres-test-migrations/run-test-migrations.js +0 -2
- package/dist/__tests__/postgres.test-database.js +7 -6
- package/dist/__tests__/test-express-app.js +2 -31
- package/dist/__tests__/test-objects.d.ts +1 -0
- package/dist/__tests__/test-objects.js +5 -1
- package/dist/config/base-api-config.js +1 -1
- package/dist/controllers/api.controller.d.ts +2 -3
- package/dist/controllers/api.controller.js +12 -12
- package/dist/controllers/auth.controller.js +2 -2
- package/dist/controllers/users.controller.d.ts +2 -1
- package/dist/controllers/users.controller.js +2 -2
- package/dist/databases/postgres/migrations/003-create-users-table.migration.js +5 -2
- package/dist/databases/postgres/migrations/004-create-refresh-tokens-table.migration.js +5 -1
- package/dist/databases/postgres/migrations/005-create-meta-org.migration.d.ts +1 -5
- package/dist/databases/postgres/migrations/005-create-meta-org.migration.js +7 -7
- package/dist/databases/postgres/migrations/006-create-admin-user.migration.d.ts +6 -3
- package/dist/databases/postgres/migrations/006-create-admin-user.migration.js +16 -13
- package/dist/databases/postgres/migrations/007-create-roles-table.migration.d.ts +1 -1
- package/dist/databases/postgres/migrations/007-create-roles-table.migration.js +6 -3
- package/dist/databases/postgres/migrations/008-create-user-roles-table.migration.d.ts +1 -1
- package/dist/databases/postgres/migrations/008-create-user-roles-table.migration.js +6 -2
- package/dist/databases/postgres/migrations/009-create-features-table.migration.d.ts +1 -1
- package/dist/databases/postgres/migrations/009-create-features-table.migration.js +6 -3
- package/dist/databases/postgres/migrations/010-create-authorizations-table.migration.d.ts +1 -1
- package/dist/databases/postgres/migrations/010-create-authorizations-table.migration.js +6 -3
- package/dist/databases/postgres/migrations/011-create-admin-authorization.migration.d.ts +3 -3
- package/dist/databases/postgres/migrations/011-create-admin-authorization.migration.js +22 -9
- package/dist/databases/postgres/migrations/database-builder.d.ts +15 -0
- package/dist/databases/postgres/migrations/database-builder.interface.d.ts +10 -0
- package/dist/databases/postgres/migrations/database-builder.interface.js +1 -0
- package/dist/databases/postgres/migrations/database-builder.js +62 -0
- package/dist/databases/postgres/migrations/setup-for-auth.migration.d.ts +5 -2
- package/dist/databases/postgres/migrations/setup-for-auth.migration.js +12 -18
- package/dist/databases/postgres/migrations/setup-for-multitenant.migration.d.ts +5 -3
- package/dist/databases/postgres/migrations/setup-for-multitenant.migration.js +7 -9
- package/dist/models/base-api-config.interface.d.ts +11 -5
- package/dist/services/email.service.js +6 -3
- package/dist/utils/api.utils.d.ts +1 -2
- package/dist/utils/api.utils.js +2 -3
- package/package.json +2 -2
- package/dist/__tests__/postgres-test-migrations/004-create-test-users-table.migration.d.ts +0 -21
- package/dist/__tests__/postgres-test-migrations/004-create-test-users-table.migration.js +0 -75
|
@@ -11,7 +11,10 @@ declare function initialize(database: IDatabase): void;
|
|
|
11
11
|
declare function getRandomId(): string;
|
|
12
12
|
declare function createMetaOrg(): Promise<void>;
|
|
13
13
|
declare function deleteMetaOrg(): Promise<void>;
|
|
14
|
-
declare function
|
|
14
|
+
declare function setupTestUsers(): Promise<{
|
|
15
|
+
metaOrgUser: IUser;
|
|
16
|
+
testOrgUser: IUser;
|
|
17
|
+
}>;
|
|
15
18
|
declare function deleteTestUser(): Promise<void>;
|
|
16
19
|
declare function simulateloginWithTestUser(): Promise<string>;
|
|
17
20
|
declare function getAuthToken(): string;
|
|
@@ -22,6 +25,7 @@ export declare class CategoryService extends GenericApiService<ICategory> {
|
|
|
22
25
|
export declare class CategoryController extends ApiController<ICategory> {
|
|
23
26
|
constructor(app: Application, database: IDatabase);
|
|
24
27
|
}
|
|
28
|
+
export declare function setupTestConfig(isMultiTenant?: boolean): void;
|
|
25
29
|
export declare class ProductService extends GenericApiService<IProduct> {
|
|
26
30
|
private db;
|
|
27
31
|
constructor(database: IDatabase);
|
|
@@ -59,10 +63,11 @@ declare const testUtils: {
|
|
|
59
63
|
deleteTestUser: typeof deleteTestUser;
|
|
60
64
|
getAuthToken: typeof getAuthToken;
|
|
61
65
|
initialize: typeof initialize;
|
|
66
|
+
SetupTestConfig: typeof setupTestConfig;
|
|
62
67
|
loginWithTestUser: typeof loginWithTestUser;
|
|
63
68
|
newUser1Email: string;
|
|
64
69
|
newUser1Password: string;
|
|
65
|
-
|
|
70
|
+
setupTestUsers: typeof setupTestUsers;
|
|
66
71
|
simulateloginWithTestUser: typeof simulateloginWithTestUser;
|
|
67
72
|
verifyToken: typeof verifyToken;
|
|
68
73
|
};
|
|
@@ -8,9 +8,12 @@ import { Join } from '../databases/operations/join.operation.js';
|
|
|
8
8
|
import { OrganizationService } from '../services/organization.service.js';
|
|
9
9
|
import { AuthService, GenericApiService } from '../services/index.js';
|
|
10
10
|
import { ObjectId } from 'mongodb';
|
|
11
|
-
import
|
|
11
|
+
import * as testObjectsModule from './test-objects.js';
|
|
12
|
+
const { getTestMetaOrg, getTestOrg, getTestMetaOrgUser, getTestMetaOrgUserContext, getTestOrgUser, getTestOrgUserContext, setTestOrgId, setTestMetaOrgId } = testObjectsModule;
|
|
12
13
|
import { CategorySpec } from './models/category.model.js';
|
|
13
14
|
import { ProductSpec } from './models/product.model.js';
|
|
15
|
+
import { setBaseApiConfig } from '../config/index.js';
|
|
16
|
+
import { entityUtils } from '@loomcore/common/utils';
|
|
14
17
|
let deviceIdCookie;
|
|
15
18
|
let authService;
|
|
16
19
|
let organizationService;
|
|
@@ -52,17 +55,17 @@ async function deleteMetaOrg() {
|
|
|
52
55
|
console.log('Error deleting meta org:', error);
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
|
-
async function
|
|
58
|
+
async function setupTestUsers() {
|
|
56
59
|
try {
|
|
57
60
|
await deleteTestUser();
|
|
58
|
-
return
|
|
61
|
+
return createTestUsers();
|
|
59
62
|
}
|
|
60
63
|
catch (error) {
|
|
61
64
|
console.log(error);
|
|
62
65
|
throw error;
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
|
-
async function
|
|
68
|
+
async function createTestUsers() {
|
|
66
69
|
if (!authService || !organizationService) {
|
|
67
70
|
throw new Error('Database not initialized. Call initialize() first.');
|
|
68
71
|
}
|
|
@@ -71,15 +74,26 @@ async function createTestUser() {
|
|
|
71
74
|
if (!existingMetaOrg) {
|
|
72
75
|
await organizationService.create(getTestMetaOrgUserContext(), getTestMetaOrg());
|
|
73
76
|
}
|
|
77
|
+
else {
|
|
78
|
+
setTestMetaOrgId(existingMetaOrg._id);
|
|
79
|
+
}
|
|
74
80
|
const existingTestOrg = await organizationService.findOne(getTestMetaOrgUserContext(), { filters: { _id: { eq: getTestOrg()._id } } });
|
|
75
81
|
if (!existingTestOrg) {
|
|
76
|
-
await organizationService.create(getTestMetaOrgUserContext(), getTestOrg());
|
|
82
|
+
const createdTestOrg = await organizationService.create(getTestMetaOrgUserContext(), getTestOrg());
|
|
83
|
+
if (!createdTestOrg) {
|
|
84
|
+
throw new Error('Failed to create test organization');
|
|
85
|
+
}
|
|
86
|
+
setTestOrgId(createdTestOrg._id);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
setTestOrgId(existingTestOrg._id);
|
|
77
90
|
}
|
|
78
|
-
const
|
|
79
|
-
|
|
91
|
+
const createdTestOrgUser = await authService.createUser(getTestOrgUserContext(), getTestOrgUser());
|
|
92
|
+
const createdMetaOrgUser = await authService.createUser(getTestMetaOrgUserContext(), getTestMetaOrgUser());
|
|
93
|
+
if (!createdTestOrgUser || !createdMetaOrgUser) {
|
|
80
94
|
throw new Error('Failed to create test user');
|
|
81
95
|
}
|
|
82
|
-
return
|
|
96
|
+
return { metaOrgUser: createdMetaOrgUser, testOrgUser: createdTestOrgUser };
|
|
83
97
|
}
|
|
84
98
|
catch (error) {
|
|
85
99
|
console.log('Error in createTestUser:', error);
|
|
@@ -141,6 +155,43 @@ export class CategoryController extends ApiController {
|
|
|
141
155
|
super('categories', app, categoryService, 'category', CategorySpec);
|
|
142
156
|
}
|
|
143
157
|
}
|
|
158
|
+
export function setupTestConfig(isMultiTenant = true) {
|
|
159
|
+
setBaseApiConfig({
|
|
160
|
+
env: 'test',
|
|
161
|
+
hostName: 'localhost',
|
|
162
|
+
appName: 'test-app',
|
|
163
|
+
clientSecret: 'test-secret',
|
|
164
|
+
database: {
|
|
165
|
+
name: 'test-db',
|
|
166
|
+
},
|
|
167
|
+
externalPort: 4000,
|
|
168
|
+
internalPort: 8083,
|
|
169
|
+
corsAllowedOrigins: ['*'],
|
|
170
|
+
saltWorkFactor: 10,
|
|
171
|
+
jobTypes: '',
|
|
172
|
+
deployedBranch: '',
|
|
173
|
+
debug: {
|
|
174
|
+
showErrors: false
|
|
175
|
+
},
|
|
176
|
+
app: { isMultiTenant: isMultiTenant },
|
|
177
|
+
auth: {
|
|
178
|
+
jwtExpirationInSeconds: 3600,
|
|
179
|
+
refreshTokenExpirationInDays: 7,
|
|
180
|
+
deviceIdCookieMaxAgeInDays: 730,
|
|
181
|
+
passwordResetTokenExpirationInMinutes: 20
|
|
182
|
+
},
|
|
183
|
+
email: {
|
|
184
|
+
emailApiKey: 'WeDontHaveAKeyYet',
|
|
185
|
+
emailApiSecret: 'WeDontHaveASecretYet',
|
|
186
|
+
fromAddress: 'test@test.com',
|
|
187
|
+
systemEmailAddress: 'system@test.com'
|
|
188
|
+
},
|
|
189
|
+
adminUser: {
|
|
190
|
+
email: 'admin@test.com',
|
|
191
|
+
password: 'admin-password'
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
144
195
|
export class ProductService extends GenericApiService {
|
|
145
196
|
db;
|
|
146
197
|
constructor(database) {
|
|
@@ -172,7 +223,8 @@ export class ProductsController extends ApiController {
|
|
|
172
223
|
}))
|
|
173
224
|
]);
|
|
174
225
|
const PublicAggregatedProductSchema = Type.Omit(AggregatedProductSchema, ['internalNumber']);
|
|
175
|
-
|
|
226
|
+
const PublicAggregatedProductSpec = entityUtils.getModelSpec(PublicAggregatedProductSchema);
|
|
227
|
+
super('products', app, productService, 'product', ProductSpec, PublicAggregatedProductSpec);
|
|
176
228
|
}
|
|
177
229
|
}
|
|
178
230
|
export class MultiTenantProductService extends MultiTenantApiService {
|
|
@@ -206,7 +258,8 @@ export class MultiTenantProductsController extends ApiController {
|
|
|
206
258
|
}))
|
|
207
259
|
]);
|
|
208
260
|
const PublicAggregatedProductSchema = Type.Omit(AggregatedProductSchema, ['internalNumber']);
|
|
209
|
-
|
|
261
|
+
const PublicAggregatedProductSpec = entityUtils.getModelSpec(PublicAggregatedProductSchema);
|
|
262
|
+
super('multi-tenant-products', app, productService, 'product', ProductSpec, PublicAggregatedProductSpec);
|
|
210
263
|
}
|
|
211
264
|
}
|
|
212
265
|
function configureJwtSecret() {
|
|
@@ -250,10 +303,11 @@ const testUtils = {
|
|
|
250
303
|
deleteTestUser,
|
|
251
304
|
getAuthToken,
|
|
252
305
|
initialize,
|
|
306
|
+
SetupTestConfig: setupTestConfig,
|
|
253
307
|
loginWithTestUser,
|
|
254
308
|
newUser1Email,
|
|
255
309
|
newUser1Password,
|
|
256
|
-
|
|
310
|
+
setupTestUsers,
|
|
257
311
|
simulateloginWithTestUser,
|
|
258
312
|
verifyToken
|
|
259
313
|
};
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { CreateTestEntitiesTableMigration } from "./001-create-test-entities-table.migration.js";
|
|
2
2
|
import { CreateCategoriesTableMigration } from "./002-create-categories-table.migration.js";
|
|
3
3
|
import { CreateProductsTableMigration } from "./003-create-products-table.migration.js";
|
|
4
|
-
import { CreateTestUsersTableMigration } from "./004-create-test-users-table.migration.js";
|
|
5
4
|
import { CreateTestItemsTableMigration } from "./005-create-test-items-table.migration.js";
|
|
6
5
|
export async function runTestMigrations(client, _orgId) {
|
|
7
6
|
const migrations = [
|
|
8
7
|
new CreateTestEntitiesTableMigration(client),
|
|
9
8
|
new CreateCategoriesTableMigration(client),
|
|
10
9
|
new CreateProductsTableMigration(client),
|
|
11
|
-
new CreateTestUsersTableMigration(client),
|
|
12
10
|
new CreateTestItemsTableMigration(client),
|
|
13
11
|
];
|
|
14
12
|
try {
|
|
@@ -6,8 +6,8 @@ import { setupDatabaseForMultitenant } from '../databases/postgres/migrations/se
|
|
|
6
6
|
import { setupDatabaseForAuth } from '../databases/postgres/migrations/setup-for-auth.migration.js';
|
|
7
7
|
import { runTestMigrations } from './postgres-test-migrations/run-test-migrations.js';
|
|
8
8
|
import { PostgresDatabase } from '../databases/postgres/postgres.database.js';
|
|
9
|
-
import { getTestMetaOrg, setTestMetaOrgId } from './test-objects.js';
|
|
10
9
|
import { getSystemUserContext } from '@loomcore/common/models';
|
|
10
|
+
import { setTestMetaOrgId } from './test-objects.js';
|
|
11
11
|
export class TestPostgresDatabase {
|
|
12
12
|
database = null;
|
|
13
13
|
postgresClient = null;
|
|
@@ -30,19 +30,20 @@ export class TestPostgresDatabase {
|
|
|
30
30
|
const testDatabase = new PostgresDatabase(postgresClient);
|
|
31
31
|
this.database = testDatabase;
|
|
32
32
|
this.postgresClient = postgresClient;
|
|
33
|
-
const
|
|
34
|
-
const multitenantResult = await setupDatabaseForMultitenant(postgresClient, metaOrg.name, metaOrg.code);
|
|
33
|
+
const multitenantResult = await setupDatabaseForMultitenant(postgresClient);
|
|
35
34
|
let success = multitenantResult.success;
|
|
36
|
-
if (!success
|
|
35
|
+
if (!success) {
|
|
37
36
|
throw new Error('Failed to setup for multitenant');
|
|
38
37
|
}
|
|
39
|
-
setTestMetaOrgId(multitenantResult.metaOrgId);
|
|
40
38
|
await initSystemUserContext(this.database);
|
|
41
|
-
success = (await setupDatabaseForAuth(postgresClient
|
|
39
|
+
success = (await setupDatabaseForAuth(postgresClient)).success;
|
|
42
40
|
if (!success) {
|
|
43
41
|
throw new Error('Failed to setup for auth');
|
|
44
42
|
}
|
|
45
43
|
const systemUserContext = getSystemUserContext();
|
|
44
|
+
if (systemUserContext._orgId) {
|
|
45
|
+
setTestMetaOrgId(systemUserContext._orgId);
|
|
46
|
+
}
|
|
46
47
|
success = (await runTestMigrations(postgresClient, systemUserContext._orgId)).success;
|
|
47
48
|
if (!success) {
|
|
48
49
|
throw new Error('Failed to run test migrations');
|
|
@@ -3,11 +3,11 @@ import bodyParser from 'body-parser';
|
|
|
3
3
|
import cookieParser from 'cookie-parser';
|
|
4
4
|
import supertest from 'supertest';
|
|
5
5
|
import { initializeTypeBox } from '@loomcore/common/validation';
|
|
6
|
-
import { setBaseApiConfig } from '../config/base-api-config.js';
|
|
7
6
|
import { errorHandler } from '../middleware/error-handler.js';
|
|
8
7
|
import { ensureUserContext } from '../middleware/ensure-user-context.js';
|
|
9
8
|
import { TestMongoDatabase } from './mongo-db.test-database.js';
|
|
10
9
|
import { TestPostgresDatabase } from './postgres.test-database.js';
|
|
10
|
+
import { setupTestConfig } from './common-test.utils.js';
|
|
11
11
|
export class TestExpressApp {
|
|
12
12
|
static app;
|
|
13
13
|
static database;
|
|
@@ -26,36 +26,7 @@ export class TestExpressApp {
|
|
|
26
26
|
return this.initPromise;
|
|
27
27
|
}
|
|
28
28
|
static async _performInit(useMongoDb) {
|
|
29
|
-
|
|
30
|
-
env: 'test',
|
|
31
|
-
hostName: 'localhost',
|
|
32
|
-
appName: 'test-app',
|
|
33
|
-
clientSecret: 'test-secret',
|
|
34
|
-
database: {
|
|
35
|
-
name: this.databaseName,
|
|
36
|
-
},
|
|
37
|
-
externalPort: 4000,
|
|
38
|
-
internalPort: 8083,
|
|
39
|
-
corsAllowedOrigins: ['*'],
|
|
40
|
-
saltWorkFactor: 10,
|
|
41
|
-
jobTypes: '',
|
|
42
|
-
deployedBranch: '',
|
|
43
|
-
debug: {
|
|
44
|
-
showErrors: false
|
|
45
|
-
},
|
|
46
|
-
app: { isMultiTenant: true },
|
|
47
|
-
auth: {
|
|
48
|
-
jwtExpirationInSeconds: 3600,
|
|
49
|
-
refreshTokenExpirationInDays: 7,
|
|
50
|
-
deviceIdCookieMaxAgeInDays: 730,
|
|
51
|
-
passwordResetTokenExpirationInMinutes: 20
|
|
52
|
-
},
|
|
53
|
-
email: {
|
|
54
|
-
emailApiKey: 'WeDontHaveAKeyYet',
|
|
55
|
-
emailApiSecret: 'WeDontHaveASecretYet',
|
|
56
|
-
fromAddress: undefined
|
|
57
|
-
}
|
|
58
|
-
});
|
|
29
|
+
setupTestConfig();
|
|
59
30
|
initializeTypeBox();
|
|
60
31
|
if (!this.database) {
|
|
61
32
|
if (useMongoDb) {
|
|
@@ -4,6 +4,7 @@ export declare function setTestMetaOrgId(metaOrgId: string): void;
|
|
|
4
4
|
export declare function getTestMetaOrg(): IOrganization;
|
|
5
5
|
export declare function getTestMetaOrgUser(): IUser;
|
|
6
6
|
export declare function getTestMetaOrgUserContext(): IUserContext;
|
|
7
|
+
export declare function setTestOrgId(orgId: string): void;
|
|
7
8
|
export declare function getTestOrg(): IOrganization;
|
|
8
9
|
export declare function getTestOrgUser(): IUser;
|
|
9
10
|
export declare function getTestOrgUserContext(): IUserContext;
|
|
@@ -51,9 +51,13 @@ export function getTestMetaOrgUserContext() {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
;
|
|
54
|
+
let TEST_ORG_ID = '6926167d06c0073a778a124f';
|
|
55
|
+
export function setTestOrgId(orgId) {
|
|
56
|
+
TEST_ORG_ID = orgId;
|
|
57
|
+
}
|
|
54
58
|
export function getTestOrg() {
|
|
55
59
|
return {
|
|
56
|
-
_id:
|
|
60
|
+
_id: TEST_ORG_ID,
|
|
57
61
|
name: 'Test Organization',
|
|
58
62
|
code: 'test-org',
|
|
59
63
|
status: 1,
|
|
@@ -23,7 +23,7 @@ export async function initSystemUserContext(database) {
|
|
|
23
23
|
throw new Error('BaseApiConfig has not been set. Call setBaseApiConfig first.');
|
|
24
24
|
}
|
|
25
25
|
if (!isSystemUserContextSet) {
|
|
26
|
-
const systemEmail = config.email
|
|
26
|
+
const systemEmail = config.email?.systemEmailAddress || 'system@example.com';
|
|
27
27
|
let metaOrgId = undefined;
|
|
28
28
|
if (config.app.isMultiTenant) {
|
|
29
29
|
const { OrganizationService } = await import('../services/organization.service.js');
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Application, NextFunction, Request, Response } from 'express';
|
|
2
|
-
import { TSchema } from '@sinclair/typebox';
|
|
3
2
|
import { IEntity, IModelSpec } from '@loomcore/common/models';
|
|
4
3
|
import { IGenericApiService } from '../services/index.js';
|
|
5
4
|
export declare abstract class ApiController<T extends IEntity> {
|
|
@@ -8,8 +7,8 @@ export declare abstract class ApiController<T extends IEntity> {
|
|
|
8
7
|
protected slug: string;
|
|
9
8
|
protected apiResourceName: string;
|
|
10
9
|
protected modelSpec?: IModelSpec;
|
|
11
|
-
protected
|
|
12
|
-
protected constructor(slug: string, app: Application, service: IGenericApiService<T>, resourceName?: string, modelSpec?: IModelSpec,
|
|
10
|
+
protected publicSpec?: IModelSpec;
|
|
11
|
+
protected constructor(slug: string, app: Application, service: IGenericApiService<T>, resourceName?: string, modelSpec?: IModelSpec, publicSpec?: IModelSpec);
|
|
13
12
|
mapRoutes(app: Application): void;
|
|
14
13
|
protected validate(entity: any, isPartial?: boolean): void;
|
|
15
14
|
protected validateMany(entities: any[], isPartial?: boolean): void;
|
|
@@ -8,14 +8,14 @@ export class ApiController {
|
|
|
8
8
|
slug;
|
|
9
9
|
apiResourceName;
|
|
10
10
|
modelSpec;
|
|
11
|
-
|
|
12
|
-
constructor(slug, app, service, resourceName = '', modelSpec,
|
|
11
|
+
publicSpec;
|
|
12
|
+
constructor(slug, app, service, resourceName = '', modelSpec, publicSpec) {
|
|
13
13
|
this.slug = slug;
|
|
14
14
|
this.app = app;
|
|
15
15
|
this.service = service;
|
|
16
16
|
this.apiResourceName = resourceName;
|
|
17
17
|
this.modelSpec = modelSpec;
|
|
18
|
-
this.
|
|
18
|
+
this.publicSpec = publicSpec;
|
|
19
19
|
this.mapRoutes(app);
|
|
20
20
|
}
|
|
21
21
|
mapRoutes(app) {
|
|
@@ -40,30 +40,30 @@ export class ApiController {
|
|
|
40
40
|
async getAll(req, res, next) {
|
|
41
41
|
res.set('Content-Type', 'application/json');
|
|
42
42
|
const entities = await this.service.getAll(req.userContext);
|
|
43
|
-
apiUtils.apiResponse(res, 200, { data: entities }, this.modelSpec, this.
|
|
43
|
+
apiUtils.apiResponse(res, 200, { data: entities }, this.modelSpec, this.publicSpec);
|
|
44
44
|
}
|
|
45
45
|
async get(req, res, next) {
|
|
46
46
|
res.set('Content-Type', 'application/json');
|
|
47
47
|
const queryOptions = apiUtils.getQueryOptionsFromRequest(req);
|
|
48
48
|
const pagedResult = await this.service.get(req.userContext, queryOptions);
|
|
49
|
-
apiUtils.apiResponse(res, 200, { data: pagedResult }, this.modelSpec, this.
|
|
49
|
+
apiUtils.apiResponse(res, 200, { data: pagedResult }, this.modelSpec, this.publicSpec);
|
|
50
50
|
}
|
|
51
51
|
async getById(req, res, next) {
|
|
52
52
|
let id = req.params?.id;
|
|
53
53
|
res.set('Content-Type', 'application/json');
|
|
54
54
|
const entity = await this.service.getById(req.userContext, id);
|
|
55
|
-
apiUtils.apiResponse(res, 200, { data: entity }, this.modelSpec, this.
|
|
55
|
+
apiUtils.apiResponse(res, 200, { data: entity }, this.modelSpec, this.publicSpec);
|
|
56
56
|
}
|
|
57
57
|
async getCount(req, res, next) {
|
|
58
58
|
res.set('Content-Type', 'application/json');
|
|
59
59
|
const count = await this.service.getCount(req.userContext);
|
|
60
|
-
apiUtils.apiResponse(res, 200, { data: count }, this.modelSpec, this.
|
|
60
|
+
apiUtils.apiResponse(res, 200, { data: count }, this.modelSpec, this.publicSpec);
|
|
61
61
|
}
|
|
62
62
|
async create(req, res, next) {
|
|
63
63
|
res.set('Content-Type', 'application/json');
|
|
64
64
|
this.validate(req.body);
|
|
65
65
|
const entity = await this.service.create(req.userContext, req.body);
|
|
66
|
-
apiUtils.apiResponse(res, 201, { data: entity || undefined }, this.modelSpec, this.
|
|
66
|
+
apiUtils.apiResponse(res, 201, { data: entity || undefined }, this.modelSpec, this.publicSpec);
|
|
67
67
|
}
|
|
68
68
|
async batchUpdate(req, res, next) {
|
|
69
69
|
res.set('Content-Type', 'application/json');
|
|
@@ -73,23 +73,23 @@ export class ApiController {
|
|
|
73
73
|
}
|
|
74
74
|
this.validateMany(entities, true);
|
|
75
75
|
const updatedEntities = await this.service.batchUpdate(req.userContext, entities);
|
|
76
|
-
apiUtils.apiResponse(res, 200, { data: updatedEntities }, this.modelSpec, this.
|
|
76
|
+
apiUtils.apiResponse(res, 200, { data: updatedEntities }, this.modelSpec, this.publicSpec);
|
|
77
77
|
}
|
|
78
78
|
async fullUpdateById(req, res, next) {
|
|
79
79
|
res.set('Content-Type', 'application/json');
|
|
80
80
|
this.validate(req.body);
|
|
81
81
|
const updateResult = await this.service.fullUpdateById(req.userContext, req.params.id, req.body);
|
|
82
|
-
apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.
|
|
82
|
+
apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.publicSpec);
|
|
83
83
|
}
|
|
84
84
|
async partialUpdateById(req, res, next) {
|
|
85
85
|
res.set('Content-Type', 'application/json');
|
|
86
86
|
this.validate(req.body, true);
|
|
87
87
|
const updateResult = await this.service.partialUpdateById(req.userContext, req.params.id, req.body);
|
|
88
|
-
apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.
|
|
88
|
+
apiUtils.apiResponse(res, 200, { data: updateResult }, this.modelSpec, this.publicSpec);
|
|
89
89
|
}
|
|
90
90
|
async deleteById(req, res, next) {
|
|
91
91
|
res.set('Content-Type', 'application/json');
|
|
92
92
|
const deleteResult = await this.service.deleteById(req.userContext, req.params.id);
|
|
93
|
-
apiUtils.apiResponse(res, 200, { data: deleteResult }, this.modelSpec, this.
|
|
93
|
+
apiUtils.apiResponse(res, 200, { data: deleteResult }, this.modelSpec, this.publicSpec);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LoginResponseSpec, TokenResponseSpec, UserSpec,
|
|
1
|
+
import { LoginResponseSpec, TokenResponseSpec, UserSpec, PublicUserSpec, passwordValidator, PublicUserContextSpec, } from '@loomcore/common/models';
|
|
2
2
|
import { entityUtils } from '@loomcore/common/utils';
|
|
3
3
|
import { BadRequestError, UnauthenticatedError } from '../errors/index.js';
|
|
4
4
|
import { isAuthenticated } from '../middleware/index.js';
|
|
@@ -35,7 +35,7 @@ export class AuthController {
|
|
|
35
35
|
throw new BadRequestError('Missing required fields: userContext is required.');
|
|
36
36
|
}
|
|
37
37
|
const user = await this.authService.createUser(userContext, body);
|
|
38
|
-
apiUtils.apiResponse(res, 201, { data: user || undefined }, UserSpec,
|
|
38
|
+
apiUtils.apiResponse(res, 201, { data: user || undefined }, UserSpec, PublicUserSpec);
|
|
39
39
|
}
|
|
40
40
|
async requestTokenUsingRefreshToken(req, res, next) {
|
|
41
41
|
const refreshToken = req.query.refreshToken;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Application } from 'express';
|
|
2
2
|
import { IUser } from '@loomcore/common/models';
|
|
3
3
|
import { ApiController } from './api.controller.js';
|
|
4
|
+
import { UserService } from '../services/index.js';
|
|
4
5
|
import { IDatabase } from '../databases/models/index.js';
|
|
5
6
|
export declare class UsersController extends ApiController<IUser> {
|
|
6
|
-
|
|
7
|
+
userService: UserService;
|
|
7
8
|
constructor(app: Application, database: IDatabase);
|
|
8
9
|
mapRoutes(app: Application): void;
|
|
9
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UserSpec,
|
|
1
|
+
import { UserSpec, PublicUserSpec } from '@loomcore/common/models';
|
|
2
2
|
import { ApiController } from './api.controller.js';
|
|
3
3
|
import { isAuthenticated } from '../middleware/index.js';
|
|
4
4
|
import { UserService } from '../services/index.js';
|
|
@@ -6,7 +6,7 @@ export class UsersController extends ApiController {
|
|
|
6
6
|
userService;
|
|
7
7
|
constructor(app, database) {
|
|
8
8
|
const userService = new UserService(database);
|
|
9
|
-
super('users', app, userService, 'user', UserSpec,
|
|
9
|
+
super('users', app, userService, 'user', UserSpec, PublicUserSpec);
|
|
10
10
|
this.userService = userService;
|
|
11
11
|
}
|
|
12
12
|
mapRoutes(app) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
import { doesTableExist } from "../utils/does-table-exist.util.js";
|
|
3
|
+
import { config } from "../../../config/index.js";
|
|
3
4
|
export class CreateUsersTableMigration {
|
|
4
5
|
client;
|
|
5
6
|
constructor(client) {
|
|
@@ -12,6 +13,9 @@ export class CreateUsersTableMigration {
|
|
|
12
13
|
await this.client.query('BEGIN');
|
|
13
14
|
const tableExists = await doesTableExist(this.client, 'users');
|
|
14
15
|
if (!tableExists) {
|
|
16
|
+
const fkConstraint = config.app.isMultiTenant
|
|
17
|
+
? ',\n CONSTRAINT "fk_users_organization" FOREIGN KEY ("_orgId") REFERENCES "organizations"("_id") ON DELETE CASCADE'
|
|
18
|
+
: '';
|
|
15
19
|
await this.client.query(`
|
|
16
20
|
CREATE TABLE "users" (
|
|
17
21
|
"_id" VARCHAR(255) PRIMARY KEY,
|
|
@@ -29,8 +33,7 @@ export class CreateUsersTableMigration {
|
|
|
29
33
|
"_updatedBy" VARCHAR(255) NOT NULL,
|
|
30
34
|
"_deleted" TIMESTAMP,
|
|
31
35
|
"_deletedBy" VARCHAR(255),
|
|
32
|
-
CONSTRAINT "
|
|
33
|
-
CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")
|
|
36
|
+
CONSTRAINT "uk_users_email" UNIQUE ("_orgId", "email")${fkConstraint}
|
|
34
37
|
)
|
|
35
38
|
`);
|
|
36
39
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
import { doesTableExist } from "../utils/does-table-exist.util.js";
|
|
3
|
+
import { config } from "../../../config/index.js";
|
|
3
4
|
export class CreateRefreshTokenTableMigration {
|
|
4
5
|
client;
|
|
5
6
|
constructor(client) {
|
|
@@ -12,6 +13,9 @@ export class CreateRefreshTokenTableMigration {
|
|
|
12
13
|
await this.client.query('BEGIN');
|
|
13
14
|
const tableExists = await doesTableExist(this.client, 'refreshTokens');
|
|
14
15
|
if (!tableExists) {
|
|
16
|
+
const fkConstraint = config.app.isMultiTenant
|
|
17
|
+
? ',\n CONSTRAINT "fk_refreshTokens_organization" FOREIGN KEY ("_orgId") REFERENCES "organizations"("_id") ON DELETE CASCADE'
|
|
18
|
+
: '';
|
|
15
19
|
await this.client.query(`
|
|
16
20
|
CREATE TABLE "refreshTokens" (
|
|
17
21
|
"_id" VARCHAR(255) PRIMARY KEY,
|
|
@@ -21,7 +25,7 @@ export class CreateRefreshTokenTableMigration {
|
|
|
21
25
|
"userId" VARCHAR(255) NOT NULL,
|
|
22
26
|
"expiresOn" BIGINT NOT NULL,
|
|
23
27
|
"created" TIMESTAMP NOT NULL,
|
|
24
|
-
"createdBy" VARCHAR(255) NOT NULL
|
|
28
|
+
"createdBy" VARCHAR(255) NOT NULL${fkConstraint}
|
|
25
29
|
)
|
|
26
30
|
`);
|
|
27
31
|
}
|
|
@@ -2,17 +2,13 @@ import { Client } from "pg";
|
|
|
2
2
|
import { IMigration } from "./migration.interface.js";
|
|
3
3
|
export declare class CreateMetaOrgMigration implements IMigration {
|
|
4
4
|
private readonly client;
|
|
5
|
-
|
|
6
|
-
private readonly orgCode;
|
|
7
|
-
constructor(client: Client, orgName: string, orgCode: string);
|
|
5
|
+
constructor(client: Client);
|
|
8
6
|
index: number;
|
|
9
7
|
execute(): Promise<{
|
|
10
8
|
success: boolean;
|
|
11
9
|
error: Error;
|
|
12
|
-
metaOrgId?: undefined;
|
|
13
10
|
} | {
|
|
14
11
|
success: boolean;
|
|
15
|
-
metaOrgId: string;
|
|
16
12
|
error: null;
|
|
17
13
|
}>;
|
|
18
14
|
revert(): Promise<{
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
|
+
import { config } from "../../../config/index.js";
|
|
3
|
+
import { initializeSystemUserContext } from "@loomcore/common/models";
|
|
2
4
|
export class CreateMetaOrgMigration {
|
|
3
5
|
client;
|
|
4
|
-
|
|
5
|
-
orgCode;
|
|
6
|
-
constructor(client, orgName, orgCode) {
|
|
6
|
+
constructor(client) {
|
|
7
7
|
this.client = client;
|
|
8
|
-
this.orgName = orgName;
|
|
9
|
-
this.orgCode = orgCode;
|
|
10
8
|
}
|
|
11
9
|
index = 5;
|
|
12
10
|
async execute() {
|
|
@@ -15,12 +13,14 @@ export class CreateMetaOrgMigration {
|
|
|
15
13
|
await this.client.query('BEGIN');
|
|
16
14
|
const orgResult = await this.client.query(`
|
|
17
15
|
INSERT INTO "organizations" ("_id", "name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
18
|
-
VALUES ('${_id}', '${
|
|
16
|
+
VALUES ('${_id}', '${config.app.metaOrgName}', '${config.app.metaOrgCode}', 1, true, NOW(), 'system', NOW(), 'system')
|
|
17
|
+
RETURNING "_id";
|
|
19
18
|
`);
|
|
20
19
|
if (orgResult.rowCount === 0) {
|
|
21
20
|
await this.client.query('ROLLBACK');
|
|
22
21
|
return { success: false, error: new Error(`Error creating meta org: No row returned`) };
|
|
23
22
|
}
|
|
23
|
+
initializeSystemUserContext(config.email?.systemEmailAddress || 'system@example.com', orgResult.rows[0]._id);
|
|
24
24
|
const migrationResult = await this.client.query(`
|
|
25
25
|
INSERT INTO "migrations" ("_id", "index", "hasRun", "reverted")
|
|
26
26
|
VALUES ('${_id}', ${this.index}, TRUE, FALSE);
|
|
@@ -30,7 +30,7 @@ export class CreateMetaOrgMigration {
|
|
|
30
30
|
return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
|
|
31
31
|
}
|
|
32
32
|
await this.client.query('COMMIT');
|
|
33
|
-
return { success: true,
|
|
33
|
+
return { success: true, error: null };
|
|
34
34
|
}
|
|
35
35
|
catch (error) {
|
|
36
36
|
await this.client.query('ROLLBACK');
|
|
@@ -3,11 +3,14 @@ import { IMigration } from "../index.js";
|
|
|
3
3
|
export declare class CreateAdminUserMigration implements IMigration {
|
|
4
4
|
private readonly client;
|
|
5
5
|
constructor(client: Client);
|
|
6
|
+
private authService;
|
|
6
7
|
index: number;
|
|
7
|
-
execute(
|
|
8
|
+
execute(): Promise<{
|
|
8
9
|
success: boolean;
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
error: Error;
|
|
11
|
+
} | {
|
|
12
|
+
success: boolean;
|
|
13
|
+
error: null;
|
|
11
14
|
}>;
|
|
12
15
|
revert(): Promise<{
|
|
13
16
|
success: boolean;
|