@lenne.tech/nest-server 11.4.6 → 11.4.8
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/core/common/decorators/restricted.decorator.d.ts +2 -0
- package/dist/core/common/decorators/restricted.decorator.js +17 -16
- package/dist/core/common/decorators/restricted.decorator.js.map +1 -1
- package/dist/core/common/enums/role.enum.d.ts +1 -0
- package/dist/core/common/enums/role.enum.js +1 -0
- package/dist/core/common/enums/role.enum.js.map +1 -1
- package/dist/core/common/helpers/input.helper.d.ts +2 -0
- package/dist/core/common/helpers/input.helper.js +2 -1
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/modules/migrate/cli/migrate-cli.js +3 -3
- package/dist/core/modules/migrate/cli/migrate-cli.js.map +1 -1
- package/dist/core/modules/migrate/helpers/migration.helper.d.ts +2 -0
- package/dist/core/modules/migrate/helpers/migration.helper.js +17 -1
- package/dist/core/modules/migrate/helpers/migration.helper.js.map +1 -1
- package/dist/core/modules/migrate/migration-runner.js +35 -21
- package/dist/core/modules/migrate/migration-runner.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/common/decorators/restricted.decorator.ts +18 -17
- package/src/core/common/enums/role.enum.ts +5 -1
- package/src/core/common/helpers/input.helper.ts +4 -2
- package/src/core/modules/migrate/cli/migrate-cli.ts +3 -3
- package/src/core/modules/migrate/helpers/migration.helper.ts +44 -0
- package/src/core/modules/migrate/migration-runner.ts +47 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.4.
|
|
3
|
+
"version": "11.4.8",
|
|
4
4
|
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node",
|
|
@@ -61,7 +61,7 @@ export const getRestricted = (object: unknown, propertyKey?: string): Restricted
|
|
|
61
61
|
*/
|
|
62
62
|
export const checkRestricted = (
|
|
63
63
|
data: any,
|
|
64
|
-
user: { hasRole: (roles: string[]) => boolean; id: any },
|
|
64
|
+
user: { hasRole: (roles: string[]) => boolean; id: any; verified?: any; verifiedAt?: any },
|
|
65
65
|
options: {
|
|
66
66
|
allowCreatorOfParent?: boolean;
|
|
67
67
|
checkObjectItself?: boolean;
|
|
@@ -108,9 +108,9 @@ export const checkRestricted = (
|
|
|
108
108
|
// Array
|
|
109
109
|
if (Array.isArray(data)) {
|
|
110
110
|
// Check array items
|
|
111
|
-
let result = data.map(item => checkRestricted(item, user, config, processedObjects));
|
|
111
|
+
let result = data.map((item) => checkRestricted(item, user, config, processedObjects));
|
|
112
112
|
if (!config.throwError && config.removeUndefinedFromResultArray) {
|
|
113
|
-
result = result.filter(item => item !== undefined);
|
|
113
|
+
result = result.filter((item) => item !== undefined);
|
|
114
114
|
}
|
|
115
115
|
return result;
|
|
116
116
|
}
|
|
@@ -134,8 +134,8 @@ export const checkRestricted = (
|
|
|
134
134
|
if (typeof item === 'string') {
|
|
135
135
|
roles.push(item);
|
|
136
136
|
} else if (
|
|
137
|
-
item?.roles?.length
|
|
138
|
-
|
|
137
|
+
item?.roles?.length &&
|
|
138
|
+
(config.processType && item.processType ? config.processType === item.processType : true)
|
|
139
139
|
) {
|
|
140
140
|
if (Array.isArray(item.roles)) {
|
|
141
141
|
roles.push(...item.roles);
|
|
@@ -156,13 +156,14 @@ export const checkRestricted = (
|
|
|
156
156
|
|
|
157
157
|
// Check access rights
|
|
158
158
|
if (
|
|
159
|
-
roles.includes(RoleEnum.S_EVERYONE)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
159
|
+
roles.includes(RoleEnum.S_EVERYONE) ||
|
|
160
|
+
user?.hasRole?.(roles) ||
|
|
161
|
+
(user?.id && roles.includes(RoleEnum.S_USER)) ||
|
|
162
|
+
(roles.includes(RoleEnum.S_SELF) && equalIds(data, user)) ||
|
|
163
|
+
(roles.includes(RoleEnum.S_CREATOR) &&
|
|
164
|
+
(('createdBy' in data && equalIds(data.createdBy, user)) ||
|
|
165
|
+
(config.allowCreatorOfParent && !('createdBy' in data) && config.isCreatorOfParent))) ||
|
|
166
|
+
(roles.includes(RoleEnum.S_VERIFIED) && (user?.verified || user?.verifiedAt))
|
|
166
167
|
) {
|
|
167
168
|
valid = true;
|
|
168
169
|
}
|
|
@@ -172,11 +173,11 @@ export const checkRestricted = (
|
|
|
172
173
|
// Get groups
|
|
173
174
|
const groups = restricted.filter((item) => {
|
|
174
175
|
return (
|
|
175
|
-
typeof item === 'object'
|
|
176
|
+
typeof item === 'object' &&
|
|
176
177
|
// Check if object is valid
|
|
177
|
-
|
|
178
|
+
item.memberOf?.length &&
|
|
178
179
|
// Check if processType is specified and is valid for current process
|
|
179
|
-
|
|
180
|
+
(config.processType && item.processType ? config.processType === item.processType : true)
|
|
180
181
|
);
|
|
181
182
|
}) as { memberOf: string | string[] }[];
|
|
182
183
|
|
|
@@ -252,8 +253,8 @@ export const checkRestricted = (
|
|
|
252
253
|
// Check rights
|
|
253
254
|
if (valid) {
|
|
254
255
|
// Check if data is user or user is creator of data (for nested plain objects)
|
|
255
|
-
config.isCreatorOfParent
|
|
256
|
-
|
|
256
|
+
config.isCreatorOfParent =
|
|
257
|
+
equalIds(data, user) || ('createdBy' in data ? equalIds(data.createdBy, user) : config.isCreatorOfParent);
|
|
257
258
|
|
|
258
259
|
// Check deep
|
|
259
260
|
data[propertyKey] = checkRestricted(data[propertyKey], user, config, processedObjects);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable perfectionist/sort-enums */
|
|
1
2
|
/**
|
|
2
3
|
* Enums for Resolver @Role and Model @Restricted decorator and for roles property in ServiceOptions
|
|
3
4
|
*
|
|
@@ -42,9 +43,12 @@ export enum RoleEnum {
|
|
|
42
43
|
// Everyone, including users who are not logged in, can access (see context user, e.g. @CurrentUser)
|
|
43
44
|
S_EVERYONE = 's_everyone',
|
|
44
45
|
|
|
45
|
-
// No one has access, not even administrators
|
|
46
|
+
// No one has access, not even administrators (regardless of which roles are still set, access will always be denied)
|
|
46
47
|
S_NO_ONE = 's_no_one',
|
|
47
48
|
|
|
49
|
+
// User must be verified (see verified or verifiedAt property of user)
|
|
50
|
+
S_VERIFIED = 's_verified',
|
|
51
|
+
|
|
48
52
|
// ===================================================================================================================
|
|
49
53
|
// Special system roles that check rights for DB objects and can be used via @Restricted for Models
|
|
50
54
|
// (classes and properties) and via ServiceOptions for Resolver methods. These roles should not be integrated in
|
|
@@ -209,7 +209,7 @@ export function assignPlain(target: Record<any, any>, ...args: Record<any, any>[
|
|
|
209
209
|
*/
|
|
210
210
|
export async function check(
|
|
211
211
|
value: any,
|
|
212
|
-
user: { hasRole: (roles: string[]) => boolean; id: any },
|
|
212
|
+
user: { hasRole: (roles: string[]) => boolean; id: any; verified?: any; verifiedAt?: any },
|
|
213
213
|
options?: {
|
|
214
214
|
allowCreatorOfParent?: boolean;
|
|
215
215
|
dbObject?: any;
|
|
@@ -262,7 +262,9 @@ export async function check(
|
|
|
262
262
|
(config.allowCreatorOfParent &&
|
|
263
263
|
config.dbObject &&
|
|
264
264
|
!('createdBy' in config.dbObject) &&
|
|
265
|
-
config.isCreatorOfParent)))
|
|
265
|
+
config.isCreatorOfParent))) ||
|
|
266
|
+
// check if the is verified
|
|
267
|
+
(roles.includes(RoleEnum.S_VERIFIED) && (user?.verified || user?.verifiedAt))
|
|
266
268
|
) {
|
|
267
269
|
valid = true;
|
|
268
270
|
}
|
|
@@ -91,7 +91,7 @@ async function listMigrations(options: CliOptions) {
|
|
|
91
91
|
const stateStore = loadStateStore(options.store);
|
|
92
92
|
|
|
93
93
|
const runner = new MigrationRunner({
|
|
94
|
-
migrationsDirectory: options.migrationsDir,
|
|
94
|
+
migrationsDirectory: path.resolve(process.cwd(), options.migrationsDir),
|
|
95
95
|
stateStore,
|
|
96
96
|
});
|
|
97
97
|
|
|
@@ -255,7 +255,7 @@ async function runDown(options: CliOptions) {
|
|
|
255
255
|
const stateStore = loadStateStore(options.store);
|
|
256
256
|
|
|
257
257
|
const runner = new MigrationRunner({
|
|
258
|
-
migrationsDirectory: options.migrationsDir,
|
|
258
|
+
migrationsDirectory: path.resolve(process.cwd(), options.migrationsDir),
|
|
259
259
|
stateStore,
|
|
260
260
|
});
|
|
261
261
|
|
|
@@ -270,7 +270,7 @@ async function runUp(options: CliOptions) {
|
|
|
270
270
|
const stateStore = loadStateStore(options.store);
|
|
271
271
|
|
|
272
272
|
const runner = new MigrationRunner({
|
|
273
|
-
migrationsDirectory: options.migrationsDir,
|
|
273
|
+
migrationsDirectory: path.resolve(process.cwd(), options.migrationsDir),
|
|
274
274
|
stateStore,
|
|
275
275
|
});
|
|
276
276
|
|
|
@@ -6,20 +6,64 @@ import * as path from 'path';
|
|
|
6
6
|
* Migration helper functions for database operations
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
// Store active connections for auto-cleanup
|
|
10
|
+
const activeConnections = new Set<MongoClient>();
|
|
11
|
+
|
|
12
|
+
// Track if we're in a migration context
|
|
13
|
+
let inMigrationContext = false;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Mark the start of a migration
|
|
17
|
+
* @internal Used by migration runner
|
|
18
|
+
*/
|
|
19
|
+
export const _startMigration = () => {
|
|
20
|
+
inMigrationContext = true;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Mark the end of a migration and close all connections
|
|
25
|
+
* @internal Used by migration runner
|
|
26
|
+
*/
|
|
27
|
+
export const _endMigration = async () => {
|
|
28
|
+
inMigrationContext = false;
|
|
29
|
+
// Close all active connections
|
|
30
|
+
const promises = Array.from(activeConnections).map((client) => client.close());
|
|
31
|
+
activeConnections.clear();
|
|
32
|
+
await Promise.all(promises);
|
|
33
|
+
};
|
|
34
|
+
|
|
9
35
|
/**
|
|
10
36
|
* Get database connection
|
|
11
37
|
*
|
|
38
|
+
* When used in migrations, connections are automatically closed after the migration completes.
|
|
39
|
+
* For manual usage outside migrations, you must close the connection manually.
|
|
40
|
+
*
|
|
12
41
|
* @param mongoUrl - MongoDB connection URI
|
|
13
42
|
* @returns Promise with database instance
|
|
14
43
|
*
|
|
15
44
|
* @example
|
|
16
45
|
* ```typescript
|
|
46
|
+
* // In migrations - connection auto-closes after migration
|
|
17
47
|
* const db = await getDb('mongodb://localhost/mydb');
|
|
18
48
|
* await db.collection('users').updateMany(...);
|
|
49
|
+
*
|
|
50
|
+
* // Outside migrations - must close manually
|
|
51
|
+
* const db = await getDb('mongodb://localhost/mydb');
|
|
52
|
+
* try {
|
|
53
|
+
* await db.collection('users').updateMany(...);
|
|
54
|
+
* } finally {
|
|
55
|
+
* await db.client.close();
|
|
56
|
+
* }
|
|
19
57
|
* ```
|
|
20
58
|
*/
|
|
21
59
|
export const getDb = async (mongoUrl: string): Promise<Db> => {
|
|
22
60
|
const client: MongoClient = await MongoClient.connect(mongoUrl);
|
|
61
|
+
|
|
62
|
+
// Track connection for auto-cleanup in migrations
|
|
63
|
+
if (inMigrationContext) {
|
|
64
|
+
activeConnections.add(client);
|
|
65
|
+
}
|
|
66
|
+
|
|
23
67
|
return client.db();
|
|
24
68
|
};
|
|
25
69
|
|
|
@@ -106,6 +106,8 @@ export class MigrationRunner {
|
|
|
106
106
|
* Run all pending migrations (up)
|
|
107
107
|
*/
|
|
108
108
|
async up(): Promise<void> {
|
|
109
|
+
const { _endMigration, _startMigration } = await import('./helpers/migration.helper');
|
|
110
|
+
|
|
109
111
|
const allMigrations = await this.loadMigrationFiles();
|
|
110
112
|
const state = await this.options.stateStore.loadAsync();
|
|
111
113
|
const completedMigrations = (state.migrations || []).map((m) => m.title);
|
|
@@ -121,23 +123,32 @@ export class MigrationRunner {
|
|
|
121
123
|
|
|
122
124
|
for (const migration of pendingMigrations) {
|
|
123
125
|
console.log(`Running migration: ${migration.title}`);
|
|
124
|
-
await migration.up();
|
|
125
|
-
|
|
126
|
-
// Update state
|
|
127
|
-
const newState = await this.options.stateStore.loadAsync();
|
|
128
|
-
const migrations = newState.migrations || [];
|
|
129
|
-
migrations.push({
|
|
130
|
-
timestamp: migration.timestamp,
|
|
131
|
-
title: migration.title,
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
await this.options.stateStore.saveAsync({
|
|
135
|
-
lastRun: migration.title,
|
|
136
|
-
migrations,
|
|
137
|
-
up: () => {},
|
|
138
|
-
} as any);
|
|
139
126
|
|
|
140
|
-
|
|
127
|
+
// Mark start of migration for auto-cleanup
|
|
128
|
+
_startMigration();
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await migration.up();
|
|
132
|
+
|
|
133
|
+
// Update state
|
|
134
|
+
const newState = await this.options.stateStore.loadAsync();
|
|
135
|
+
const migrations = newState.migrations || [];
|
|
136
|
+
migrations.push({
|
|
137
|
+
timestamp: migration.timestamp,
|
|
138
|
+
title: migration.title,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await this.options.stateStore.saveAsync({
|
|
142
|
+
lastRun: migration.title,
|
|
143
|
+
migrations,
|
|
144
|
+
up: () => {},
|
|
145
|
+
} as any);
|
|
146
|
+
|
|
147
|
+
console.log(`✓ Migration completed: ${migration.title}`);
|
|
148
|
+
} finally {
|
|
149
|
+
// Always close connections, even on error
|
|
150
|
+
await _endMigration();
|
|
151
|
+
}
|
|
141
152
|
}
|
|
142
153
|
|
|
143
154
|
console.log('All migrations completed successfully');
|
|
@@ -147,6 +158,8 @@ export class MigrationRunner {
|
|
|
147
158
|
* Rollback the last migration (down)
|
|
148
159
|
*/
|
|
149
160
|
async down(): Promise<void> {
|
|
161
|
+
const { _endMigration, _startMigration } = await import('./helpers/migration.helper');
|
|
162
|
+
|
|
150
163
|
const state = await this.options.stateStore.loadAsync();
|
|
151
164
|
const completedMigrations = state.migrations || [];
|
|
152
165
|
|
|
@@ -168,17 +181,26 @@ export class MigrationRunner {
|
|
|
168
181
|
}
|
|
169
182
|
|
|
170
183
|
console.log(`Rolling back migration: ${migrationToRollback.title}`);
|
|
171
|
-
await migrationToRollback.down();
|
|
172
184
|
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
up: () => {},
|
|
179
|
-
} as any);
|
|
185
|
+
// Mark start of migration for auto-cleanup
|
|
186
|
+
_startMigration();
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
await migrationToRollback.down();
|
|
180
190
|
|
|
181
|
-
|
|
191
|
+
// Update state
|
|
192
|
+
const newMigrations = completedMigrations.slice(0, -1);
|
|
193
|
+
await this.options.stateStore.saveAsync({
|
|
194
|
+
lastRun: newMigrations.length > 0 ? newMigrations[newMigrations.length - 1].title : undefined,
|
|
195
|
+
migrations: newMigrations,
|
|
196
|
+
up: () => {},
|
|
197
|
+
} as any);
|
|
198
|
+
|
|
199
|
+
console.log(`✓ Migration rolled back: ${migrationToRollback.title}`);
|
|
200
|
+
} finally {
|
|
201
|
+
// Always close connections, even on error
|
|
202
|
+
await _endMigration();
|
|
203
|
+
}
|
|
182
204
|
}
|
|
183
205
|
|
|
184
206
|
/**
|