@flusys/nestjs-shared 1.0.0-rc → 2.0.0
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/README.md +493 -658
- package/cjs/classes/api-service.class.js +59 -92
- package/cjs/classes/winston-logger-adapter.class.js +23 -40
- package/cjs/constants/permissions.js +11 -1
- package/cjs/dtos/delete.dto.js +10 -0
- package/cjs/dtos/response-payload.dto.js +0 -75
- package/cjs/guards/permission.guard.js +19 -18
- package/cjs/interceptors/index.js +0 -3
- package/cjs/interceptors/set-user-field-on-body.interceptor.js +20 -3
- package/cjs/middlewares/logger.middleware.js +50 -89
- package/cjs/modules/datasource/datasource.module.js +11 -14
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +0 -4
- package/cjs/modules/utils/utils.service.js +22 -103
- package/cjs/utils/error-handler.util.js +12 -67
- package/cjs/utils/html-sanitizer.util.js +1 -11
- package/cjs/utils/index.js +2 -0
- package/cjs/utils/request.util.js +70 -0
- package/cjs/utils/string.util.js +63 -0
- package/classes/api-service.class.d.ts +2 -0
- package/classes/winston-logger-adapter.class.d.ts +2 -0
- package/constants/permissions.d.ts +12 -0
- package/dtos/delete.dto.d.ts +1 -0
- package/dtos/response-payload.dto.d.ts +0 -13
- package/fesm/classes/api-service.class.js +59 -92
- package/fesm/classes/winston-logger-adapter.class.js +23 -40
- package/fesm/constants/permissions.js +8 -1
- package/fesm/dtos/delete.dto.js +12 -2
- package/fesm/dtos/response-payload.dto.js +0 -69
- package/fesm/guards/permission.guard.js +19 -18
- package/fesm/interceptors/index.js +0 -3
- package/fesm/interceptors/set-user-field-on-body.interceptor.js +3 -0
- package/fesm/middlewares/logger.middleware.js +50 -83
- package/fesm/modules/datasource/datasource.module.js +11 -14
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +0 -4
- package/fesm/modules/utils/utils.service.js +19 -89
- package/fesm/utils/error-handler.util.js +12 -68
- package/fesm/utils/html-sanitizer.util.js +1 -14
- package/fesm/utils/index.js +2 -0
- package/fesm/utils/request.util.js +58 -0
- package/fesm/utils/string.util.js +71 -0
- package/guards/permission.guard.d.ts +2 -0
- package/interceptors/index.d.ts +0 -3
- package/interceptors/set-user-field-on-body.interceptor.d.ts +3 -0
- package/interfaces/logged-user-info.interface.d.ts +0 -2
- package/middlewares/logger.middleware.d.ts +2 -2
- package/modules/datasource/datasource.module.d.ts +1 -0
- package/modules/datasource/multi-tenant-datasource.service.d.ts +0 -1
- package/modules/utils/utils.service.d.ts +2 -18
- package/package.json +2 -2
- package/utils/error-handler.util.d.ts +3 -18
- package/utils/html-sanitizer.util.d.ts +0 -1
- package/utils/index.d.ts +2 -0
- package/utils/request.util.d.ts +4 -0
- package/utils/string.util.d.ts +2 -0
- package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -12
- package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -12
- package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -12
- package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -4
- package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -4
- package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -4
- package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -1
- package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -1
- package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
get buildCookieOptions () {
|
|
13
|
+
return buildCookieOptions;
|
|
14
|
+
},
|
|
15
|
+
get isBrowserRequest () {
|
|
16
|
+
return isBrowserRequest;
|
|
17
|
+
},
|
|
18
|
+
get parseDurationToMs () {
|
|
19
|
+
return parseDurationToMs;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const _constants = require("../constants");
|
|
23
|
+
/** Time unit multipliers in milliseconds */ const TIME_UNIT_MS = {
|
|
24
|
+
s: 1000,
|
|
25
|
+
m: 60 * 1000,
|
|
26
|
+
h: 60 * 60 * 1000,
|
|
27
|
+
d: 24 * 60 * 60 * 1000,
|
|
28
|
+
w: 7 * 24 * 60 * 60 * 1000
|
|
29
|
+
};
|
|
30
|
+
/** Get normalized client type from request header */ function getClientType(req) {
|
|
31
|
+
const clientType = req.headers[_constants.CLIENT_TYPE_HEADER];
|
|
32
|
+
return clientType ? clientType.toLowerCase() : null;
|
|
33
|
+
}
|
|
34
|
+
function isBrowserRequest(req) {
|
|
35
|
+
const clientType = getClientType(req);
|
|
36
|
+
if (clientType) {
|
|
37
|
+
return clientType === 'browser' || clientType === 'web';
|
|
38
|
+
}
|
|
39
|
+
const accept = req.headers['accept'] || '';
|
|
40
|
+
if (accept.includes('text/html')) return true;
|
|
41
|
+
const userAgent = req.headers['user-agent'] || '';
|
|
42
|
+
const browserPatterns = /mozilla|chrome|safari|firefox|edge|opera|msie/i;
|
|
43
|
+
return browserPatterns.test(userAgent) && !userAgent.includes('Postman');
|
|
44
|
+
}
|
|
45
|
+
function buildCookieOptions(req) {
|
|
46
|
+
const hostname = req.hostname || '';
|
|
47
|
+
const origin = req.headers.origin || '';
|
|
48
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
49
|
+
const forwardedProto = req.headers['x-forwarded-proto'];
|
|
50
|
+
const isHttps = isProduction || forwardedProto === 'https' || origin.startsWith('https://') || req.secure;
|
|
51
|
+
let domain;
|
|
52
|
+
const domainParts = hostname.split('.');
|
|
53
|
+
if (domainParts.length > 2 && !hostname.includes('localhost')) {
|
|
54
|
+
domain = '.' + domainParts.slice(-2).join('.');
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
secure: isHttps,
|
|
58
|
+
sameSite: isHttps ? 'strict' : 'lax',
|
|
59
|
+
...domain && {
|
|
60
|
+
domain
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function parseDurationToMs(duration, defaultMs = TIME_UNIT_MS.w) {
|
|
65
|
+
const match = duration.match(/^(\d+)(s|m|h|d|w)$/);
|
|
66
|
+
if (!match) return defaultMs;
|
|
67
|
+
const value = parseInt(match[1], 10);
|
|
68
|
+
const unit = match[2];
|
|
69
|
+
return value * (TIME_UNIT_MS[unit] ?? TIME_UNIT_MS.d);
|
|
70
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String utility functions
|
|
3
|
+
*/ /**
|
|
4
|
+
* Generate URL-friendly slug from a string
|
|
5
|
+
* @param text - The text to convert to slug
|
|
6
|
+
* @param maxLength - Maximum length of the slug (default: 100)
|
|
7
|
+
* @returns URL-friendly slug
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* generateSlug('My Company Name') // 'my-company-name'
|
|
11
|
+
* generateSlug('Hello World!') // 'hello-world'
|
|
12
|
+
*/ "use strict";
|
|
13
|
+
Object.defineProperty(exports, "__esModule", {
|
|
14
|
+
value: true
|
|
15
|
+
});
|
|
16
|
+
function _export(target, all) {
|
|
17
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
_export(exports, {
|
|
23
|
+
get generateSlug () {
|
|
24
|
+
return generateSlug;
|
|
25
|
+
},
|
|
26
|
+
get generateUniqueSlug () {
|
|
27
|
+
return generateUniqueSlug;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
function generateSlug(text, maxLength = 100) {
|
|
31
|
+
return text.toLowerCase().trim().replace(/[^a-z0-9\s-]/g, '') // Remove special characters
|
|
32
|
+
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
33
|
+
.replace(/-+/g, '-') // Replace multiple hyphens with single
|
|
34
|
+
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
|
|
35
|
+
.substring(0, maxLength);
|
|
36
|
+
}
|
|
37
|
+
async function generateUniqueSlug(text, findMatchingSlugs, maxLength = 100) {
|
|
38
|
+
const baseSlug = generateSlug(text, maxLength);
|
|
39
|
+
// Single query to get all matching slugs
|
|
40
|
+
const existingSlugs = await findMatchingSlugs(baseSlug);
|
|
41
|
+
// No collisions - base slug is available
|
|
42
|
+
if (existingSlugs.length === 0) {
|
|
43
|
+
return baseSlug;
|
|
44
|
+
}
|
|
45
|
+
// Build set for O(1) lookup
|
|
46
|
+
const existingSet = new Set(existingSlugs);
|
|
47
|
+
// Base slug not taken (only suffixed versions exist)
|
|
48
|
+
if (!existingSet.has(baseSlug)) {
|
|
49
|
+
return baseSlug;
|
|
50
|
+
}
|
|
51
|
+
// Find next available suffix in memory (fast)
|
|
52
|
+
let counter = 1;
|
|
53
|
+
while(counter < 10000){
|
|
54
|
+
const slugWithSuffix = `${baseSlug}-${counter}`;
|
|
55
|
+
if (!existingSet.has(slugWithSuffix)) {
|
|
56
|
+
return slugWithSuffix;
|
|
57
|
+
}
|
|
58
|
+
counter++;
|
|
59
|
+
}
|
|
60
|
+
// Fallback: append random string (extremely rare edge case)
|
|
61
|
+
const randomSuffix = Math.random().toString(36).substring(2, 8);
|
|
62
|
+
return `${baseSlug}-${randomSuffix}`;
|
|
63
|
+
}
|
|
@@ -30,6 +30,8 @@ export declare abstract class ApiService<CreateDtoT extends object, UpdateDtoT e
|
|
|
30
30
|
clearCacheForAll(): Promise<void>;
|
|
31
31
|
clearCacheForId(entities: EntityT[]): Promise<void>;
|
|
32
32
|
private handleError;
|
|
33
|
+
private ensureArray;
|
|
34
|
+
private executeInTransaction;
|
|
33
35
|
protected ensureRepositoryInitialized(): Promise<void>;
|
|
34
36
|
protected beforeInsertOperation(_dto: CreateDtoT | Array<CreateDtoT>, _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
|
|
35
37
|
protected afterInsertOperation(_entity: EntityT[], _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
|
|
@@ -3,6 +3,7 @@ import { ILogger } from '../interfaces/logger.interface';
|
|
|
3
3
|
export declare class WinstonLoggerAdapter implements ILogger {
|
|
4
4
|
private readonly context?;
|
|
5
5
|
constructor(context?: string);
|
|
6
|
+
private buildLogMeta;
|
|
6
7
|
log(message: string, context?: string, ...args: any[]): void;
|
|
7
8
|
error(message: string, trace?: string, context?: string, ...args: any[]): void;
|
|
8
9
|
warn(message: string, context?: string, ...args: any[]): void;
|
|
@@ -12,6 +13,7 @@ export declare class WinstonLoggerAdapter implements ILogger {
|
|
|
12
13
|
export declare class NestLoggerAdapter implements ILogger {
|
|
13
14
|
private readonly logger;
|
|
14
15
|
constructor(logger: Logger);
|
|
16
|
+
private formatMessage;
|
|
15
17
|
log(message: string, context?: string, ...args: any[]): void;
|
|
16
18
|
error(message: string, trace?: string, context?: string, ...args: any[]): void;
|
|
17
19
|
warn(message: string, context?: string, ...args: any[]): void;
|
|
@@ -80,6 +80,12 @@ export declare const FORM_PERMISSIONS: {
|
|
|
80
80
|
readonly UPDATE: "form.update";
|
|
81
81
|
readonly DELETE: "form.delete";
|
|
82
82
|
};
|
|
83
|
+
export declare const FORM_RESULT_PERMISSIONS: {
|
|
84
|
+
readonly CREATE: "form-result.create";
|
|
85
|
+
readonly READ: "form-result.read";
|
|
86
|
+
readonly UPDATE: "form-result.update";
|
|
87
|
+
readonly DELETE: "form-result.delete";
|
|
88
|
+
};
|
|
83
89
|
export declare const PERMISSIONS: {
|
|
84
90
|
readonly USER: {
|
|
85
91
|
readonly CREATE: "user.create";
|
|
@@ -163,5 +169,11 @@ export declare const PERMISSIONS: {
|
|
|
163
169
|
readonly UPDATE: "form.update";
|
|
164
170
|
readonly DELETE: "form.delete";
|
|
165
171
|
};
|
|
172
|
+
readonly FORM_RESULT: {
|
|
173
|
+
readonly CREATE: "form-result.create";
|
|
174
|
+
readonly READ: "form-result.read";
|
|
175
|
+
readonly UPDATE: "form-result.update";
|
|
176
|
+
readonly DELETE: "form-result.delete";
|
|
177
|
+
};
|
|
166
178
|
};
|
|
167
179
|
export type PermissionCode = (typeof PERMISSIONS)[keyof typeof PERMISSIONS][keyof (typeof PERMISSIONS)[keyof typeof PERMISSIONS]];
|
package/dtos/delete.dto.d.ts
CHANGED
|
@@ -41,16 +41,3 @@ export declare class MessageResponseDto {
|
|
|
41
41
|
message: string;
|
|
42
42
|
_meta?: RequestMetaDto;
|
|
43
43
|
}
|
|
44
|
-
export declare class ValidationErrorDto {
|
|
45
|
-
field: string;
|
|
46
|
-
message: string;
|
|
47
|
-
constraint?: string;
|
|
48
|
-
}
|
|
49
|
-
export declare class ErrorResponseDto {
|
|
50
|
-
success: false;
|
|
51
|
-
message: string;
|
|
52
|
-
code?: string;
|
|
53
|
-
errors?: ValidationErrorDto[];
|
|
54
|
-
_meta?: RequestMetaDto;
|
|
55
|
-
}
|
|
56
|
-
export type ApiResponse<T> = SingleResponseDto<T> | ListResponseDto<T> | BulkResponseDto<T> | MessageResponseDto | ErrorResponseDto;
|
|
@@ -16,116 +16,52 @@ import { Logger, NotFoundException } from '@nestjs/common';
|
|
|
16
16
|
import { In } from 'typeorm';
|
|
17
17
|
/** Generic API service with CRUD operations and caching support */ export class ApiService {
|
|
18
18
|
async insert(dto, user) {
|
|
19
|
-
|
|
20
|
-
const qr = this.repository.manager.connection.createQueryRunner();
|
|
21
|
-
await qr.connect();
|
|
22
|
-
await qr.startTransaction();
|
|
23
|
-
try {
|
|
19
|
+
return this.executeInTransaction('insert', async (qr)=>{
|
|
24
20
|
await this.beforeInsertOperation(dto, user, qr);
|
|
25
21
|
const entities = await this.convertRequestDtoToEntity(dto, user);
|
|
26
22
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
27
|
-
await this.afterInsertOperation(saved, user, qr);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
])
|
|
34
|
-
]);
|
|
35
|
-
const first = Array.isArray(saved) ? saved[0] : saved;
|
|
36
|
-
return this.convertEntityToResponseDto(first, false);
|
|
37
|
-
} catch (error) {
|
|
38
|
-
await qr.rollbackTransaction();
|
|
39
|
-
this.handleError(error, 'insert');
|
|
40
|
-
} finally{
|
|
41
|
-
await qr.release();
|
|
42
|
-
}
|
|
23
|
+
await this.afterInsertOperation(this.ensureArray(saved), user, qr);
|
|
24
|
+
return {
|
|
25
|
+
saved,
|
|
26
|
+
returnFirst: true
|
|
27
|
+
};
|
|
28
|
+
});
|
|
43
29
|
}
|
|
44
30
|
async insertMany(dtos, user) {
|
|
45
|
-
|
|
46
|
-
const qr = this.repository.manager.connection.createQueryRunner();
|
|
47
|
-
await qr.connect();
|
|
48
|
-
await qr.startTransaction();
|
|
49
|
-
try {
|
|
31
|
+
return this.executeInTransaction('insertMany', async (qr)=>{
|
|
50
32
|
await this.beforeInsertOperation(dtos, user, qr);
|
|
51
33
|
const entities = await this.convertRequestDtoToEntity(dtos, user);
|
|
52
34
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
53
|
-
await this.afterInsertOperation(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.clearCacheForId(Array.isArray(saved) ? saved : [
|
|
60
|
-
saved
|
|
61
|
-
])
|
|
62
|
-
]);
|
|
63
|
-
const list = (Array.isArray(saved) ? saved : [
|
|
64
|
-
saved
|
|
65
|
-
]).map((e)=>this.convertEntityToResponseDto(e, false));
|
|
66
|
-
return list;
|
|
67
|
-
} catch (error) {
|
|
68
|
-
await qr.rollbackTransaction();
|
|
69
|
-
this.handleError(error, 'insertMany');
|
|
70
|
-
} finally{
|
|
71
|
-
await qr.release();
|
|
72
|
-
}
|
|
35
|
+
await this.afterInsertOperation(this.ensureArray(saved), user, qr);
|
|
36
|
+
return {
|
|
37
|
+
saved,
|
|
38
|
+
returnFirst: false
|
|
39
|
+
};
|
|
40
|
+
});
|
|
73
41
|
}
|
|
74
42
|
async update(dto, user) {
|
|
75
|
-
|
|
76
|
-
const qr = this.repository.manager.connection.createQueryRunner();
|
|
77
|
-
await qr.connect();
|
|
78
|
-
await qr.startTransaction();
|
|
79
|
-
try {
|
|
43
|
+
return this.executeInTransaction('update', async (qr)=>{
|
|
80
44
|
await this.beforeUpdateOperation(dto, user, qr);
|
|
81
45
|
const entities = await this.convertRequestDtoToEntity(dto, user);
|
|
82
46
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
83
|
-
await this.afterUpdateOperation(saved, user, qr);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
])
|
|
90
|
-
]);
|
|
91
|
-
const first = Array.isArray(saved) ? saved[0] : saved;
|
|
92
|
-
return this.convertEntityToResponseDto(first, false);
|
|
93
|
-
} catch (error) {
|
|
94
|
-
await qr.rollbackTransaction();
|
|
95
|
-
this.handleError(error, 'update');
|
|
96
|
-
} finally{
|
|
97
|
-
await qr.release();
|
|
98
|
-
}
|
|
47
|
+
await this.afterUpdateOperation(this.ensureArray(saved), user, qr);
|
|
48
|
+
return {
|
|
49
|
+
saved,
|
|
50
|
+
returnFirst: true
|
|
51
|
+
};
|
|
52
|
+
});
|
|
99
53
|
}
|
|
100
54
|
async updateMany(dtos, user) {
|
|
101
|
-
|
|
102
|
-
const qr = this.repository.manager.connection.createQueryRunner();
|
|
103
|
-
await qr.connect();
|
|
104
|
-
await qr.startTransaction();
|
|
105
|
-
try {
|
|
55
|
+
return this.executeInTransaction('updateMany', async (qr)=>{
|
|
106
56
|
await this.beforeUpdateOperation(dtos, user, qr);
|
|
107
57
|
const entities = await this.convertRequestDtoToEntity(dtos, user);
|
|
108
58
|
const saved = await qr.manager.save(this.repository.target, entities);
|
|
109
|
-
await this.afterUpdateOperation(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.clearCacheForId(Array.isArray(saved) ? saved : [
|
|
116
|
-
saved
|
|
117
|
-
])
|
|
118
|
-
]);
|
|
119
|
-
const list = (Array.isArray(saved) ? saved : [
|
|
120
|
-
saved
|
|
121
|
-
]).map((e)=>this.convertEntityToResponseDto(e, false));
|
|
122
|
-
return list;
|
|
123
|
-
} catch (error) {
|
|
124
|
-
await qr.rollbackTransaction();
|
|
125
|
-
this.handleError(error, 'updateMany');
|
|
126
|
-
} finally{
|
|
127
|
-
await qr.release();
|
|
128
|
-
}
|
|
59
|
+
await this.afterUpdateOperation(this.ensureArray(saved), user, qr);
|
|
60
|
+
return {
|
|
61
|
+
saved,
|
|
62
|
+
returnFirst: false
|
|
63
|
+
};
|
|
64
|
+
});
|
|
129
65
|
}
|
|
130
66
|
async findByIds(ids, user) {
|
|
131
67
|
await this.ensureRepositoryInitialized();
|
|
@@ -344,6 +280,37 @@ import { In } from 'typeorm';
|
|
|
344
280
|
});
|
|
345
281
|
ErrorHandler.rethrowError(error);
|
|
346
282
|
}
|
|
283
|
+
/** Ensures value is always an array */ ensureArray(value) {
|
|
284
|
+
return Array.isArray(value) ? value : [
|
|
285
|
+
value
|
|
286
|
+
];
|
|
287
|
+
}
|
|
288
|
+
/** Executes operation in transaction with automatic cache clearing */ async executeInTransaction(operation, fn) {
|
|
289
|
+
await this.ensureRepositoryInitialized();
|
|
290
|
+
const qr = this.repository.manager.connection.createQueryRunner();
|
|
291
|
+
await qr.connect();
|
|
292
|
+
await qr.startTransaction();
|
|
293
|
+
try {
|
|
294
|
+
const { saved, returnFirst } = await fn(qr);
|
|
295
|
+
await qr.commitTransaction();
|
|
296
|
+
const savedArray = this.ensureArray(saved);
|
|
297
|
+
if (this.isCacheable) {
|
|
298
|
+
await Promise.all([
|
|
299
|
+
this.clearCacheForAll(),
|
|
300
|
+
this.clearCacheForId(savedArray)
|
|
301
|
+
]);
|
|
302
|
+
}
|
|
303
|
+
if (returnFirst) {
|
|
304
|
+
return this.convertEntityToResponseDto(savedArray[0], false);
|
|
305
|
+
}
|
|
306
|
+
return savedArray.map((e)=>this.convertEntityToResponseDto(e, false));
|
|
307
|
+
} catch (error) {
|
|
308
|
+
await qr.rollbackTransaction();
|
|
309
|
+
this.handleError(error, operation);
|
|
310
|
+
} finally{
|
|
311
|
+
await qr.release();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
347
314
|
// Hooks (override in child classes)
|
|
348
315
|
async ensureRepositoryInitialized() {}
|
|
349
316
|
async beforeInsertOperation(_dto, _user, _queryRunner) {}
|
|
@@ -44,46 +44,31 @@ import { getRequestId, getUserId, getTenantId, getCompanyId } from '../middlewar
|
|
|
44
44
|
* // Output: { requestId: 'uuid', userId: 'user-id', context: 'MyService', message: 'Operation completed', orderId: '123' }
|
|
45
45
|
* ```
|
|
46
46
|
*/ export class WinstonLoggerAdapter {
|
|
47
|
-
|
|
48
|
-
const meta = args
|
|
49
|
-
|
|
47
|
+
buildLogMeta(context, args, extra) {
|
|
48
|
+
const meta = args?.length && typeof args[0] === 'object' ? args[0] : {};
|
|
49
|
+
return {
|
|
50
50
|
context: context || this.context,
|
|
51
51
|
...getCorrelationMeta(),
|
|
52
|
-
...meta
|
|
53
|
-
|
|
52
|
+
...meta,
|
|
53
|
+
...extra
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
log(message, context, ...args) {
|
|
57
|
+
winstonLogger.info(message, this.buildLogMeta(context, args));
|
|
54
58
|
}
|
|
55
59
|
error(message, trace, context, ...args) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
stack: trace,
|
|
60
|
-
...getCorrelationMeta(),
|
|
61
|
-
...meta
|
|
62
|
-
});
|
|
60
|
+
winstonLogger.error(message, this.buildLogMeta(context, args, {
|
|
61
|
+
stack: trace
|
|
62
|
+
}));
|
|
63
63
|
}
|
|
64
64
|
warn(message, context, ...args) {
|
|
65
|
-
|
|
66
|
-
winstonLogger.warn(message, {
|
|
67
|
-
context: context || this.context,
|
|
68
|
-
...getCorrelationMeta(),
|
|
69
|
-
...meta
|
|
70
|
-
});
|
|
65
|
+
winstonLogger.warn(message, this.buildLogMeta(context, args));
|
|
71
66
|
}
|
|
72
67
|
debug(message, context, ...args) {
|
|
73
|
-
|
|
74
|
-
winstonLogger.debug(message, {
|
|
75
|
-
context: context || this.context,
|
|
76
|
-
...getCorrelationMeta(),
|
|
77
|
-
...meta
|
|
78
|
-
});
|
|
68
|
+
winstonLogger.debug(message, this.buildLogMeta(context, args));
|
|
79
69
|
}
|
|
80
70
|
verbose(message, context, ...args) {
|
|
81
|
-
|
|
82
|
-
winstonLogger.verbose(message, {
|
|
83
|
-
context: context || this.context,
|
|
84
|
-
...getCorrelationMeta(),
|
|
85
|
-
...meta
|
|
86
|
-
});
|
|
71
|
+
winstonLogger.verbose(message, this.buildLogMeta(context, args));
|
|
87
72
|
}
|
|
88
73
|
constructor(context){
|
|
89
74
|
_define_property(this, "context", void 0);
|
|
@@ -97,25 +82,23 @@ import { getRequestId, getUserId, getTenantId, getCompanyId } from '../middlewar
|
|
|
97
82
|
* Use this when you need to use NestJS's built-in logger
|
|
98
83
|
* instead of Winston (e.g., for testing)
|
|
99
84
|
*/ export class NestLoggerAdapter {
|
|
85
|
+
formatMessage(message, args) {
|
|
86
|
+
return args?.length ? `${message} ${JSON.stringify(args)}` : message;
|
|
87
|
+
}
|
|
100
88
|
log(message, context, ...args) {
|
|
101
|
-
|
|
102
|
-
this.logger.log(logMessage, context);
|
|
89
|
+
this.logger.log(this.formatMessage(message, args), context);
|
|
103
90
|
}
|
|
104
91
|
error(message, trace, context, ...args) {
|
|
105
|
-
|
|
106
|
-
this.logger.error(logMessage, trace, context);
|
|
92
|
+
this.logger.error(this.formatMessage(message, args), trace, context);
|
|
107
93
|
}
|
|
108
94
|
warn(message, context, ...args) {
|
|
109
|
-
|
|
110
|
-
this.logger.warn(logMessage, context);
|
|
95
|
+
this.logger.warn(this.formatMessage(message, args), context);
|
|
111
96
|
}
|
|
112
97
|
debug(message, context, ...args) {
|
|
113
|
-
|
|
114
|
-
this.logger.debug(logMessage, context);
|
|
98
|
+
this.logger.debug(this.formatMessage(message, args), context);
|
|
115
99
|
}
|
|
116
100
|
verbose(message, context, ...args) {
|
|
117
|
-
|
|
118
|
-
this.logger.verbose(logMessage, context);
|
|
101
|
+
this.logger.verbose(this.formatMessage(message, args), context);
|
|
119
102
|
}
|
|
120
103
|
constructor(logger){
|
|
121
104
|
_define_property(this, "logger", void 0);
|
|
@@ -94,6 +94,12 @@ export const FORM_PERMISSIONS = {
|
|
|
94
94
|
UPDATE: 'form.update',
|
|
95
95
|
DELETE: 'form.delete'
|
|
96
96
|
};
|
|
97
|
+
export const FORM_RESULT_PERMISSIONS = {
|
|
98
|
+
CREATE: 'form-result.create',
|
|
99
|
+
READ: 'form-result.read',
|
|
100
|
+
UPDATE: 'form-result.update',
|
|
101
|
+
DELETE: 'form-result.delete'
|
|
102
|
+
};
|
|
97
103
|
// ==================== AGGREGATED EXPORTS ====================
|
|
98
104
|
/**
|
|
99
105
|
* All permission codes grouped by module
|
|
@@ -117,5 +123,6 @@ export const FORM_PERMISSIONS = {
|
|
|
117
123
|
EMAIL_CONFIG: EMAIL_CONFIG_PERMISSIONS,
|
|
118
124
|
EMAIL_TEMPLATE: EMAIL_TEMPLATE_PERMISSIONS,
|
|
119
125
|
// Form Builder
|
|
120
|
-
FORM: FORM_PERMISSIONS
|
|
126
|
+
FORM: FORM_PERMISSIONS,
|
|
127
|
+
FORM_RESULT: FORM_RESULT_PERMISSIONS
|
|
121
128
|
};
|
package/fesm/dtos/delete.dto.js
CHANGED
|
@@ -20,12 +20,13 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
20
20
|
function _ts_metadata(k, v) {
|
|
21
21
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
22
22
|
}
|
|
23
|
-
import { ApiProperty } from '@nestjs/swagger';
|
|
24
|
-
import { IsIn, IsNotEmpty } from 'class-validator';
|
|
23
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
24
|
+
import { IsIn, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
|
|
25
25
|
export class DeleteDto {
|
|
26
26
|
constructor(){
|
|
27
27
|
_define_property(this, "id", void 0);
|
|
28
28
|
_define_property(this, "type", void 0);
|
|
29
|
+
_define_property(this, "deletedById", void 0);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
_ts_decorate([
|
|
@@ -70,3 +71,12 @@ _ts_decorate([
|
|
|
70
71
|
]),
|
|
71
72
|
_ts_metadata("design:type", String)
|
|
72
73
|
], DeleteDto.prototype, "type", void 0);
|
|
74
|
+
_ts_decorate([
|
|
75
|
+
ApiPropertyOptional({
|
|
76
|
+
description: 'User ID who initiated the deletion (auto-set by interceptor)',
|
|
77
|
+
example: 'f2e9c8d0-7a2a-11eb-9439-0242ac130002'
|
|
78
|
+
}),
|
|
79
|
+
IsOptional(),
|
|
80
|
+
IsUUID(),
|
|
81
|
+
_ts_metadata("design:type", String)
|
|
82
|
+
], DeleteDto.prototype, "deletedById", void 0);
|
|
@@ -262,72 +262,3 @@ _ts_decorate([
|
|
|
262
262
|
MessageResponseDto = _ts_decorate([
|
|
263
263
|
ApiExtraModels()
|
|
264
264
|
], MessageResponseDto);
|
|
265
|
-
export class ValidationErrorDto {
|
|
266
|
-
constructor(){
|
|
267
|
-
_define_property(this, "field", void 0);
|
|
268
|
-
_define_property(this, "message", void 0);
|
|
269
|
-
_define_property(this, "constraint", void 0);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
_ts_decorate([
|
|
273
|
-
ApiProperty({
|
|
274
|
-
example: 'email'
|
|
275
|
-
}),
|
|
276
|
-
_ts_metadata("design:type", String)
|
|
277
|
-
], ValidationErrorDto.prototype, "field", void 0);
|
|
278
|
-
_ts_decorate([
|
|
279
|
-
ApiProperty({
|
|
280
|
-
example: 'Invalid email format'
|
|
281
|
-
}),
|
|
282
|
-
_ts_metadata("design:type", String)
|
|
283
|
-
], ValidationErrorDto.prototype, "message", void 0);
|
|
284
|
-
_ts_decorate([
|
|
285
|
-
ApiPropertyOptional({
|
|
286
|
-
example: 'isEmail'
|
|
287
|
-
}),
|
|
288
|
-
_ts_metadata("design:type", String)
|
|
289
|
-
], ValidationErrorDto.prototype, "constraint", void 0);
|
|
290
|
-
export class ErrorResponseDto {
|
|
291
|
-
constructor(){
|
|
292
|
-
_define_property(this, "success", void 0);
|
|
293
|
-
_define_property(this, "message", void 0);
|
|
294
|
-
_define_property(this, "code", void 0);
|
|
295
|
-
_define_property(this, "errors", void 0);
|
|
296
|
-
_define_property(this, "_meta", void 0);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
_ts_decorate([
|
|
300
|
-
ApiProperty({
|
|
301
|
-
example: false
|
|
302
|
-
}),
|
|
303
|
-
_ts_metadata("design:type", Boolean)
|
|
304
|
-
], ErrorResponseDto.prototype, "success", void 0);
|
|
305
|
-
_ts_decorate([
|
|
306
|
-
ApiProperty({
|
|
307
|
-
example: 'Validation failed'
|
|
308
|
-
}),
|
|
309
|
-
_ts_metadata("design:type", String)
|
|
310
|
-
], ErrorResponseDto.prototype, "message", void 0);
|
|
311
|
-
_ts_decorate([
|
|
312
|
-
ApiPropertyOptional({
|
|
313
|
-
example: 'VALIDATION_ERROR'
|
|
314
|
-
}),
|
|
315
|
-
_ts_metadata("design:type", String)
|
|
316
|
-
], ErrorResponseDto.prototype, "code", void 0);
|
|
317
|
-
_ts_decorate([
|
|
318
|
-
ApiPropertyOptional({
|
|
319
|
-
type: [
|
|
320
|
-
ValidationErrorDto
|
|
321
|
-
]
|
|
322
|
-
}),
|
|
323
|
-
_ts_metadata("design:type", Array)
|
|
324
|
-
], ErrorResponseDto.prototype, "errors", void 0);
|
|
325
|
-
_ts_decorate([
|
|
326
|
-
ApiPropertyOptional({
|
|
327
|
-
type: RequestMetaDto
|
|
328
|
-
}),
|
|
329
|
-
_ts_metadata("design:type", typeof RequestMetaDto === "undefined" ? Object : RequestMetaDto)
|
|
330
|
-
], ErrorResponseDto.prototype, "_meta", void 0);
|
|
331
|
-
ErrorResponseDto = _ts_decorate([
|
|
332
|
-
ApiExtraModels()
|
|
333
|
-
], ErrorResponseDto);
|