@loomcore/api 0.0.21 → 0.0.22
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 -1
- package/dist/__tests__/common-test.utils.js +53 -4
- package/dist/__tests__/test-express-app.js +4 -1
- package/dist/config/base-api-config.d.ts +2 -0
- package/dist/config/base-api-config.js +22 -0
- package/dist/models/base-api-config.interface.d.ts +1 -0
- package/dist/services/auth.service.js +4 -3
- package/dist/services/generic-api.service.js +6 -2
- package/dist/services/organization.service.d.ts +1 -0
- package/dist/services/organization.service.js +4 -0
- package/package.json +2 -2
|
@@ -2,6 +2,8 @@ import { Db, ObjectId } from 'mongodb';
|
|
|
2
2
|
import { IUser, IUserContext } from '@loomcore/common/models';
|
|
3
3
|
declare function initialize(database: Db): void;
|
|
4
4
|
declare function createIndexes(db: Db): Promise<void>;
|
|
5
|
+
declare function createMetaOrg(): Promise<void>;
|
|
6
|
+
declare function deleteMetaOrg(): Promise<void>;
|
|
5
7
|
declare function setupTestUser(): Promise<{
|
|
6
8
|
_id: ObjectId;
|
|
7
9
|
email: string;
|
|
@@ -12,17 +14,21 @@ declare function setupTestUser(): Promise<{
|
|
|
12
14
|
_updated: Date;
|
|
13
15
|
_updatedBy: string;
|
|
14
16
|
}>;
|
|
15
|
-
declare function deleteTestUser(): Promise<
|
|
17
|
+
declare function deleteTestUser(): Promise<any[]>;
|
|
16
18
|
declare function simulateloginWithTestUser(): Promise<string>;
|
|
17
19
|
declare function getAuthToken(): string;
|
|
18
20
|
declare function verifyToken(token: string): any;
|
|
19
21
|
declare function getTestUser(): Partial<IUser>;
|
|
20
22
|
declare function configureJwtSecret(): void;
|
|
21
23
|
declare function loginWithTestUser(agent: any): Promise<string>;
|
|
24
|
+
declare function cleanup(): Promise<void>;
|
|
22
25
|
declare const testUtils: {
|
|
26
|
+
cleanup: typeof cleanup;
|
|
23
27
|
configureJwtSecret: typeof configureJwtSecret;
|
|
24
28
|
constDeviceIdCookie: string;
|
|
25
29
|
createIndexes: typeof createIndexes;
|
|
30
|
+
createMetaOrg: typeof createMetaOrg;
|
|
31
|
+
deleteMetaOrg: typeof deleteMetaOrg;
|
|
26
32
|
deleteTestUser: typeof deleteTestUser;
|
|
27
33
|
getAuthToken: typeof getAuthToken;
|
|
28
34
|
getTestUser: typeof getTestUser;
|
|
@@ -43,9 +43,43 @@ async function createIndexes(db) {
|
|
|
43
43
|
createIndexes: "users", indexes: [{ key: { email: 1 }, name: 'email_index', unique: true, collation: { locale: 'en', strength: 1 } }]
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
|
+
async function createMetaOrg() {
|
|
47
|
+
if (!db || !collections.organizations) {
|
|
48
|
+
throw new Error('Database not initialized. Call initialize() first.');
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const existingMetaOrg = await collections.organizations.findOne({ isMetaOrg: true });
|
|
52
|
+
if (!existingMetaOrg) {
|
|
53
|
+
const metaOrgInsertResult = await collections.organizations.insertOne({
|
|
54
|
+
_id: new ObjectId(),
|
|
55
|
+
name: 'Meta Organization',
|
|
56
|
+
isMetaOrg: true,
|
|
57
|
+
_created: new Date(),
|
|
58
|
+
_createdBy: 'system',
|
|
59
|
+
_updated: new Date(),
|
|
60
|
+
_updatedBy: 'system'
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.log('Error in createMetaOrg:', error);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function deleteMetaOrg() {
|
|
70
|
+
if (!collections.organizations) {
|
|
71
|
+
return Promise.resolve();
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await collections.organizations.deleteOne({ isMetaOrg: true });
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.log('Error deleting meta org:', error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
46
80
|
async function setupTestUser() {
|
|
47
81
|
try {
|
|
48
|
-
|
|
82
|
+
await deleteTestUser();
|
|
49
83
|
return createTestUser();
|
|
50
84
|
}
|
|
51
85
|
catch (error) {
|
|
@@ -91,11 +125,14 @@ async function createTestUser() {
|
|
|
91
125
|
}
|
|
92
126
|
}
|
|
93
127
|
function deleteTestUser() {
|
|
94
|
-
let
|
|
128
|
+
let promises = [];
|
|
95
129
|
if (testUser) {
|
|
96
|
-
|
|
130
|
+
promises.push(collections.users.deleteOne({ _id: testUser._id }));
|
|
97
131
|
}
|
|
98
|
-
|
|
132
|
+
if (collections.organizations) {
|
|
133
|
+
promises.push(collections.organizations.deleteOne({ _id: new ObjectId(testOrgId) }));
|
|
134
|
+
}
|
|
135
|
+
return Promise.all(promises);
|
|
99
136
|
}
|
|
100
137
|
async function simulateloginWithTestUser() {
|
|
101
138
|
const req = {
|
|
@@ -156,10 +193,22 @@ async function loginWithTestUser(agent) {
|
|
|
156
193
|
const authorizationHeaderValue = `Bearer ${response.body?.data?.tokens?.accessToken}`;
|
|
157
194
|
return authorizationHeaderValue;
|
|
158
195
|
}
|
|
196
|
+
async function cleanup() {
|
|
197
|
+
try {
|
|
198
|
+
await deleteTestUser();
|
|
199
|
+
await deleteMetaOrg();
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
console.log('Error during cleanup:', error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
159
205
|
const testUtils = {
|
|
206
|
+
cleanup,
|
|
160
207
|
configureJwtSecret,
|
|
161
208
|
constDeviceIdCookie,
|
|
162
209
|
createIndexes,
|
|
210
|
+
createMetaOrg,
|
|
211
|
+
deleteMetaOrg,
|
|
163
212
|
deleteTestUser,
|
|
164
213
|
getAuthToken,
|
|
165
214
|
getTestUser,
|
|
@@ -6,7 +6,7 @@ import { MongoMemoryServer } from 'mongodb-memory-server';
|
|
|
6
6
|
import { MongoClient } from 'mongodb';
|
|
7
7
|
import { initializeTypeBox } from '@loomcore/common/validation';
|
|
8
8
|
import testUtils from './common-test.utils.js';
|
|
9
|
-
import { setBaseApiConfig } from '../config/base-api-config.js';
|
|
9
|
+
import { setBaseApiConfig, initSystemUserContext } from '../config/base-api-config.js';
|
|
10
10
|
import { errorHandler } from '../middleware/error-handler.js';
|
|
11
11
|
import { ensureUserContext } from '../middleware/ensure-user-context.js';
|
|
12
12
|
export class TestExpressApp {
|
|
@@ -66,7 +66,9 @@ export class TestExpressApp {
|
|
|
66
66
|
this.db = this.client.db();
|
|
67
67
|
testUtils.initialize(this.db);
|
|
68
68
|
await testUtils.createIndexes(this.db);
|
|
69
|
+
await testUtils.createMetaOrg();
|
|
69
70
|
}
|
|
71
|
+
await initSystemUserContext(this.db);
|
|
70
72
|
if (!this.app) {
|
|
71
73
|
this.app = express();
|
|
72
74
|
this.app.use(bodyParser.json());
|
|
@@ -87,6 +89,7 @@ export class TestExpressApp {
|
|
|
87
89
|
this.app.use(errorHandler);
|
|
88
90
|
}
|
|
89
91
|
static async cleanup() {
|
|
92
|
+
await testUtils.cleanup();
|
|
90
93
|
if (this.client) {
|
|
91
94
|
await this.client.close();
|
|
92
95
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Db } from 'mongodb';
|
|
1
2
|
import { IBaseApiConfig } from '../models/index.js';
|
|
2
3
|
export declare let config: IBaseApiConfig;
|
|
3
4
|
export declare function setBaseApiConfig(apiConfig: IBaseApiConfig): void;
|
|
5
|
+
export declare function initSystemUserContext(db: Db): Promise<void>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { EmptyUserContext, initializeSystemUserContext } from '@loomcore/common/models';
|
|
1
2
|
export let config;
|
|
2
3
|
let isConfigSet = false;
|
|
4
|
+
let isSystemUserContextSet = false;
|
|
3
5
|
function copyOnlyBaseApiConfigProperties(obj) {
|
|
4
6
|
const baseConfig = {};
|
|
5
7
|
Object.keys(obj).forEach((key) => {
|
|
@@ -16,3 +18,23 @@ export function setBaseApiConfig(apiConfig) {
|
|
|
16
18
|
console.warn('BaseApiConfig data has already been set. Ignoring subsequent calls to setBaseApiConfig.');
|
|
17
19
|
}
|
|
18
20
|
}
|
|
21
|
+
export async function initSystemUserContext(db) {
|
|
22
|
+
if (!isConfigSet) {
|
|
23
|
+
throw new Error('BaseApiConfig has not been set. Call setBaseApiConfig first.');
|
|
24
|
+
}
|
|
25
|
+
if (!isSystemUserContextSet) {
|
|
26
|
+
const systemEmail = config.email.systemEmailAddress || 'system@example.com';
|
|
27
|
+
let metaOrgId = undefined;
|
|
28
|
+
if (config.app.isMultiTenant) {
|
|
29
|
+
const { OrganizationService } = await import('../services/organization.service.js');
|
|
30
|
+
const organizationService = new OrganizationService(db);
|
|
31
|
+
const metaOrg = await organizationService.getMetaOrg(EmptyUserContext);
|
|
32
|
+
metaOrgId = metaOrg._id;
|
|
33
|
+
}
|
|
34
|
+
initializeSystemUserContext(systemEmail, metaOrgId);
|
|
35
|
+
isSystemUserContextSet = true;
|
|
36
|
+
}
|
|
37
|
+
else if (config.env !== 'test') {
|
|
38
|
+
console.warn('SystemUserContext has already been set. Ignoring subsequent calls to initSystemUserContext.');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ObjectId } from 'mongodb';
|
|
2
2
|
import moment from 'moment';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
|
-
import { EmptyUserContext, passwordValidator, UserSpec } from '@loomcore/common/models';
|
|
4
|
+
import { EmptyUserContext, passwordValidator, UserSpec, getSystemUserContext } from '@loomcore/common/models';
|
|
5
5
|
import { entityUtils } from '@loomcore/common/utils';
|
|
6
6
|
import { BadRequestError, ServerError } from '../errors/index.js';
|
|
7
7
|
import { JwtService, EmailService } from './index.js';
|
|
@@ -49,7 +49,7 @@ export class AuthService extends GenericApiService {
|
|
|
49
49
|
refreshToken: refreshTokenObject.token,
|
|
50
50
|
expiresOn: accessTokenExpiresOn
|
|
51
51
|
};
|
|
52
|
-
this.updateLastLoggedIn(userContext.user._id
|
|
52
|
+
this.updateLastLoggedIn(userContext.user._id)
|
|
53
53
|
.catch(err => console.log(`Error updating lastLoggedIn: ${err}`));
|
|
54
54
|
this.transformSingle(userContext.user);
|
|
55
55
|
loginResponse = { tokens: tokenResponse, userContext };
|
|
@@ -267,7 +267,8 @@ export class AuthService extends GenericApiService {
|
|
|
267
267
|
throw new BadRequestError('userId is not a valid ObjectId');
|
|
268
268
|
}
|
|
269
269
|
const updates = { _lastLoggedIn: moment().utc().toISOString() };
|
|
270
|
-
|
|
270
|
+
const systemUserContext = getSystemUserContext();
|
|
271
|
+
await this.partialUpdateById(systemUserContext, userId, updates);
|
|
271
272
|
}
|
|
272
273
|
catch (error) {
|
|
273
274
|
console.log(`Failed to update lastLoggedIn for user ${userId}: ${error}`);
|
|
@@ -337,7 +337,11 @@ export class GenericApiService {
|
|
|
337
337
|
const transformedEntity = dbUtils.convertObjectIdsToStrings(single, this.modelSpec.fullSchema);
|
|
338
338
|
return transformedEntity;
|
|
339
339
|
}
|
|
340
|
-
stripSenderProvidedSystemProperties(doc) {
|
|
340
|
+
stripSenderProvidedSystemProperties(userContext, doc) {
|
|
341
|
+
const isSystemUser = userContext.user?._id === 'system';
|
|
342
|
+
if (isSystemUser) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
341
345
|
for (const key in doc) {
|
|
342
346
|
if (Object.prototype.hasOwnProperty.call(doc, key) && key.startsWith('_') && key !== '_orgId') {
|
|
343
347
|
delete doc[key];
|
|
@@ -356,7 +360,7 @@ export class GenericApiService {
|
|
|
356
360
|
}
|
|
357
361
|
async prepareEntity(userContext, entity, isCreate) {
|
|
358
362
|
const preparedEntity = _.clone(entity);
|
|
359
|
-
this.stripSenderProvidedSystemProperties(preparedEntity);
|
|
363
|
+
this.stripSenderProvidedSystemProperties(userContext, preparedEntity);
|
|
360
364
|
if (this.modelSpec?.isAuditable) {
|
|
361
365
|
if (isCreate) {
|
|
362
366
|
this.auditForCreate(userContext, preparedEntity);
|
|
@@ -5,4 +5,5 @@ export declare class OrganizationService extends GenericApiService<IOrganization
|
|
|
5
5
|
constructor(db: Db);
|
|
6
6
|
getAuthTokenByRepoCode(userContext: IUserContext, orgId: string): Promise<string | null | undefined>;
|
|
7
7
|
validateRepoAuthToken(userContext: IUserContext, orgCode: string, authToken: string): Promise<string | null>;
|
|
8
|
+
getMetaOrg(userContext: IUserContext): Promise<IOrganization>;
|
|
8
9
|
}
|
|
@@ -13,4 +13,8 @@ export class OrganizationService extends GenericApiService {
|
|
|
13
13
|
const orgId = org.authToken === authToken ? org._id.toString() : null;
|
|
14
14
|
return orgId;
|
|
15
15
|
}
|
|
16
|
+
async getMetaOrg(userContext) {
|
|
17
|
+
const org = await this.findOne(userContext, { isMetaOrg: true });
|
|
18
|
+
return org;
|
|
19
|
+
}
|
|
16
20
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loomcore/api",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb",
|
|
6
6
|
"scripts": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"jsonwebtoken": "^9.0.2"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@loomcore/common": "^0.0.
|
|
47
|
+
"@loomcore/common": "^0.0.12",
|
|
48
48
|
"@sinclair/typebox": "^0.34.31",
|
|
49
49
|
"cookie-parser": "^1.4.6",
|
|
50
50
|
"cors": "^2.8.5",
|