@lenne.tech/nest-server 11.10.6 → 11.11.1
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/models/pagination-info.model.d.ts +15 -0
- package/dist/core/common/models/pagination-info.model.js +84 -0
- package/dist/core/common/models/pagination-info.model.js.map +1 -0
- package/dist/core/common/services/crud.service.d.ts +4 -0
- package/dist/core/common/services/crud.service.js +9 -0
- package/dist/core/common/services/crud.service.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +4 -1
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/server/modules/user/outputs/find-and-count-users-result.output.d.ts +2 -0
- package/dist/server/modules/user/outputs/find-and-count-users-result.output.js +9 -0
- package/dist/server/modules/user/outputs/find-and-count-users-result.output.js.map +1 -1
- package/dist/server/modules/user/user.resolver.d.ts +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/common/models/pagination-info.model.ts +101 -0
- package/src/core/common/services/crud.service.ts +19 -3
- package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +5 -2
- package/src/index.ts +1 -0
- package/src/server/modules/user/outputs/find-and-count-users-result.output.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.11.1",
|
|
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",
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ObjectType } from '@nestjs/graphql';
|
|
2
|
+
|
|
3
|
+
import { UnifiedField } from '../decorators/unified-field.decorator';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pagination information for paginated queries
|
|
7
|
+
*
|
|
8
|
+
* Provides metadata about the current page, total pages, and navigation flags
|
|
9
|
+
* to facilitate frontend pagination UI implementation.
|
|
10
|
+
*/
|
|
11
|
+
@ObjectType({ description: 'Pagination information for paginated queries' })
|
|
12
|
+
export class PaginationInfo {
|
|
13
|
+
/**
|
|
14
|
+
* Total number of items across all pages
|
|
15
|
+
*/
|
|
16
|
+
@UnifiedField({
|
|
17
|
+
description: 'Total number of items across all pages',
|
|
18
|
+
})
|
|
19
|
+
totalCount: number;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Total number of pages
|
|
23
|
+
*/
|
|
24
|
+
@UnifiedField({
|
|
25
|
+
description: 'Total number of pages',
|
|
26
|
+
})
|
|
27
|
+
pageCount: number;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Current page number (1-based)
|
|
31
|
+
*/
|
|
32
|
+
@UnifiedField({
|
|
33
|
+
description: 'Current page number (1-based)',
|
|
34
|
+
})
|
|
35
|
+
currentPage: number;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Number of items per page
|
|
39
|
+
*/
|
|
40
|
+
@UnifiedField({
|
|
41
|
+
description: 'Number of items per page',
|
|
42
|
+
})
|
|
43
|
+
perPage: number;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Indicates if there is a next page
|
|
47
|
+
*/
|
|
48
|
+
@UnifiedField({
|
|
49
|
+
description: 'Indicates if there is a next page',
|
|
50
|
+
})
|
|
51
|
+
hasNextPage: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Indicates if there is a previous page
|
|
55
|
+
*/
|
|
56
|
+
@UnifiedField({
|
|
57
|
+
description: 'Indicates if there is a previous page',
|
|
58
|
+
})
|
|
59
|
+
hasPreviousPage: boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create PaginationInfo from query parameters and total count
|
|
63
|
+
*/
|
|
64
|
+
static create(options: {
|
|
65
|
+
limit?: number;
|
|
66
|
+
offset?: number;
|
|
67
|
+
skip?: number;
|
|
68
|
+
take?: number;
|
|
69
|
+
totalCount: number;
|
|
70
|
+
}): PaginationInfo {
|
|
71
|
+
const { totalCount } = options;
|
|
72
|
+
const skip = options.skip ?? options.offset ?? 0;
|
|
73
|
+
const perPage = options.take ?? options.limit ?? 0;
|
|
74
|
+
|
|
75
|
+
// Handle edge case: no limit specified (return all items as single page)
|
|
76
|
+
if (perPage === 0) {
|
|
77
|
+
return {
|
|
78
|
+
currentPage: totalCount > 0 ? 1 : 0,
|
|
79
|
+
hasNextPage: false,
|
|
80
|
+
hasPreviousPage: false,
|
|
81
|
+
pageCount: totalCount > 0 ? 1 : 0,
|
|
82
|
+
perPage: 0,
|
|
83
|
+
totalCount,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const pageCount = Math.ceil(totalCount / perPage);
|
|
88
|
+
const currentPage = totalCount === 0 ? 0 : Math.floor(skip / perPage) + 1;
|
|
89
|
+
const hasNextPage = currentPage < pageCount;
|
|
90
|
+
const hasPreviousPage = currentPage > 1;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
currentPage,
|
|
94
|
+
hasNextPage,
|
|
95
|
+
hasPreviousPage,
|
|
96
|
+
pageCount,
|
|
97
|
+
perPage,
|
|
98
|
+
totalCount,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -15,6 +15,7 @@ import { convertFilterArgsToQuery } from '../helpers/filter.helper';
|
|
|
15
15
|
import { mergePlain, prepareServiceOptionsForCreate } from '../helpers/input.helper';
|
|
16
16
|
import { ServiceOptions } from '../interfaces/service-options.interface';
|
|
17
17
|
import { CoreModel } from '../models/core-model.model';
|
|
18
|
+
import { PaginationInfo } from '../models/pagination-info.model';
|
|
18
19
|
import { PlainObject } from '../types/plain-object.type';
|
|
19
20
|
import { ConfigService } from './config.service';
|
|
20
21
|
import { ModuleService } from './module.service';
|
|
@@ -218,7 +219,11 @@ export abstract class CrudService<
|
|
|
218
219
|
async findAndCount(
|
|
219
220
|
filter?: FilterArgs | { filterQuery?: QueryFilter<any>; queryOptions?: QueryOptions; samples?: number },
|
|
220
221
|
serviceOptions?: ServiceOptions,
|
|
221
|
-
): Promise<{
|
|
222
|
+
): Promise<{
|
|
223
|
+
items: Model[];
|
|
224
|
+
pagination: PaginationInfo;
|
|
225
|
+
totalCount: number;
|
|
226
|
+
}> {
|
|
222
227
|
// If filter is not instance of FilterArgs a simple form with filterQuery and queryOptions is set
|
|
223
228
|
// and should not be processed as FilterArgs
|
|
224
229
|
if (!(filter instanceof FilterArgs) && serviceOptions?.inputType === FilterArgs) {
|
|
@@ -284,6 +289,17 @@ export abstract class CrudService<
|
|
|
284
289
|
(await this.mainDbModel.aggregate(aggregation, collation ? { collation } : {}).exec())[0] || {};
|
|
285
290
|
dbResult.totalCount = dbResult.totalCount?.[0]?.total || 0;
|
|
286
291
|
dbResult.items = dbResult.items?.map((item) => this.mainDbModel.hydrate(item)) || [];
|
|
292
|
+
|
|
293
|
+
// Calculate pagination info
|
|
294
|
+
const queryOptions = filterQuery.queryOptions || {};
|
|
295
|
+
dbResult.pagination = PaginationInfo.create({
|
|
296
|
+
limit: queryOptions.limit,
|
|
297
|
+
offset: queryOptions.offset,
|
|
298
|
+
skip: queryOptions.skip,
|
|
299
|
+
take: queryOptions.take,
|
|
300
|
+
totalCount: dbResult.totalCount,
|
|
301
|
+
});
|
|
302
|
+
|
|
287
303
|
return dbResult;
|
|
288
304
|
},
|
|
289
305
|
{ input: filter, outputPath: 'items', serviceOptions },
|
|
@@ -297,7 +313,7 @@ export abstract class CrudService<
|
|
|
297
313
|
async findAndCountForce(
|
|
298
314
|
filter?: FilterArgs | { filterQuery?: QueryFilter<any>; queryOptions?: QueryOptions; samples?: number },
|
|
299
315
|
serviceOptions: ServiceOptions = {},
|
|
300
|
-
): Promise<{ items: Model[]; totalCount: number }> {
|
|
316
|
+
): Promise<{ items: Model[]; pagination: PaginationInfo; totalCount: number; }> {
|
|
301
317
|
serviceOptions.raw = true;
|
|
302
318
|
return this.findAndCount(filter, serviceOptions);
|
|
303
319
|
}
|
|
@@ -309,7 +325,7 @@ export abstract class CrudService<
|
|
|
309
325
|
async findAndCountRaw(
|
|
310
326
|
filter?: FilterArgs | { filterQuery?: QueryFilter<any>; queryOptions?: QueryOptions; samples?: number },
|
|
311
327
|
serviceOptions: ServiceOptions = {},
|
|
312
|
-
): Promise<{ items: Model[]; totalCount: number }> {
|
|
328
|
+
): Promise<{ items: Model[]; pagination: PaginationInfo; totalCount: number; }> {
|
|
313
329
|
serviceOptions = serviceOptions || {};
|
|
314
330
|
serviceOptions.raw = true;
|
|
315
331
|
return this.findAndCountForce(filter, serviceOptions);
|
|
@@ -244,9 +244,12 @@ export class CoreBetterAuthApiMiddleware implements NestMiddleware {
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
// Clean up the used challenge mapping after verification
|
|
248
|
-
|
|
247
|
+
// Clean up the used challenge mapping only after SUCCESSFUL verification
|
|
248
|
+
// On failure, keep the challenge so the user can retry with a different passkey
|
|
249
|
+
if (challengeIdToDelete && this.challengeService && response.ok) {
|
|
249
250
|
await this.challengeService.deleteChallengeMapping(challengeIdToDelete);
|
|
251
|
+
} else if (challengeIdToDelete && !response.ok) {
|
|
252
|
+
this.logger.debug(`Keeping challenge mapping after failed verification (status=${response.status}) for retry`);
|
|
250
253
|
}
|
|
251
254
|
|
|
252
255
|
// Convert Web Standard Response to Express response using shared helper
|
package/src/index.ts
CHANGED
|
@@ -62,6 +62,7 @@ export * from './core/common/interfaces/service-options.interface';
|
|
|
62
62
|
export * from './core/common/middlewares/to-lower-case.middleware';
|
|
63
63
|
export * from './core/common/models/core-model.model';
|
|
64
64
|
export * from './core/common/models/core-persistence.model';
|
|
65
|
+
export * from './core/common/models/pagination-info.model';
|
|
65
66
|
export * from './core/common/pipes/check-input.pipe';
|
|
66
67
|
export * from './core/common/pipes/map-and-validate.pipe';
|
|
67
68
|
export * from './core/common/plugins/complexity.plugin';
|
|
@@ -3,6 +3,7 @@ import { ObjectType } from '@nestjs/graphql';
|
|
|
3
3
|
import { Restricted } from '../../../../core/common/decorators/restricted.decorator';
|
|
4
4
|
import { UnifiedField } from '../../../../core/common/decorators/unified-field.decorator';
|
|
5
5
|
import { RoleEnum } from '../../../../core/common/enums/role.enum';
|
|
6
|
+
import { PaginationInfo } from '../../../../core/common/models/pagination-info.model';
|
|
6
7
|
import { User } from '../user.model';
|
|
7
8
|
|
|
8
9
|
@ObjectType({ description: 'Result of find and count' })
|
|
@@ -21,4 +22,11 @@ export class FindAndCountUsersResult {
|
|
|
21
22
|
isOptional: false,
|
|
22
23
|
})
|
|
23
24
|
totalCount: number;
|
|
25
|
+
|
|
26
|
+
@UnifiedField({
|
|
27
|
+
description: 'Pagination information',
|
|
28
|
+
isOptional: true,
|
|
29
|
+
type: () => PaginationInfo,
|
|
30
|
+
})
|
|
31
|
+
pagination?: PaginationInfo;
|
|
24
32
|
}
|