xcally-nest-library 0.0.36 → 0.0.37-XM3778-bae91
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/.jest/jestEnv.js +3 -0
- package/.nvmrc +1 -1
- package/.prettierrc +18 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/jest.config.d.ts +3 -0
- package/dist/jest.config.js +61 -0
- package/dist/jest.config.js.map +1 -0
- package/dist/src/core/application/__tests__/cached-base.service.spec.d.ts +1 -0
- package/dist/src/core/application/__tests__/cached-base.service.spec.js +224 -0
- package/dist/src/core/application/__tests__/cached-base.service.spec.js.map +1 -0
- package/dist/src/core/application/__tests__/mock-types.d.ts +66 -0
- package/dist/src/core/application/__tests__/mock-types.js +9 -0
- package/dist/src/core/application/__tests__/mock-types.js.map +1 -0
- package/dist/src/core/application/__tests__/mocks/paginated-query.mock.d.ts +2 -0
- package/dist/src/core/application/__tests__/mocks/paginated-query.mock.js +12 -0
- package/dist/src/core/application/__tests__/mocks/paginated-query.mock.js.map +1 -0
- package/dist/src/core/application/__tests__/mocks/paginated-result.mock.d.ts +31 -0
- package/dist/src/core/application/__tests__/mocks/paginated-result.mock.js +169 -0
- package/dist/src/core/application/__tests__/mocks/paginated-result.mock.js.map +1 -0
- package/dist/src/core/application/base.interface.d.ts +4 -4
- package/dist/src/core/application/base.mongo.interface.d.ts +16 -0
- package/dist/src/core/application/base.mongo.interface.js +3 -0
- package/dist/src/core/application/base.mongo.interface.js.map +1 -0
- package/dist/src/core/application/base.mongo.service.d.ts +23 -0
- package/dist/src/core/application/base.mongo.service.js +91 -0
- package/dist/src/core/application/base.mongo.service.js.map +1 -0
- package/dist/src/core/application/base.service.d.ts +2 -2
- package/dist/src/core/application/cached-base.service.d.ts +26 -0
- package/dist/src/core/application/cached-base.service.js +52 -0
- package/dist/src/core/application/cached-base.service.js.map +1 -0
- package/dist/src/core/application/cached.service.d.ts +13 -0
- package/dist/src/core/application/cached.service.js +55 -0
- package/dist/src/core/application/cached.service.js.map +1 -0
- package/dist/src/core/domain/interfaces/base.mongo.repository.interface.d.ts +12 -0
- package/dist/src/core/domain/interfaces/base.mongo.repository.interface.js +3 -0
- package/dist/src/core/domain/interfaces/base.mongo.repository.interface.js.map +1 -0
- package/dist/src/core/domain/interfaces/cache-configuration.interface.d.ts +8 -0
- package/dist/src/core/domain/interfaces/cache-configuration.interface.js +11 -0
- package/dist/src/core/domain/interfaces/cache-configuration.interface.js.map +1 -0
- package/dist/src/core/infrastracture/databases/base.mongo.entity.d.ts +14 -0
- package/dist/src/core/infrastracture/databases/base.mongo.entity.interface.d.ts +10 -0
- package/dist/src/core/infrastracture/databases/base.mongo.entity.interface.js +3 -0
- package/dist/src/core/infrastracture/databases/base.mongo.entity.interface.js.map +1 -0
- package/dist/src/core/infrastracture/databases/base.mongo.entity.js +66 -0
- package/dist/src/core/infrastracture/databases/base.mongo.entity.js.map +1 -0
- package/dist/src/core/infrastracture/databases/base.mongo.repository.d.ts +20 -0
- package/dist/src/core/infrastracture/databases/base.mongo.repository.js +275 -0
- package/dist/src/core/infrastracture/databases/base.mongo.repository.js.map +1 -0
- package/dist/src/core/infrastracture/databases/base.repository.d.ts +1 -1
- package/dist/src/modules/logger/pino/logger.module.js +1 -1
- package/dist/src/modules/logger/pino/logger.module.js.map +1 -1
- package/dist/src/modules/logger/pino/logger.service.js +4 -4
- package/dist/src/modules/logger/pino/logger.service.js.map +1 -1
- package/dist/tsconfig.json +38 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/eslint.config.mjs +49 -0
- package/index.ts +9 -0
- package/jest.config.ts +219 -0
- package/package.json +18 -6
- package/scripts/publish-feat.sh +67 -0
- package/src/core/application/__tests__/cached-base.service.spec.ts +367 -0
- package/src/core/application/__tests__/mock-types.ts +73 -0
- package/src/core/application/__tests__/mocks/paginated-query.mock.ts +10 -0
- package/src/core/application/__tests__/mocks/paginated-result.mock.ts +166 -0
- package/src/core/application/base.interface.ts +4 -5
- package/src/core/application/base.mongo.interface.ts +15 -0
- package/src/core/application/base.mongo.service.ts +170 -0
- package/src/core/application/base.service.ts +2 -2
- package/src/core/application/cached-base.service.ts +119 -0
- package/src/core/application/cached.service.ts +96 -0
- package/src/core/domain/interfaces/base.mongo.repository.interface.ts +14 -0
- package/src/core/domain/interfaces/base.repository.interface.ts +1 -0
- package/src/core/domain/interfaces/cache-configuration.interface.ts +10 -0
- package/src/core/infrastracture/databases/base.mongo.entity.interface.ts +12 -0
- package/src/core/infrastracture/databases/base.mongo.entity.ts +65 -0
- package/src/core/infrastracture/databases/base.mongo.repository.ts +371 -0
- package/src/core/infrastracture/databases/base.repository.ts +1 -1
- package/src/modules/logger/pino/logger.module.ts +1 -1
- package/src/modules/logger/pino/logger.service.ts +4 -4
- package/tsconfig.json +1 -0
- package/dist/src/modules/db-hooks-subscriber/db-hooks-subscriber.module.d.ts +0 -2
- package/dist/src/modules/db-hooks-subscriber/db-hooks-subscriber.module.js +0 -22
- package/dist/src/modules/db-hooks-subscriber/db-hooks-subscriber.module.js.map +0 -1
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { IBaseMongoRepository } from '@/core/domain/interfaces/base.mongo.repository.interface';
|
|
2
|
+
import { ObjectId } from 'mongodb';
|
|
3
|
+
import { PaginateQuery, Paginated } from 'nestjs-paginate';
|
|
4
|
+
import { EntityManager, MongoRepository, ObjectLiteral } from 'typeorm';
|
|
5
|
+
import { ExtendedPaginateConfig } from './base.mongo.entity.interface';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Represents a generic MongoDB repository for managing entities.
|
|
9
|
+
*/
|
|
10
|
+
export class BaseMongoRepository<E extends ObjectLiteral>
|
|
11
|
+
extends MongoRepository<E>
|
|
12
|
+
implements IBaseMongoRepository<E>
|
|
13
|
+
{
|
|
14
|
+
public paginateConfig: ExtendedPaginateConfig<E>;
|
|
15
|
+
|
|
16
|
+
constructor(manager: EntityManager, entityCls: new () => E, paginateConfig?: ExtendedPaginateConfig<E>) {
|
|
17
|
+
super(entityCls, manager);
|
|
18
|
+
this.paginateConfig = paginateConfig || {
|
|
19
|
+
sortableColumns: [],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Retrieves a paginated list of entities based on the provided query.
|
|
25
|
+
*
|
|
26
|
+
* @param query - The pagination query parameters.
|
|
27
|
+
* @returns A promise that resolves to a paginated list of entities.
|
|
28
|
+
*/
|
|
29
|
+
async getMany(query: PaginateQuery): Promise<Paginated<E>> {
|
|
30
|
+
//this implementation of pagination is considering skip and limit and not cursors
|
|
31
|
+
|
|
32
|
+
const page = query.page ?? 1;
|
|
33
|
+
|
|
34
|
+
//get the limit (combining config limits and query limits)
|
|
35
|
+
const defaultLimit = this.paginateConfig.defaultLimit ?? 20;
|
|
36
|
+
const maxLimit = this.paginateConfig.maxLimit ?? 100;
|
|
37
|
+
const inputLimit = query.limit ?? defaultLimit;
|
|
38
|
+
const limit = Math.min(inputLimit, maxLimit);
|
|
39
|
+
|
|
40
|
+
const skip = (page - 1) * limit;
|
|
41
|
+
|
|
42
|
+
// building complex filters (where clause)
|
|
43
|
+
const filter = this.buildMongoFilter(query, this.paginateConfig);
|
|
44
|
+
|
|
45
|
+
// building sort clause (multiple fields)
|
|
46
|
+
const sort = this.buildMongoSort(query);
|
|
47
|
+
|
|
48
|
+
// selecting fields (projection)
|
|
49
|
+
const selectCondition = this.buildMongoSelect(query, this.paginateConfig);
|
|
50
|
+
|
|
51
|
+
// fetching data and total count
|
|
52
|
+
const [data, totalItems] = await this.findAndCount({
|
|
53
|
+
where: filter as any,
|
|
54
|
+
order: sort as any,
|
|
55
|
+
skip,
|
|
56
|
+
take: limit,
|
|
57
|
+
select: selectCondition,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const totalPages = Math.ceil(totalItems / limit);
|
|
61
|
+
|
|
62
|
+
const meta = {
|
|
63
|
+
itemsPerPage: limit,
|
|
64
|
+
totalItems,
|
|
65
|
+
currentPage: page,
|
|
66
|
+
totalPages,
|
|
67
|
+
// cast these to any to avoid the extremely strict SortBy/E typings from nestjs-paginate
|
|
68
|
+
sortBy: (query.sortBy ?? []) as unknown as any,
|
|
69
|
+
searchBy: (query.searchBy ?? []) as unknown as any,
|
|
70
|
+
search: query.search ?? '',
|
|
71
|
+
select: selectCondition ?? [],
|
|
72
|
+
filter: query.filter ?? {},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const links = {
|
|
76
|
+
current: query.path ? `${query.path}?page=${page}&limit=${limit}` : '',
|
|
77
|
+
first: query.path ? `${query.path}?page=1&limit=${limit}` : undefined,
|
|
78
|
+
previous: page > 1 && query.path ? `${query.path}?page=${page - 1}&limit=${limit}` : undefined,
|
|
79
|
+
next: page < totalPages && query.path ? `${query.path}?page=${page + 1}&limit=${limit}` : undefined,
|
|
80
|
+
last: query.path ? `${query.path}?page=${totalPages}&limit=${limit}` : undefined,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const plainData = data.map(d => JSON.parse(JSON.stringify(d)));
|
|
84
|
+
|
|
85
|
+
// final cast: runtime structure matches Paginated<E>, cast to satisfy TS
|
|
86
|
+
return {
|
|
87
|
+
data: plainData,
|
|
88
|
+
meta: meta as unknown as Paginated<E>['meta'],
|
|
89
|
+
links: links as any,
|
|
90
|
+
} as Paginated<E>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Retrieves an entity from the database based on its ID.
|
|
95
|
+
*
|
|
96
|
+
* @param id - The ID of the entity to retrieve (string or ObjectId).
|
|
97
|
+
* @returns A promise that resolves to the retrieved entity.
|
|
98
|
+
*/
|
|
99
|
+
async findById(id: string | ObjectId): Promise<E> {
|
|
100
|
+
let objectId: ObjectId;
|
|
101
|
+
|
|
102
|
+
if (typeof id === 'string') {
|
|
103
|
+
if (!ObjectId.isValid(id)) {
|
|
104
|
+
throw new Error(`Invalid ObjectId string: ${id}`);
|
|
105
|
+
}
|
|
106
|
+
objectId = new ObjectId(id);
|
|
107
|
+
} else if (id instanceof ObjectId) {
|
|
108
|
+
objectId = id;
|
|
109
|
+
} else {
|
|
110
|
+
throw new Error(`Invalid id type: ${typeof id}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return this.findOne({
|
|
114
|
+
where: { _id: objectId },
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
protected buildMongoFilter(query: PaginateQuery, paginateConfig: ExtendedPaginateConfig<E>): Record<string, any> {
|
|
119
|
+
// apply filter whitelist from config
|
|
120
|
+
const { filter = {} } = query;
|
|
121
|
+
const filterConfig = paginateConfig.filterableColumns || {};
|
|
122
|
+
const allowedFilterColumns = new Set(Object.keys(filterConfig));
|
|
123
|
+
const dateCols = new Set(paginateConfig.dateColumns || []);
|
|
124
|
+
|
|
125
|
+
// conditions have positional order on $or $and operators
|
|
126
|
+
const orderedConditions: { field: string; ops: string[]; valuePart: string; isDate: boolean }[] = [];
|
|
127
|
+
|
|
128
|
+
// operator whitelist
|
|
129
|
+
for (const [field, rawValue] of Object.entries(filter)) {
|
|
130
|
+
if (!allowedFilterColumns.has(field)) {
|
|
131
|
+
console.warn(`Column filter not allowed: ${field}`);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// dates needs to be parsed correctly (transported as strings but stored as Date)
|
|
136
|
+
const isDate = dateCols.has(field);
|
|
137
|
+
|
|
138
|
+
// array values like e.g. $in ['val1','val2']
|
|
139
|
+
if (Array.isArray(rawValue)) {
|
|
140
|
+
for (const cond of rawValue) {
|
|
141
|
+
if (typeof cond !== 'string') continue;
|
|
142
|
+
const tokens = cond.split(':');
|
|
143
|
+
const ops = tokens.filter(t => t.startsWith('$'));
|
|
144
|
+
const valuePart = tokens.slice(tokens.findIndex(t => !t.startsWith('$'))).join(':');
|
|
145
|
+
orderedConditions.push({ field, ops, valuePart, isDate });
|
|
146
|
+
}
|
|
147
|
+
// every other case is a string
|
|
148
|
+
} else if (typeof rawValue === 'string') {
|
|
149
|
+
if (!rawValue.includes(':')) {
|
|
150
|
+
// simple single operator
|
|
151
|
+
orderedConditions.push({ field, ops: [], valuePart: rawValue, isDate });
|
|
152
|
+
} else {
|
|
153
|
+
// multiple operators in a single string value e.g. $not:$in:value
|
|
154
|
+
const tokens = rawValue.split(':');
|
|
155
|
+
const ops = tokens.filter(t => t.startsWith('$'));
|
|
156
|
+
const valuePart = tokens.slice(tokens.findIndex(t => !t.startsWith('$'))).join(':');
|
|
157
|
+
orderedConditions.push({ field, ops, valuePart, isDate });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// main AND group
|
|
163
|
+
const andGroup: any[] = [];
|
|
164
|
+
let lastCondition: any = null;
|
|
165
|
+
|
|
166
|
+
for (const { field, ops, valuePart, isDate } of orderedConditions) {
|
|
167
|
+
// remove the first logical operator if it is $or or $and and handle the others
|
|
168
|
+
const mainLogical = ops[0] === '$or' || ops[0] === '$and' ? ops[0] : null;
|
|
169
|
+
if (mainLogical) ops.shift();
|
|
170
|
+
|
|
171
|
+
// build logical condition for some special operators like $btw,$ilike...
|
|
172
|
+
const special = this.buildSpecialOperator(ops, valuePart, isDate);
|
|
173
|
+
let finalVal: any;
|
|
174
|
+
|
|
175
|
+
if (special) {
|
|
176
|
+
// handled by special operator builder
|
|
177
|
+
finalVal = special;
|
|
178
|
+
} else if (ops.length && ops[ops.length - 1] === '$in') {
|
|
179
|
+
// handle $in and $nin with comma separated values
|
|
180
|
+
const vals = valuePart.split(',').map(s => this.parsePrimitive(s.trim(), isDate));
|
|
181
|
+
finalVal = ops[0] === '$not' ? { $nin: vals } : { $in: vals };
|
|
182
|
+
} else {
|
|
183
|
+
// primitive type like $eq with string, number, boolean, dates..
|
|
184
|
+
finalVal = this.parsePrimitive(valuePart, isDate);
|
|
185
|
+
if (ops.length) {
|
|
186
|
+
const temp: Record<string, any> = {};
|
|
187
|
+
// apply remaining operators
|
|
188
|
+
this.applyOps(temp, ops, finalVal);
|
|
189
|
+
finalVal = temp;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const condition: Record<string, any> = { [field]: finalVal };
|
|
194
|
+
|
|
195
|
+
// $and, $or are applied backward to the previous condition positionally
|
|
196
|
+
if ((mainLogical === '$or' || mainLogical === '$and') && lastCondition) {
|
|
197
|
+
const prev = andGroup.pop();
|
|
198
|
+
const merged = { [mainLogical]: [prev, condition] };
|
|
199
|
+
andGroup.push(merged);
|
|
200
|
+
lastCondition = merged;
|
|
201
|
+
} else {
|
|
202
|
+
// otherwise is a simple push to the main AND group
|
|
203
|
+
andGroup.push(condition);
|
|
204
|
+
lastCondition = condition;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// final filtering conditions
|
|
209
|
+
const finalFilter = andGroup.length === 1 ? andGroup[0] : { $and: andGroup };
|
|
210
|
+
|
|
211
|
+
//console.log('Final MongoDB filter:', JSON.stringify(finalFilter, null, 2));
|
|
212
|
+
return finalFilter;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// transform string field into real stored type (needed because reflection doesn't work with generics<E>)
|
|
216
|
+
private parsePrimitive(raw: string, isDateField: boolean): any {
|
|
217
|
+
if (isDateField) {
|
|
218
|
+
const d = new Date(raw);
|
|
219
|
+
if (!isNaN(d.getTime())) return d;
|
|
220
|
+
}
|
|
221
|
+
if (raw === 'true') return true;
|
|
222
|
+
if (raw === 'false') return false;
|
|
223
|
+
if (raw !== '' && !Number.isNaN(Number(raw)) && !/^0\d+/.test(raw)) return Number(raw);
|
|
224
|
+
return raw;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// helper to apply nested operators
|
|
228
|
+
private applyOps(targetObj: Record<string, any>, ops: string[], value: any): void {
|
|
229
|
+
let ref = targetObj;
|
|
230
|
+
for (let i = 0; i < ops.length; i++) {
|
|
231
|
+
const op = ops[i];
|
|
232
|
+
const isLast = i === ops.length - 1;
|
|
233
|
+
if (isLast) ref[op] = value;
|
|
234
|
+
else {
|
|
235
|
+
if (ref[op] === undefined) ref[op] = {};
|
|
236
|
+
ref = ref[op];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// builds special operators like $btw, $sw, $ilike, $null, $contains.. (with regex support)
|
|
242
|
+
private buildSpecialOperator(ops: string[], valuePart: string, isDate: boolean): Record<string, any> | null {
|
|
243
|
+
const isNegated = ops.includes('$not');
|
|
244
|
+
const cleanOps = ops.filter(o => o !== '$not');
|
|
245
|
+
const values = valuePart.split(',').map(v => this.parsePrimitive(v.trim(), isDate));
|
|
246
|
+
|
|
247
|
+
const asSafeRegexString = (val: any): string => {
|
|
248
|
+
if (val == null) return '';
|
|
249
|
+
const s = String(val);
|
|
250
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
switch (cleanOps[0]) {
|
|
254
|
+
case '$btw': {
|
|
255
|
+
const [from, to] = values;
|
|
256
|
+
const expr = { $gte: from, $lte: to };
|
|
257
|
+
return isNegated ? { $not: expr } : expr;
|
|
258
|
+
}
|
|
259
|
+
case '$sw': {
|
|
260
|
+
const regex = new RegExp(`^${asSafeRegexString(values[0])}`, 'i');
|
|
261
|
+
return isNegated ? { $not: regex } : { $regex: regex };
|
|
262
|
+
}
|
|
263
|
+
case '$ilike': {
|
|
264
|
+
const regex = new RegExp(asSafeRegexString(values[0]), 'i');
|
|
265
|
+
return isNegated ? { $not: regex } : { $regex: regex };
|
|
266
|
+
}
|
|
267
|
+
case '$null': {
|
|
268
|
+
return isNegated ? { $ne: null } : { $eq: null };
|
|
269
|
+
}
|
|
270
|
+
case '$contains': {
|
|
271
|
+
const expr = { $all: values };
|
|
272
|
+
return isNegated ? { $not: expr } : expr;
|
|
273
|
+
}
|
|
274
|
+
default:
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// builds the sort object for MongoDB queries (multiple fields sorting)
|
|
280
|
+
protected buildMongoSort(query: PaginateQuery): Record<string, 1 | -1> {
|
|
281
|
+
const { sortBy = [] } = query;
|
|
282
|
+
const sort: Record<string, 1 | -1> = {};
|
|
283
|
+
|
|
284
|
+
sortBy.forEach(([field, direction]) => {
|
|
285
|
+
sort[field] = direction.toUpperCase() === 'ASC' ? 1 : -1;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// default: _id DESC
|
|
289
|
+
if (Object.keys(sort).length === 0) {
|
|
290
|
+
sort['_id'] = -1;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return sort;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// builds the projection fields for MongoDB queries
|
|
297
|
+
// for future use with cursor-based pagination
|
|
298
|
+
protected buildMongoProjectionFields(query: PaginateQuery, cursorColumn = '_id'): string[] | undefined {
|
|
299
|
+
const { select } = query;
|
|
300
|
+
|
|
301
|
+
if (select && select.length) {
|
|
302
|
+
const fields = [...select];
|
|
303
|
+
if (!fields.includes(cursorColumn)) {
|
|
304
|
+
fields.push(cursorColumn);
|
|
305
|
+
}
|
|
306
|
+
return fields;
|
|
307
|
+
}
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// normalizes the cursor value to string (for pagination links)
|
|
312
|
+
// for future use with cursor-based pagination
|
|
313
|
+
protected normalizeCursorValue(val: any): string {
|
|
314
|
+
if (!val && val !== 0) return undefined;
|
|
315
|
+
|
|
316
|
+
if (val instanceof ObjectId && typeof val.toHexString === 'function') {
|
|
317
|
+
return val.toHexString();
|
|
318
|
+
}
|
|
319
|
+
if (val && val._bsontype === 'ObjectID' && typeof val.toHexString === 'function') {
|
|
320
|
+
return val.toHexString();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return String(val);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// builds the query string for pagination links
|
|
327
|
+
// for future use with cursor-based pagination
|
|
328
|
+
protected buildQueryString(obj: Record<string, any>): string {
|
|
329
|
+
const params = new URLSearchParams();
|
|
330
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
331
|
+
if (v === undefined || v === null) continue;
|
|
332
|
+
params.append(k, String(v));
|
|
333
|
+
}
|
|
334
|
+
return params.toString();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// filter the selected fields trough the whitelist coming from config
|
|
338
|
+
protected buildMongoSelect(query: PaginateQuery, paginateConfig: ExtendedPaginateConfig<E>): string[] | undefined {
|
|
339
|
+
const requestedSelect = query.select;
|
|
340
|
+
|
|
341
|
+
// no select requested -> no filtering needed
|
|
342
|
+
if (!requestedSelect || requestedSelect.length === 0) {
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const allowedSelectColumns = new Set(paginateConfig.select || []);
|
|
347
|
+
|
|
348
|
+
// if no allowed columns, warn and return undefined
|
|
349
|
+
if (allowedSelectColumns.size === 0) {
|
|
350
|
+
console.warn('no columns are allowed for selection in config');
|
|
351
|
+
// undefined means -> use typeorm default (all columns)
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// whitelist from config applied
|
|
356
|
+
const validSelect = requestedSelect.filter(field => {
|
|
357
|
+
const isAllowed = allowedSelectColumns.has(field);
|
|
358
|
+
if (!isAllowed) {
|
|
359
|
+
console.warn(`column not allowed: ${field}`);
|
|
360
|
+
}
|
|
361
|
+
return isAllowed;
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// if no valid fields remain after filtering, return undefined to avoid errors
|
|
365
|
+
if (validSelect.length === 0) {
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return validSelect;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Repository, EntityManager, Equal, FindOptionsWhere, ObjectLiteral } from 'typeorm';
|
|
2
2
|
import { PaginateConfig, PaginateQuery, Paginated, paginate } from 'nestjs-paginate';
|
|
3
|
-
import { IBaseRepository } from '
|
|
3
|
+
import { IBaseRepository } from '@/core/domain/interfaces/base.repository.interface';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Represents a generic repository for managing entities.
|
|
@@ -22,9 +22,9 @@ export class LoggerService {
|
|
|
22
22
|
) {}
|
|
23
23
|
|
|
24
24
|
private withAdditionalTraceId(meta?: LoggerMeta) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
return { ...meta,
|
|
25
|
+
const xcTraceId: string | undefined = this.cls.get('xcTraceId');
|
|
26
|
+
const xcActionId: string | undefined = this.cls.get('xcActionId');
|
|
27
|
+
return { ...meta, xcTraceId: xcTraceId ?? meta?.xcTraceId, xcActionId: xcActionId ?? meta?.xcActionId };
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
debug(msg: string, meta?: LoggerMeta): void {
|
|
@@ -60,7 +60,7 @@ export class LoggerService {
|
|
|
60
60
|
// Esempio di uso corretto:
|
|
61
61
|
someMethod() {
|
|
62
62
|
this.cls.run(() => {
|
|
63
|
-
this.cls.set('
|
|
63
|
+
this.cls.set('xcTraceId', 'my-trace-id');
|
|
64
64
|
// Tutto il codice asincrono che parte da qui vedrà il valore
|
|
65
65
|
this.logger.info('Log con trace id'); // Qui funziona
|
|
66
66
|
setTimeout(() => {
|
package/tsconfig.json
CHANGED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.DbHooksSubscriberModule = void 0;
|
|
10
|
-
const common_1 = require("@nestjs/common");
|
|
11
|
-
const nestjs_cls_1 = require("nestjs-cls");
|
|
12
|
-
let DbHooksSubscriberModule = class DbHooksSubscriberModule {
|
|
13
|
-
};
|
|
14
|
-
exports.DbHooksSubscriberModule = DbHooksSubscriberModule;
|
|
15
|
-
exports.DbHooksSubscriberModule = DbHooksSubscriberModule = __decorate([
|
|
16
|
-
(0, common_1.Module)({
|
|
17
|
-
imports: [
|
|
18
|
-
nestjs_cls_1.ClsModule,
|
|
19
|
-
],
|
|
20
|
-
})
|
|
21
|
-
], DbHooksSubscriberModule);
|
|
22
|
-
//# sourceMappingURL=db-hooks-subscriber.module.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"db-hooks-subscriber.module.js","sourceRoot":"","sources":["../../../../src/modules/db-hooks-subscriber/db-hooks-subscriber.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2CAAuC;AAShC,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;CAAG,CAAA;AAA1B,0DAAuB;kCAAvB,uBAAuB;IAPnC,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,sBAAS;SAEV;KAEF,CAAC;GACW,uBAAuB,CAAG"}
|