@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.
Files changed (63) hide show
  1. package/README.md +493 -658
  2. package/cjs/classes/api-service.class.js +59 -92
  3. package/cjs/classes/winston-logger-adapter.class.js +23 -40
  4. package/cjs/constants/permissions.js +11 -1
  5. package/cjs/dtos/delete.dto.js +10 -0
  6. package/cjs/dtos/response-payload.dto.js +0 -75
  7. package/cjs/guards/permission.guard.js +19 -18
  8. package/cjs/interceptors/index.js +0 -3
  9. package/cjs/interceptors/set-user-field-on-body.interceptor.js +20 -3
  10. package/cjs/middlewares/logger.middleware.js +50 -89
  11. package/cjs/modules/datasource/datasource.module.js +11 -14
  12. package/cjs/modules/datasource/multi-tenant-datasource.service.js +0 -4
  13. package/cjs/modules/utils/utils.service.js +22 -103
  14. package/cjs/utils/error-handler.util.js +12 -67
  15. package/cjs/utils/html-sanitizer.util.js +1 -11
  16. package/cjs/utils/index.js +2 -0
  17. package/cjs/utils/request.util.js +70 -0
  18. package/cjs/utils/string.util.js +63 -0
  19. package/classes/api-service.class.d.ts +2 -0
  20. package/classes/winston-logger-adapter.class.d.ts +2 -0
  21. package/constants/permissions.d.ts +12 -0
  22. package/dtos/delete.dto.d.ts +1 -0
  23. package/dtos/response-payload.dto.d.ts +0 -13
  24. package/fesm/classes/api-service.class.js +59 -92
  25. package/fesm/classes/winston-logger-adapter.class.js +23 -40
  26. package/fesm/constants/permissions.js +8 -1
  27. package/fesm/dtos/delete.dto.js +12 -2
  28. package/fesm/dtos/response-payload.dto.js +0 -69
  29. package/fesm/guards/permission.guard.js +19 -18
  30. package/fesm/interceptors/index.js +0 -3
  31. package/fesm/interceptors/set-user-field-on-body.interceptor.js +3 -0
  32. package/fesm/middlewares/logger.middleware.js +50 -83
  33. package/fesm/modules/datasource/datasource.module.js +11 -14
  34. package/fesm/modules/datasource/multi-tenant-datasource.service.js +0 -4
  35. package/fesm/modules/utils/utils.service.js +19 -89
  36. package/fesm/utils/error-handler.util.js +12 -68
  37. package/fesm/utils/html-sanitizer.util.js +1 -14
  38. package/fesm/utils/index.js +2 -0
  39. package/fesm/utils/request.util.js +58 -0
  40. package/fesm/utils/string.util.js +71 -0
  41. package/guards/permission.guard.d.ts +2 -0
  42. package/interceptors/index.d.ts +0 -3
  43. package/interceptors/set-user-field-on-body.interceptor.d.ts +3 -0
  44. package/interfaces/logged-user-info.interface.d.ts +0 -2
  45. package/middlewares/logger.middleware.d.ts +2 -2
  46. package/modules/datasource/datasource.module.d.ts +1 -0
  47. package/modules/datasource/multi-tenant-datasource.service.d.ts +0 -1
  48. package/modules/utils/utils.service.d.ts +2 -18
  49. package/package.json +2 -2
  50. package/utils/error-handler.util.d.ts +3 -18
  51. package/utils/html-sanitizer.util.d.ts +0 -1
  52. package/utils/index.d.ts +2 -0
  53. package/utils/request.util.d.ts +4 -0
  54. package/utils/string.util.d.ts +2 -0
  55. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -12
  56. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -12
  57. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -12
  58. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -4
  59. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -4
  60. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -4
  61. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -1
  62. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -1
  63. 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]];
@@ -1,4 +1,5 @@
1
1
  export declare class DeleteDto {
2
2
  id: string | string[];
3
3
  type: 'delete' | 'restore' | 'permanent';
4
+ deletedById?: string;
4
5
  }
@@ -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
- await this.ensureRepositoryInitialized();
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
- await qr.commitTransaction();
29
- if (this.isCacheable) await Promise.all([
30
- this.clearCacheForAll(),
31
- this.clearCacheForId(Array.isArray(saved) ? saved : [
32
- saved
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
- await this.ensureRepositoryInitialized();
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(Array.isArray(saved) ? saved : [
54
- saved
55
- ], user, qr);
56
- await qr.commitTransaction();
57
- if (this.isCacheable) await Promise.all([
58
- this.clearCacheForAll(),
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
- await this.ensureRepositoryInitialized();
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
- await qr.commitTransaction();
85
- if (this.isCacheable) await Promise.all([
86
- this.clearCacheForAll(),
87
- this.clearCacheForId(Array.isArray(saved) ? saved : [
88
- saved
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
- await this.ensureRepositoryInitialized();
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(Array.isArray(saved) ? saved : [
110
- saved
111
- ], user, qr);
112
- await qr.commitTransaction();
113
- if (this.isCacheable) await Promise.all([
114
- this.clearCacheForAll(),
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
- log(message, context, ...args) {
48
- const meta = args.length > 0 && typeof args[0] === 'object' ? args[0] : {};
49
- winstonLogger.info(message, {
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
- const meta = args.length > 0 && typeof args[0] === 'object' ? args[0] : {};
57
- winstonLogger.error(message, {
58
- context: context || this.context,
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
- const meta = args.length > 0 && typeof args[0] === 'object' ? args[0] : {};
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
- const meta = args.length > 0 && typeof args[0] === 'object' ? args[0] : {};
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
- const meta = args.length > 0 && typeof args[0] === 'object' ? args[0] : {};
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
- const logMessage = args.length > 0 ? `${message} ${JSON.stringify(args)}` : message;
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
- const logMessage = args.length > 0 ? `${message} ${JSON.stringify(args)}` : message;
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
- const logMessage = args.length > 0 ? `${message} ${JSON.stringify(args)}` : message;
110
- this.logger.warn(logMessage, context);
95
+ this.logger.warn(this.formatMessage(message, args), context);
111
96
  }
112
97
  debug(message, context, ...args) {
113
- const logMessage = args.length > 0 ? `${message} ${JSON.stringify(args)}` : message;
114
- this.logger.debug(logMessage, context);
98
+ this.logger.debug(this.formatMessage(message, args), context);
115
99
  }
116
100
  verbose(message, context, ...args) {
117
- const logMessage = args.length > 0 ? `${message} ${JSON.stringify(args)}` : message;
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
  };
@@ -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);