@loomcore/api 0.1.26 → 0.1.28
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/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/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 +5 -3
- package/dist/databases/postgres/migrations/006-create-admin-user.migration.js +11 -9
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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/setup-for-auth.migration.d.ts +5 -2
- package/dist/databases/postgres/migrations/setup-for-auth.migration.js +15 -17
- package/dist/databases/postgres/migrations/setup-for-multitenant.migration.d.ts +5 -3
- package/dist/databases/postgres/migrations/setup-for-multitenant.migration.js +7 -30
- package/dist/models/base-api-config.interface.d.ts +6 -0
- 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(): 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() {
|
|
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: true },
|
|
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: undefined,
|
|
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,
|
|
@@ -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) {
|
|
@@ -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,7 +13,8 @@ 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');
|
|
@@ -29,8 +28,9 @@ export class CreateMetaOrgMigration {
|
|
|
29
28
|
await this.client.query('ROLLBACK');
|
|
30
29
|
return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
|
|
31
30
|
}
|
|
31
|
+
initializeSystemUserContext(config.email.systemEmailAddress || 'system@example.com', orgResult.rows[0]._id);
|
|
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');
|
|
@@ -4,10 +4,12 @@ export declare class CreateAdminUserMigration implements IMigration {
|
|
|
4
4
|
private readonly client;
|
|
5
5
|
constructor(client: Client);
|
|
6
6
|
index: number;
|
|
7
|
-
execute(
|
|
7
|
+
execute(): Promise<{
|
|
8
8
|
success: boolean;
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
error: Error;
|
|
10
|
+
} | {
|
|
11
|
+
success: boolean;
|
|
12
|
+
error: null;
|
|
11
13
|
}>;
|
|
12
14
|
revert(): Promise<{
|
|
13
15
|
success: boolean;
|
|
@@ -2,13 +2,14 @@ import { PostgresDatabase } from "../index.js";
|
|
|
2
2
|
import { randomUUID } from "crypto";
|
|
3
3
|
import { getSystemUserContext } from "@loomcore/common/models";
|
|
4
4
|
import { AuthService } from "../../../services/auth.service.js";
|
|
5
|
+
import { config } from "../../../config/index.js";
|
|
5
6
|
export class CreateAdminUserMigration {
|
|
6
7
|
client;
|
|
7
8
|
constructor(client) {
|
|
8
9
|
this.client = client;
|
|
9
10
|
}
|
|
10
11
|
index = 6;
|
|
11
|
-
async execute(
|
|
12
|
+
async execute() {
|
|
12
13
|
const _id = randomUUID().toString();
|
|
13
14
|
const systemUserContext = getSystemUserContext();
|
|
14
15
|
let createdUser;
|
|
@@ -18,17 +19,16 @@ export class CreateAdminUserMigration {
|
|
|
18
19
|
createdUser = await authService.createUser(systemUserContext, {
|
|
19
20
|
_id: _id,
|
|
20
21
|
_orgId: systemUserContext._orgId,
|
|
21
|
-
email:
|
|
22
|
-
password:
|
|
22
|
+
email: config.adminUser?.email,
|
|
23
|
+
password: config.adminUser?.password,
|
|
23
24
|
firstName: 'Admin',
|
|
24
25
|
lastName: 'User',
|
|
25
26
|
displayName: 'Admin User',
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
29
|
catch (error) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
30
|
+
console.error(`Error creating admin user: ${error.message}`);
|
|
31
|
+
return { success: false, error: new Error(`Error creating admin user: ${error.message}`) };
|
|
32
32
|
}
|
|
33
33
|
try {
|
|
34
34
|
const result = await this.client.query(`
|
|
@@ -36,13 +36,15 @@ export class CreateAdminUserMigration {
|
|
|
36
36
|
VALUES ('${_id}', ${this.index}, TRUE, FALSE);
|
|
37
37
|
`);
|
|
38
38
|
if (result.rowCount === 0) {
|
|
39
|
-
|
|
39
|
+
console.error(`Error inserting migration ${this.index} to migrations table: No row returned`);
|
|
40
|
+
return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
catch (error) {
|
|
43
|
-
|
|
44
|
+
console.error(`Error inserting migration ${this.index} to migrations table: ${error.message}`);
|
|
45
|
+
return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: ${error.message}`) };
|
|
44
46
|
}
|
|
45
|
-
return { success: true,
|
|
47
|
+
return { success: true, error: null };
|
|
46
48
|
}
|
|
47
49
|
async revert() {
|
|
48
50
|
throw new Error('Not implemented');
|
|
@@ -2,9 +2,9 @@ import { Client } from "pg";
|
|
|
2
2
|
import { IMigration } from "./migration.interface.js";
|
|
3
3
|
export declare class CreateAdminAuthorizationMigration implements IMigration {
|
|
4
4
|
private readonly client;
|
|
5
|
-
|
|
6
|
-
private
|
|
7
|
-
|
|
5
|
+
constructor(client: Client);
|
|
6
|
+
private organizationService;
|
|
7
|
+
private authService;
|
|
8
8
|
index: number;
|
|
9
9
|
execute(): Promise<{
|
|
10
10
|
success: boolean;
|
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
|
+
import { AuthService, OrganizationService } from "../../../services/index.js";
|
|
3
|
+
import { PostgresDatabase } from "../index.js";
|
|
4
|
+
import { config } from "../../../config/index.js";
|
|
5
|
+
import { EmptyUserContext } from "@loomcore/common/models";
|
|
2
6
|
export class CreateAdminAuthorizationMigration {
|
|
3
7
|
client;
|
|
4
|
-
|
|
5
|
-
metaOrgId;
|
|
6
|
-
constructor(client, adminUserId, metaOrgId) {
|
|
8
|
+
constructor(client) {
|
|
7
9
|
this.client = client;
|
|
8
|
-
|
|
9
|
-
this.
|
|
10
|
+
const database = new PostgresDatabase(this.client);
|
|
11
|
+
this.organizationService = new OrganizationService(database);
|
|
12
|
+
this.authService = new AuthService(database);
|
|
10
13
|
}
|
|
14
|
+
organizationService;
|
|
15
|
+
authService;
|
|
11
16
|
index = 11;
|
|
12
17
|
async execute() {
|
|
13
18
|
const _id = randomUUID().toString();
|
|
14
19
|
try {
|
|
20
|
+
const metaOrg = await this.organizationService.getMetaOrg(EmptyUserContext);
|
|
21
|
+
if (!config.adminUser?.email) {
|
|
22
|
+
return { success: false, error: new Error('Create admin authorization: Admin user email not found in config') };
|
|
23
|
+
}
|
|
24
|
+
const adminUser = await this.authService.getUserByEmail(config.adminUser?.email);
|
|
25
|
+
if (!metaOrg || !adminUser) {
|
|
26
|
+
return { success: false, error: new Error('Create admin authorization: Meta organization or admin user not found') };
|
|
27
|
+
}
|
|
15
28
|
await this.client.query('BEGIN');
|
|
16
29
|
const roleId = randomUUID().toString();
|
|
17
30
|
const roleResult = await this.client.query(`
|
|
18
31
|
INSERT INTO "roles" ("_id", "_orgId", "name")
|
|
19
32
|
VALUES ($1, $2, 'admin')
|
|
20
|
-
`, [roleId,
|
|
33
|
+
`, [roleId, metaOrg?._id]);
|
|
21
34
|
if (roleResult.rowCount === 0) {
|
|
22
35
|
await this.client.query('ROLLBACK');
|
|
23
36
|
return { success: false, error: new Error('Failed to create admin role') };
|
|
@@ -26,7 +39,7 @@ export class CreateAdminAuthorizationMigration {
|
|
|
26
39
|
const userRoleResult = await this.client.query(`
|
|
27
40
|
INSERT INTO "user_roles" ("_id", "_orgId", "_userId", "_roleId", "_created", "_createdBy", "_updated", "_updatedBy")
|
|
28
41
|
VALUES ($1, $2, $3, $4, NOW(), 'system', NOW(), 'system')
|
|
29
|
-
`, [userRoleId,
|
|
42
|
+
`, [userRoleId, metaOrg?._id, adminUser?._id, roleId]);
|
|
30
43
|
if (userRoleResult.rowCount === 0) {
|
|
31
44
|
await this.client.query('ROLLBACK');
|
|
32
45
|
return { success: false, error: new Error('Failed to create user role') };
|
|
@@ -35,7 +48,7 @@ export class CreateAdminAuthorizationMigration {
|
|
|
35
48
|
const featureResult = await this.client.query(`
|
|
36
49
|
INSERT INTO "features" ("_id", "_orgId", "name")
|
|
37
50
|
VALUES ($1, $2, 'admin')
|
|
38
|
-
`, [featureId,
|
|
51
|
+
`, [featureId, metaOrg?._id]);
|
|
39
52
|
if (featureResult.rowCount === 0) {
|
|
40
53
|
await this.client.query('ROLLBACK');
|
|
41
54
|
return { success: false, error: new Error('Failed to create admin feature') };
|
|
@@ -47,7 +60,7 @@ export class CreateAdminAuthorizationMigration {
|
|
|
47
60
|
"_created", "_createdBy", "_updated", "_updatedBy"
|
|
48
61
|
)
|
|
49
62
|
VALUES ($1, $2, $3, $4, NOW(), 'system', NOW(), 'system')
|
|
50
|
-
`, [authorizationId,
|
|
63
|
+
`, [authorizationId, metaOrg?._id, roleId, featureId]);
|
|
51
64
|
if (authorizationResult.rowCount === 0) {
|
|
52
65
|
await this.client.query('ROLLBACK');
|
|
53
66
|
return { success: false, error: new Error('Failed to create admin authorization') };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Client } from "pg";
|
|
2
|
-
export declare function setupDatabaseForAuth(client: Client
|
|
2
|
+
export declare function setupDatabaseForAuth(client: Client): Promise<{
|
|
3
3
|
success: boolean;
|
|
4
|
-
error: Error
|
|
4
|
+
error: Error;
|
|
5
|
+
} | {
|
|
6
|
+
success: boolean;
|
|
7
|
+
error: null;
|
|
5
8
|
}>;
|
|
@@ -8,7 +8,7 @@ import { CreateRoleTableMigration } from "./007-create-roles-table.migration.js"
|
|
|
8
8
|
import { CreateAuthorizationsTableMigration } from "./010-create-authorizations-table.migration.js";
|
|
9
9
|
import { CreateFeaturesTableMigration } from "./009-create-features-table.migration.js";
|
|
10
10
|
import { CreateAdminAuthorizationMigration } from "./011-create-admin-authorization.migration.js";
|
|
11
|
-
export async function setupDatabaseForAuth(client
|
|
11
|
+
export async function setupDatabaseForAuth(client) {
|
|
12
12
|
let runMigrations = [];
|
|
13
13
|
if (await doesTableExist(client, 'migrations')) {
|
|
14
14
|
const migrations = await client.query(`
|
|
@@ -20,13 +20,12 @@ export async function setupDatabaseForAuth(client, adminUsername, adminPassword,
|
|
|
20
20
|
return row.index;
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
-
let adminUserId;
|
|
24
23
|
if (!runMigrations.includes(1)) {
|
|
25
24
|
const migration = new CreateMigrationTableMigration(client);
|
|
26
25
|
const result = await migration.execute();
|
|
27
26
|
if (!result.success) {
|
|
28
27
|
console.error('setupDatabaseForAuth: error creating migrations table', result.error);
|
|
29
|
-
return
|
|
28
|
+
return result;
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
if (!runMigrations.includes(3)) {
|
|
@@ -34,7 +33,7 @@ export async function setupDatabaseForAuth(client, adminUsername, adminPassword,
|
|
|
34
33
|
const result = await migration.execute();
|
|
35
34
|
if (!result.success) {
|
|
36
35
|
console.error('setupDatabaseForAuth: error creating users table', result.error);
|
|
37
|
-
return
|
|
36
|
+
return result;
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
if (!runMigrations.includes(4)) {
|
|
@@ -42,20 +41,23 @@ export async function setupDatabaseForAuth(client, adminUsername, adminPassword,
|
|
|
42
41
|
const result = await migration.execute();
|
|
43
42
|
if (!result.success) {
|
|
44
43
|
console.error('setupDatabaseForAuth: error creating refresh_tokens table', result.error);
|
|
45
|
-
return
|
|
44
|
+
return result;
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
if (!runMigrations.includes(6)) {
|
|
49
48
|
const migration = new CreateAdminUserMigration(client);
|
|
50
|
-
const result = await migration.execute(
|
|
51
|
-
|
|
49
|
+
const result = await migration.execute();
|
|
50
|
+
if (!result.success) {
|
|
51
|
+
console.error('setupDatabaseForAuth: error creating admin user', result.error);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
52
54
|
}
|
|
53
55
|
if (!runMigrations.includes(7)) {
|
|
54
56
|
const migration = new CreateRoleTableMigration(client);
|
|
55
57
|
const result = await migration.execute();
|
|
56
58
|
if (!result.success) {
|
|
57
59
|
console.error('setupDatabaseForAuth: error creating roles table', result.error);
|
|
58
|
-
return
|
|
60
|
+
return result;
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
if (!runMigrations.includes(8)) {
|
|
@@ -63,7 +65,7 @@ export async function setupDatabaseForAuth(client, adminUsername, adminPassword,
|
|
|
63
65
|
const result = await migration.execute();
|
|
64
66
|
if (!result.success) {
|
|
65
67
|
console.error('setupDatabaseForAuth: error creating user_roles table', result.error);
|
|
66
|
-
return
|
|
68
|
+
return result;
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
if (!runMigrations.includes(9)) {
|
|
@@ -71,7 +73,7 @@ export async function setupDatabaseForAuth(client, adminUsername, adminPassword,
|
|
|
71
73
|
const result = await migration.execute();
|
|
72
74
|
if (!result.success) {
|
|
73
75
|
console.error('setupDatabaseForAuth: error creating features table', result.error);
|
|
74
|
-
return
|
|
76
|
+
return result;
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
if (!runMigrations.includes(10)) {
|
|
@@ -79,19 +81,15 @@ export async function setupDatabaseForAuth(client, adminUsername, adminPassword,
|
|
|
79
81
|
const result = await migration.execute();
|
|
80
82
|
if (!result.success) {
|
|
81
83
|
console.error('setupDatabaseForAuth: error creating authorizations table', result.error);
|
|
82
|
-
return
|
|
84
|
+
return result;
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
if (!runMigrations.includes(11)) {
|
|
86
|
-
|
|
87
|
-
console.error('setupDatabaseForAuth: Admin user ID is required');
|
|
88
|
-
return { success: true, error: null };
|
|
89
|
-
}
|
|
90
|
-
const migration = new CreateAdminAuthorizationMigration(client, adminUserId, metaOrgId);
|
|
88
|
+
const migration = new CreateAdminAuthorizationMigration(client);
|
|
91
89
|
const result = await migration.execute();
|
|
92
90
|
if (!result.success) {
|
|
93
91
|
console.error('setupDatabaseForAuth: error creating admin authorization', result.error);
|
|
94
|
-
return
|
|
92
|
+
return result;
|
|
95
93
|
}
|
|
96
94
|
}
|
|
97
95
|
return { success: true, error: null };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Client } from "pg";
|
|
2
|
-
export declare function setupDatabaseForMultitenant(client: Client
|
|
2
|
+
export declare function setupDatabaseForMultitenant(client: Client): Promise<{
|
|
3
3
|
success: boolean;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
error: Error;
|
|
5
|
+
} | {
|
|
6
|
+
success: boolean;
|
|
7
|
+
error: null;
|
|
6
8
|
}>;
|
|
@@ -2,12 +2,8 @@ import { CreateMigrationTableMigration } from "./001-create-migrations-table.mig
|
|
|
2
2
|
import { CreateOrganizationsTableMigration } from "./002-create-organizations-table.migration.js";
|
|
3
3
|
import { doesTableExist } from "../utils/does-table-exist.util.js";
|
|
4
4
|
import { CreateMetaOrgMigration } from "./005-create-meta-org.migration.js";
|
|
5
|
-
|
|
6
|
-
import { OrganizationService } from "../../../services/index.js";
|
|
7
|
-
import { EmptyUserContext } from "@loomcore/common/models";
|
|
8
|
-
export async function setupDatabaseForMultitenant(client, metaOrgName, metaOrgCode) {
|
|
5
|
+
export async function setupDatabaseForMultitenant(client) {
|
|
9
6
|
let runMigrations = [];
|
|
10
|
-
let metaOrgId;
|
|
11
7
|
if (await doesTableExist(client, 'migrations')) {
|
|
12
8
|
const migrations = await client.query(`
|
|
13
9
|
SELECT "_id", "index"
|
|
@@ -23,7 +19,7 @@ export async function setupDatabaseForMultitenant(client, metaOrgName, metaOrgCo
|
|
|
23
19
|
const result = await createMigrationTableMigration.execute();
|
|
24
20
|
if (!result.success) {
|
|
25
21
|
console.log('setupDatabaseForMultitenant: error creating migration table', result.error);
|
|
26
|
-
return
|
|
22
|
+
return result;
|
|
27
23
|
}
|
|
28
24
|
}
|
|
29
25
|
if (!runMigrations.includes(2)) {
|
|
@@ -31,35 +27,16 @@ export async function setupDatabaseForMultitenant(client, metaOrgName, metaOrgCo
|
|
|
31
27
|
const result = await createOrganizationTableMigration.execute();
|
|
32
28
|
if (!result.success) {
|
|
33
29
|
console.log('setupDatabaseForMultitenant: error creating organizations table', result.error);
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
const database = new PostgresDatabase(client);
|
|
39
|
-
const organizationService = new OrganizationService(database);
|
|
40
|
-
const metaOrg = await organizationService.getMetaOrg(EmptyUserContext);
|
|
41
|
-
if (metaOrg) {
|
|
42
|
-
metaOrgId = metaOrg._id;
|
|
30
|
+
return result;
|
|
43
31
|
}
|
|
44
32
|
}
|
|
45
33
|
if (!runMigrations.includes(5)) {
|
|
46
|
-
const createMetaOrgMigration = new CreateMetaOrgMigration(client
|
|
34
|
+
const createMetaOrgMigration = new CreateMetaOrgMigration(client);
|
|
47
35
|
const result = await createMetaOrgMigration.execute();
|
|
48
|
-
if (!result.success
|
|
36
|
+
if (!result.success) {
|
|
49
37
|
console.log('setupDatabaseForMultitenant: error creating meta org', result.error);
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
metaOrgId = result.metaOrgId;
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
if (!metaOrgId) {
|
|
56
|
-
const database = new PostgresDatabase(client);
|
|
57
|
-
const organizationService = new OrganizationService(database);
|
|
58
|
-
const metaOrg = await organizationService.getMetaOrg(EmptyUserContext);
|
|
59
|
-
if (metaOrg) {
|
|
60
|
-
metaOrgId = metaOrg._id;
|
|
61
|
-
}
|
|
38
|
+
return result;
|
|
62
39
|
}
|
|
63
40
|
}
|
|
64
|
-
return { success: true,
|
|
41
|
+
return { success: true, error: null };
|
|
65
42
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Request, Response } from 'express';
|
|
2
|
-
import { TSchema } from '@sinclair/typebox';
|
|
3
2
|
import { IQueryOptions, IError, IPagedResult, IModelSpec } from '@loomcore/common/models';
|
|
4
3
|
export interface IApiResponseOptions<T> {
|
|
5
4
|
messages?: string[];
|
|
6
5
|
errors?: IError[];
|
|
7
6
|
data?: T;
|
|
8
7
|
}
|
|
9
|
-
declare function apiResponse<T>(response: Response, status: number, options?: IApiResponseOptions<T>, modelSpec?: IModelSpec,
|
|
8
|
+
declare function apiResponse<T>(response: Response, status: number, options?: IApiResponseOptions<T>, modelSpec?: IModelSpec, publicSpec?: IModelSpec): Response;
|
|
10
9
|
declare function getQueryOptionsFromRequest(request: Request): IQueryOptions;
|
|
11
10
|
declare function getPagedResult<T>(entities: T[], totalRows: number, queryOptions: IQueryOptions): IPagedResult<T>;
|
|
12
11
|
export declare const apiUtils: {
|
package/dist/utils/api.utils.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { entityUtils } from '@loomcore/common/utils';
|
|
2
1
|
import { DefaultQueryOptions } from '@loomcore/common/models';
|
|
3
|
-
function apiResponse(response, status, options = {}, modelSpec,
|
|
2
|
+
function apiResponse(response, status, options = {}, modelSpec, publicSpec) {
|
|
4
3
|
const success = status >= 200 && status < 300;
|
|
5
4
|
let apiResponse;
|
|
6
|
-
const specForEncoding =
|
|
5
|
+
const specForEncoding = publicSpec ?? modelSpec;
|
|
7
6
|
if (specForEncoding && options.data) {
|
|
8
7
|
if (Array.isArray(options.data)) {
|
|
9
8
|
options.data = options.data.map((item) => specForEncoding.encode(item));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loomcore/api",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb or PostgreSQL",
|
|
6
6
|
"scripts": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"qs": "^6.14.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@loomcore/common": "^0.0.
|
|
56
|
+
"@loomcore/common": "^0.0.29",
|
|
57
57
|
"@sinclair/typebox": "0.34.33",
|
|
58
58
|
"cookie-parser": "^1.4.6",
|
|
59
59
|
"cors": "^2.8.5",
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Client } from "pg";
|
|
2
|
-
import { IMigration } from "../../databases/postgres/migrations/index.js";
|
|
3
|
-
export declare class CreateTestUsersTableMigration implements IMigration {
|
|
4
|
-
private readonly client;
|
|
5
|
-
constructor(client: Client);
|
|
6
|
-
index: number;
|
|
7
|
-
execute(_orgId?: string): Promise<{
|
|
8
|
-
success: boolean;
|
|
9
|
-
error: Error;
|
|
10
|
-
} | {
|
|
11
|
-
success: boolean;
|
|
12
|
-
error: null;
|
|
13
|
-
}>;
|
|
14
|
-
revert(_orgId?: string): Promise<{
|
|
15
|
-
success: boolean;
|
|
16
|
-
error: Error;
|
|
17
|
-
} | {
|
|
18
|
-
success: boolean;
|
|
19
|
-
error: null;
|
|
20
|
-
}>;
|
|
21
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "crypto";
|
|
2
|
-
export class CreateTestUsersTableMigration {
|
|
3
|
-
client;
|
|
4
|
-
constructor(client) {
|
|
5
|
-
this.client = client;
|
|
6
|
-
}
|
|
7
|
-
index = 4;
|
|
8
|
-
async execute(_orgId) {
|
|
9
|
-
const _id = randomUUID().toString();
|
|
10
|
-
try {
|
|
11
|
-
await this.client.query(`
|
|
12
|
-
CREATE TABLE "testUsers" (
|
|
13
|
-
"_id" VARCHAR(255) PRIMARY KEY,
|
|
14
|
-
"_orgId" VARCHAR(255),
|
|
15
|
-
"email" VARCHAR(255) NOT NULL,
|
|
16
|
-
"password" VARCHAR(255) NOT NULL,
|
|
17
|
-
"firstName" VARCHAR(255),
|
|
18
|
-
"lastName" VARCHAR(255),
|
|
19
|
-
"displayName" VARCHAR(255),
|
|
20
|
-
"_lastLoggedIn" TIMESTAMP,
|
|
21
|
-
"_lastPasswordChange" TIMESTAMP,
|
|
22
|
-
"_created" TIMESTAMP NOT NULL,
|
|
23
|
-
"_createdBy" VARCHAR(255) NOT NULL,
|
|
24
|
-
"_updated" TIMESTAMP NOT NULL,
|
|
25
|
-
"_updatedBy" VARCHAR(255) NOT NULL,
|
|
26
|
-
"_deleted" TIMESTAMP,
|
|
27
|
-
"_deletedBy" VARCHAR(255)
|
|
28
|
-
)
|
|
29
|
-
`);
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
return { success: false, error: new Error(`Error creating test users table: ${error.message}`) };
|
|
33
|
-
}
|
|
34
|
-
if (_orgId) {
|
|
35
|
-
try {
|
|
36
|
-
await this.client.query(`
|
|
37
|
-
Insert into "migrations" ("_id", "_orgId", "index", "hasRun", "reverted") values ('${_id}', '${_orgId}', ${this.index}, TRUE, FALSE);
|
|
38
|
-
`);
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: ${error.message}`) };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
try {
|
|
46
|
-
await this.client.query(`
|
|
47
|
-
Insert into "migrations" ("_id", "index", "hasRun", "reverted") values ('${_id}', ${this.index}, TRUE, FALSE);
|
|
48
|
-
`);
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: ${error.message}`) };
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return { success: true, error: null };
|
|
55
|
-
}
|
|
56
|
-
async revert(_orgId) {
|
|
57
|
-
try {
|
|
58
|
-
await this.client.query(`
|
|
59
|
-
DROP TABLE "testUsers";
|
|
60
|
-
`);
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
return { success: false, error: new Error(`Error dropping test users table: ${error.message}`) };
|
|
64
|
-
}
|
|
65
|
-
try {
|
|
66
|
-
await this.client.query(`
|
|
67
|
-
Update "migrations" SET "reverted" = TRUE WHERE "index" = '${this.index}' AND "_orgId" = '${_orgId}';
|
|
68
|
-
`);
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
return { success: false, error: new Error(`Error updating migration record: ${error.message}`) };
|
|
72
|
-
}
|
|
73
|
-
return { success: true, error: null };
|
|
74
|
-
}
|
|
75
|
-
}
|