@flusys/nestjs-form-builder 3.0.1 → 4.0.0-lts

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.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
5
  _export_star(require("./form-builder.constants"), exports);
6
+ _export_star(require("./message-keys"), exports);
6
7
  function _export_star(from, to) {
7
8
  Object.keys(from).forEach(function(k) {
8
9
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
@@ -0,0 +1,62 @@
1
+ // ==================== FORM BUILDER MODULE MESSAGE KEYS ====================
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ function _export(target, all) {
7
+ for(var name in all)Object.defineProperty(target, name, {
8
+ enumerable: true,
9
+ get: Object.getOwnPropertyDescriptor(all, name).get
10
+ });
11
+ }
12
+ _export(exports, {
13
+ get FORM_BUILDER_MODULE_MESSAGES () {
14
+ return FORM_BUILDER_MODULE_MESSAGES;
15
+ },
16
+ get FORM_MESSAGES () {
17
+ return FORM_MESSAGES;
18
+ },
19
+ get FORM_RESULT_MESSAGES () {
20
+ return FORM_RESULT_MESSAGES;
21
+ }
22
+ });
23
+ const FORM_MESSAGES = {
24
+ CREATE_SUCCESS: 'form.create.success',
25
+ CREATE_MANY_SUCCESS: 'form.create.many.success',
26
+ GET_SUCCESS: 'form.get.success',
27
+ GET_ALL_SUCCESS: 'form.get.all.success',
28
+ UPDATE_SUCCESS: 'form.update.success',
29
+ UPDATE_MANY_SUCCESS: 'form.update.many.success',
30
+ DELETE_SUCCESS: 'form.delete.success',
31
+ RESTORE_SUCCESS: 'form.restore.success',
32
+ NOT_FOUND: 'form.not.found',
33
+ SCHEMA_SUCCESS: 'form.schema.success',
34
+ PUBLISH_SUCCESS: 'form.publish.success',
35
+ UNPUBLISH_SUCCESS: 'form.unpublish.success',
36
+ DUPLICATE_SUCCESS: 'form.duplicate.success',
37
+ NOT_PUBLIC: 'form.not.public',
38
+ AUTH_REQUIRED: 'form.auth.required',
39
+ ACCESS_DENIED: 'form.access.denied',
40
+ INVALID_ACCESS_TYPE: 'form.invalid.access.type',
41
+ PERMISSION_CHECK_FAILED: 'form.permission.check.failed'
42
+ };
43
+ const FORM_RESULT_MESSAGES = {
44
+ CREATE_SUCCESS: 'form.result.create.success',
45
+ CREATE_MANY_SUCCESS: 'form.result.create.many.success',
46
+ GET_SUCCESS: 'form.result.get.success',
47
+ GET_ALL_SUCCESS: 'form.result.get.all.success',
48
+ UPDATE_SUCCESS: 'form.result.update.success',
49
+ UPDATE_MANY_SUCCESS: 'form.result.update.many.success',
50
+ DELETE_SUCCESS: 'form.result.delete.success',
51
+ RESTORE_SUCCESS: 'form.result.restore.success',
52
+ NOT_FOUND: 'form.result.not.found',
53
+ SUBMIT_SUCCESS: 'form.result.submit.success',
54
+ EXPORT_SUCCESS: 'form.result.export.success',
55
+ STATS_SUCCESS: 'form.result.stats.success',
56
+ HAS_SUBMITTED_SUCCESS: 'form.result.has.submitted.success',
57
+ HAS_NOT_SUBMITTED_SUCCESS: 'form.result.has.not.submitted.success'
58
+ };
59
+ const FORM_BUILDER_MODULE_MESSAGES = {
60
+ FORM: FORM_MESSAGES,
61
+ FORM_RESULT: FORM_RESULT_MESSAGES
62
+ };
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "FormResultController", {
9
9
  }
10
10
  });
11
11
  const _classes = require("@flusys/nestjs-shared/classes");
12
+ const _config = require("../config");
12
13
  const _decorators = require("@flusys/nestjs-shared/decorators");
13
14
  const _guards = require("@flusys/nestjs-shared/guards");
14
15
  const _interfaces = require("@flusys/nestjs-shared/interfaces");
@@ -44,6 +45,7 @@ function _ts_param(paramIndex, decorator) {
44
45
  };
45
46
  }
46
47
  let FormResultController = class FormResultController extends (0, _classes.createApiController)(_dtos.CreateFormResultDto, _dtos.UpdateFormResultDto, _dtos.FormResultResponseDto, {
48
+ entityName: 'formResult',
47
49
  security: {
48
50
  insert: {
49
51
  level: 'permission',
@@ -125,6 +127,7 @@ let FormResultController = class FormResultController extends (0, _classes.creat
125
127
  return {
126
128
  success: true,
127
129
  message: 'Form results retrieved successfully',
130
+ messageKey: _config.FORM_RESULT_MESSAGES.GET_ALL_SUCCESS,
128
131
  data: result.data,
129
132
  meta: {
130
133
  total: result.total,
@@ -142,6 +145,7 @@ let FormResultController = class FormResultController extends (0, _classes.creat
142
145
  return {
143
146
  success: true,
144
147
  message: hasSubmitted ? 'User has submitted this form' : 'User has not submitted this form',
148
+ messageKey: hasSubmitted ? _config.FORM_RESULT_MESSAGES.HAS_SUBMITTED_SUCCESS : _config.FORM_RESULT_MESSAGES.HAS_NOT_SUBMITTED_SUCCESS,
145
149
  data: hasSubmitted
146
150
  };
147
151
  }
@@ -44,6 +44,7 @@ function _ts_param(paramIndex, decorator) {
44
44
  };
45
45
  }
46
46
  let FormController = class FormController extends (0, _classes.createApiController)(_dtos.CreateFormDto, _dtos.UpdateFormDto, _dtos.FormResponseDto, {
47
+ entityName: 'form',
47
48
  security: {
48
49
  insert: {
49
50
  level: 'permission',
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "FormBuilderDataSourceProvider", {
10
10
  });
11
11
  const _modules = require("@flusys/nestjs-shared/modules");
12
12
  const _common = require("@nestjs/common");
13
+ const _constants = require("@flusys/nestjs-shared/constants");
13
14
  const _core = require("@nestjs/core");
14
15
  const _express = require("express");
15
16
  const _formbuilderconfigservice = require("./form-builder-config.service");
@@ -109,7 +110,10 @@ let FormBuilderDataSourceProvider = class FormBuilderDataSourceProvider extends
109
110
  }
110
111
  const config = this.getDefaultDatabaseConfig();
111
112
  if (!config) {
112
- throw new Error('No database config available. Provide defaultDatabaseConfig in FormBuilderModule options.');
113
+ throw new _common.InternalServerErrorException({
114
+ message: 'No database config available. Provide defaultDatabaseConfig in FormBuilderModule options.',
115
+ messageKey: _constants.SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
116
+ });
113
117
  }
114
118
  const connectionPromise = this.createDataSourceFromConfig(config);
115
119
  FormBuilderDataSourceProvider.singleConnectionLock = connectionPromise;
@@ -142,7 +146,7 @@ let FormBuilderDataSourceProvider = class FormBuilderDataSourceProvider extends
142
146
  }
143
147
  }
144
148
  constructor(configService, request){
145
- super(FormBuilderDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), _define_property(this, "logger", void 0), this.configService = configService, this.logger = new _common.Logger(FormBuilderDataSourceProvider.name);
149
+ super(FormBuilderDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), this.configService = configService;
146
150
  }
147
151
  };
148
152
  _define_property(FormBuilderDataSourceProvider, "tenantConnections", new Map());
@@ -9,10 +9,14 @@ Object.defineProperty(exports, "FormResultService", {
9
9
  }
10
10
  });
11
11
  const _classes = require("@flusys/nestjs-shared/classes");
12
+ const _config = require("../config");
13
+ const _decorators = require("@flusys/nestjs-shared/decorators");
14
+ const _interfaces = require("@flusys/nestjs-shared/interfaces");
12
15
  const _modules = require("@flusys/nestjs-shared/modules");
13
16
  const _common = require("@nestjs/common");
14
17
  const _typeorm = require("typeorm");
15
18
  const _formbuilderconfigservice = require("./form-builder-config.service");
19
+ const _formresultdto = require("../dtos/form-result.dto");
16
20
  const _entities = require("../entities");
17
21
  const _formaccesstypeenum = require("../enums/form-access-type.enum");
18
22
  const _formbuilderdatasourceprovider = require("./form-builder-datasource.provider");
@@ -69,7 +73,10 @@ let FormResultService = class FormResultService extends _classes.RequestScopedAp
69
73
  }
70
74
  });
71
75
  if (!form) {
72
- throw new _common.NotFoundException('Form not found or inactive');
76
+ throw new _common.NotFoundException({
77
+ message: 'Form not found or inactive',
78
+ messageKey: _config.FORM_MESSAGES.NOT_FOUND
79
+ });
73
80
  }
74
81
  return form;
75
82
  }
@@ -83,21 +90,33 @@ let FormResultService = class FormResultService extends _classes.RequestScopedAp
83
90
  async validateSubmissionAccess(form, user, isPublic) {
84
91
  if (isPublic) {
85
92
  if (form.accessType !== _formaccesstypeenum.FormAccessType.PUBLIC) {
86
- throw new _common.UnauthorizedException('This form is not available for public submission');
93
+ throw new _common.UnauthorizedException({
94
+ message: 'This form is not available for public submission',
95
+ messageKey: _config.FORM_MESSAGES.NOT_PUBLIC
96
+ });
87
97
  }
88
98
  return;
89
99
  }
90
100
  if (form.accessType === _formaccesstypeenum.FormAccessType.PUBLIC) return;
91
101
  if (!user) {
92
- throw new _common.UnauthorizedException('Authentication required to submit this form');
102
+ throw new _common.UnauthorizedException({
103
+ message: 'Authentication required to submit this form',
104
+ messageKey: _config.FORM_MESSAGES.AUTH_REQUIRED
105
+ });
93
106
  }
94
107
  if (form.accessType === _formaccesstypeenum.FormAccessType.ACTION_GROUP && form.actionGroups?.length) {
95
- const hasPermission = await (0, _permissionutils.validateUserPermissions)(user, form.actionGroups, this.cacheManager, this.formBuilderConfig.isCompanyFeatureEnabled(), this.logger, 'submitting form', form.id);
108
+ const hasPermission = await (0, _permissionutils.validateUserPermissions)(user, form.actionGroups, this.cacheManager, this.formBuilderConfig.isCompanyFeatureEnabled());
96
109
  if (!hasPermission) {
97
- throw new _common.ForbiddenException('You do not have permission to submit this form');
110
+ throw new _common.ForbiddenException({
111
+ message: 'You do not have permission to submit this form',
112
+ messageKey: _config.FORM_MESSAGES.ACCESS_DENIED
113
+ });
98
114
  }
99
115
  } else if (form.accessType !== _formaccesstypeenum.FormAccessType.AUTHENTICATED) {
100
- throw new _common.BadRequestException('Invalid form access type');
116
+ throw new _common.BadRequestException({
117
+ message: 'Invalid form access type',
118
+ messageKey: _config.FORM_MESSAGES.INVALID_ACCESS_TYPE
119
+ });
101
120
  }
102
121
  }
103
122
  buildUserDraftWhere(formId, userId) {
@@ -230,7 +249,10 @@ let FormResultService = class FormResultService extends _classes.RequestScopedAp
230
249
  }
231
250
  });
232
251
  if (!draft) {
233
- throw new _common.NotFoundException('Draft not found');
252
+ throw new _common.NotFoundException({
253
+ message: 'Draft not found',
254
+ messageKey: _config.FORM_RESULT_MESSAGES.NOT_FOUND
255
+ });
234
256
  }
235
257
  const form = await this.getActiveForm(dto.formId);
236
258
  const isDraft = dto.isDraft ?? false;
@@ -273,9 +295,35 @@ let FormResultService = class FormResultService extends _classes.RequestScopedAp
273
295
  return count > 0;
274
296
  }
275
297
  constructor(cacheManager, utilsService, formBuilderConfig, dataSourceProvider){
276
- super('form_result', null, cacheManager, utilsService, FormResultService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "formBuilderConfig", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "formRepository", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider, this.formRepository = null;
298
+ super('form_result', null, cacheManager, utilsService, FormResultService.name, true, 'form-builder'), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "formBuilderConfig", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "formRepository", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider, this.formRepository = null;
277
299
  }
278
300
  };
301
+ _ts_decorate([
302
+ (0, _decorators.LogAction)({
303
+ action: 'formResult.submit',
304
+ module: 'form-builder'
305
+ }),
306
+ _ts_metadata("design:type", Function),
307
+ _ts_metadata("design:paramtypes", [
308
+ typeof _formresultdto.SubmitFormDto === "undefined" ? Object : _formresultdto.SubmitFormDto,
309
+ Object,
310
+ void 0
311
+ ]),
312
+ _ts_metadata("design:returntype", Promise)
313
+ ], FormResultService.prototype, "submitForm", null);
314
+ _ts_decorate([
315
+ (0, _decorators.LogAction)({
316
+ action: 'formResult.updateDraft',
317
+ module: 'form-builder'
318
+ }),
319
+ _ts_metadata("design:type", Function),
320
+ _ts_metadata("design:paramtypes", [
321
+ String,
322
+ typeof _formresultdto.SubmitFormDto === "undefined" ? Object : _formresultdto.SubmitFormDto,
323
+ typeof _interfaces.ILoggedUserInfo === "undefined" ? Object : _interfaces.ILoggedUserInfo
324
+ ]),
325
+ _ts_metadata("design:returntype", Promise)
326
+ ], FormResultService.prototype, "updateDraft", null);
279
327
  FormResultService = _ts_decorate([
280
328
  (0, _common.Injectable)({
281
329
  scope: _common.Scope.REQUEST
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "FormService", {
9
9
  }
10
10
  });
11
11
  const _classes = require("@flusys/nestjs-shared/classes");
12
+ const _config = require("../config");
12
13
  const _modules = require("@flusys/nestjs-shared/modules");
13
14
  const _utils = require("@flusys/nestjs-shared/utils");
14
15
  const _common = require("@nestjs/common");
@@ -65,14 +66,16 @@ let FormService = class FormService extends _classes.RequestScopedApiService {
65
66
  }
66
67
  });
67
68
  if (!dbData) {
68
- throw new _common.NotFoundException('Form not found');
69
+ throw new _common.NotFoundException({
70
+ message: 'Form not found',
71
+ messageKey: _config.FORM_MESSAGES.NOT_FOUND
72
+ });
69
73
  }
70
74
  form = dbData;
71
75
  isUpdate = true;
72
76
  // Auto-increment schema version if schema changed
73
77
  if (dto.schema && JSON.stringify(dto.schema) !== JSON.stringify(form.schema)) {
74
78
  dto.schemaVersion = form.schemaVersion + 1;
75
- this.logger.log(`Schema changed, incrementing version from ${form.schemaVersion} to ${dto.schemaVersion}`);
76
79
  }
77
80
  }
78
81
  // Merge DTO into form
@@ -170,7 +173,7 @@ let FormService = class FormService extends _classes.RequestScopedApiService {
170
173
  }
171
174
  async findPublicActiveForm(where) {
172
175
  await this.ensureRepositoryInitialized();
173
- return this.repository.findOne({
176
+ return await this.repository.findOne({
174
177
  where: {
175
178
  ...where,
176
179
  accessType: _formaccesstypeenum.FormAccessType.PUBLIC,
@@ -185,7 +188,10 @@ let FormService = class FormService extends _classes.RequestScopedApiService {
185
188
  id: formId
186
189
  });
187
190
  if (!form) {
188
- throw new _common.NotFoundException('Form not found or not available for public access');
191
+ throw new _common.NotFoundException({
192
+ message: 'Form not found or not available for public access',
193
+ messageKey: _config.FORM_MESSAGES.NOT_PUBLIC
194
+ });
189
195
  }
190
196
  return this.toPublicForm(form);
191
197
  }
@@ -199,7 +205,10 @@ let FormService = class FormService extends _classes.RequestScopedApiService {
199
205
  }
200
206
  });
201
207
  if (!form) {
202
- throw new _common.NotFoundException('Form not found or inactive');
208
+ throw new _common.NotFoundException({
209
+ message: 'Form not found or inactive',
210
+ messageKey: _config.FORM_MESSAGES.NOT_FOUND
211
+ });
203
212
  }
204
213
  // Access validation based on accessType
205
214
  if (form.accessType === _formaccesstypeenum.FormAccessType.PUBLIC) {
@@ -207,12 +216,18 @@ let FormService = class FormService extends _classes.RequestScopedApiService {
207
216
  }
208
217
  // All non-public forms require authentication
209
218
  if (!user) {
210
- throw new _common.UnauthorizedException('Authentication required to submit this form');
219
+ throw new _common.UnauthorizedException({
220
+ message: 'Authentication required to submit this form',
221
+ messageKey: _config.FORM_MESSAGES.AUTH_REQUIRED
222
+ });
211
223
  }
212
224
  if (form.accessType === _formaccesstypeenum.FormAccessType.AUTHENTICATED || form.accessType === _formaccesstypeenum.FormAccessType.ACTION_GROUP) {
213
225
  return form; // Permission check for ACTION_GROUP is handled by the controller/guard
214
226
  }
215
- throw new _common.BadRequestException('Invalid access type');
227
+ throw new _common.BadRequestException({
228
+ message: 'Invalid access type',
229
+ messageKey: _config.FORM_MESSAGES.INVALID_ACCESS_TYPE
230
+ });
216
231
  }
217
232
  async getBySlug(slug) {
218
233
  await this.ensureRepositoryInitialized();
@@ -250,7 +265,10 @@ let FormService = class FormService extends _classes.RequestScopedApiService {
250
265
  ]
251
266
  });
252
267
  if (!form) {
253
- throw new _common.NotFoundException('Form not found');
268
+ throw new _common.NotFoundException({
269
+ message: 'Form not found',
270
+ messageKey: _config.FORM_MESSAGES.NOT_FOUND
271
+ });
254
272
  }
255
273
  return {
256
274
  id: form.id,
@@ -265,15 +283,18 @@ let FormService = class FormService extends _classes.RequestScopedApiService {
265
283
  const form = await this.getFormForSubmission(formId, user);
266
284
  // For action_group access, check permissions from cache
267
285
  if (form.accessType === _formaccesstypeenum.FormAccessType.ACTION_GROUP && form.actionGroups?.length) {
268
- const hasPermission = await (0, _permissionutils.validateUserPermissions)(user, form.actionGroups, this.cacheManager, this.formBuilderConfig.isCompanyFeatureEnabled(), this.logger, 'accessing form', form.id);
286
+ const hasPermission = await (0, _permissionutils.validateUserPermissions)(user, form.actionGroups, this.cacheManager, this.formBuilderConfig.isCompanyFeatureEnabled());
269
287
  if (!hasPermission) {
270
- throw new _common.ForbiddenException('You do not have permission to access this form');
288
+ throw new _common.ForbiddenException({
289
+ message: 'You do not have permission to access this form',
290
+ messageKey: _config.FORM_MESSAGES.ACCESS_DENIED
291
+ });
271
292
  }
272
293
  }
273
294
  return this.toPublicForm(form);
274
295
  }
275
296
  constructor(cacheManager, utilsService, formBuilderConfig, dataSourceProvider){
276
- super('form', null, cacheManager, utilsService, FormService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "formBuilderConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider;
297
+ super('form', null, cacheManager, utilsService, FormService.name, true, 'form-builder'), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "formBuilderConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider;
277
298
  }
278
299
  };
279
300
  FormService = _ts_decorate([
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "validateUserPermissions", {
8
8
  return validateUserPermissions;
9
9
  }
10
10
  });
11
+ const _config = require("../config");
11
12
  const _common = require("@nestjs/common");
12
13
  const CACHE_PREFIX = 'permissions';
13
14
  /**
@@ -15,38 +16,29 @@ const CACHE_PREFIX = 'permissions';
15
16
  * Uses same cache key format as PermissionGuard in nestjs-shared
16
17
  */ function buildPermissionCacheKey(user, enableCompanyFeature) {
17
18
  if (enableCompanyFeature && user.companyId) {
18
- // Company-based permissions
19
19
  return `${CACHE_PREFIX}:company:${user.companyId}:branch:${user.branchId || 'null'}:user:${user.id}`;
20
20
  }
21
- // User-based permissions
22
21
  return `${CACHE_PREFIX}:user:${user.id}`;
23
22
  }
24
23
  /**
25
24
  * Get user permissions from cache
26
25
  * Fail-closed: if cache fails, deny access rather than allowing potentially unauthorized access
27
- */ async function getUserPermissionsFromCache(user, cacheManager, enableCompanyFeature, logger) {
26
+ */ async function getUserPermissionsFromCache(user, cacheManager, enableCompanyFeature) {
28
27
  const cacheKey = buildPermissionCacheKey(user, enableCompanyFeature);
29
28
  try {
30
29
  const permissions = await cacheManager.get(cacheKey);
31
- if (!permissions) {
32
- logger.warn(`No permissions found in cache for user ${user.id} (key: ${cacheKey})`);
33
- return [];
34
- }
35
- return permissions;
36
- } catch (error) {
37
- // Fail-closed: if cache fails, deny access rather than allowing potentially unauthorized access
38
- logger.error(`Failed to get permissions from cache for user ${user.id}: ${error}`);
39
- throw new _common.ForbiddenException('Unable to verify permissions. Please try again.');
30
+ return permissions ?? [];
31
+ } catch {
32
+ throw new _common.ForbiddenException({
33
+ message: 'Unable to verify permissions. Please try again.',
34
+ messageKey: _config.FORM_MESSAGES.PERMISSION_CHECK_FAILED
35
+ });
40
36
  }
41
37
  }
42
- async function validateUserPermissions(user, requiredPermissions, cacheManager, enableCompanyFeature, logger, context, resourceId) {
38
+ async function validateUserPermissions(user, requiredPermissions, cacheManager, enableCompanyFeature) {
43
39
  if (!requiredPermissions.length) {
44
- return true; // No permissions required
40
+ return true;
45
41
  }
46
- const userPermissions = await getUserPermissionsFromCache(user, cacheManager, enableCompanyFeature, logger);
47
- const hasPermission = requiredPermissions.some((perm)=>userPermissions.includes(perm));
48
- if (!hasPermission) {
49
- logger.warn(`Permission denied for user ${user.id} ${context} ${resourceId}. ` + `Required: [${requiredPermissions.join(', ')}], User has: [${userPermissions.join(', ')}]`);
50
- }
51
- return hasPermission;
42
+ const userPermissions = await getUserPermissionsFromCache(user, cacheManager, enableCompanyFeature);
43
+ return requiredPermissions.some((perm)=>userPermissions.includes(perm));
52
44
  }
package/config/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from './form-builder.constants';
2
+ export * from './message-keys';
@@ -0,0 +1,74 @@
1
+ export declare const FORM_MESSAGES: {
2
+ readonly CREATE_SUCCESS: "form.create.success";
3
+ readonly CREATE_MANY_SUCCESS: "form.create.many.success";
4
+ readonly GET_SUCCESS: "form.get.success";
5
+ readonly GET_ALL_SUCCESS: "form.get.all.success";
6
+ readonly UPDATE_SUCCESS: "form.update.success";
7
+ readonly UPDATE_MANY_SUCCESS: "form.update.many.success";
8
+ readonly DELETE_SUCCESS: "form.delete.success";
9
+ readonly RESTORE_SUCCESS: "form.restore.success";
10
+ readonly NOT_FOUND: "form.not.found";
11
+ readonly SCHEMA_SUCCESS: "form.schema.success";
12
+ readonly PUBLISH_SUCCESS: "form.publish.success";
13
+ readonly UNPUBLISH_SUCCESS: "form.unpublish.success";
14
+ readonly DUPLICATE_SUCCESS: "form.duplicate.success";
15
+ readonly NOT_PUBLIC: "form.not.public";
16
+ readonly AUTH_REQUIRED: "form.auth.required";
17
+ readonly ACCESS_DENIED: "form.access.denied";
18
+ readonly INVALID_ACCESS_TYPE: "form.invalid.access.type";
19
+ readonly PERMISSION_CHECK_FAILED: "form.permission.check.failed";
20
+ };
21
+ export declare const FORM_RESULT_MESSAGES: {
22
+ readonly CREATE_SUCCESS: "form.result.create.success";
23
+ readonly CREATE_MANY_SUCCESS: "form.result.create.many.success";
24
+ readonly GET_SUCCESS: "form.result.get.success";
25
+ readonly GET_ALL_SUCCESS: "form.result.get.all.success";
26
+ readonly UPDATE_SUCCESS: "form.result.update.success";
27
+ readonly UPDATE_MANY_SUCCESS: "form.result.update.many.success";
28
+ readonly DELETE_SUCCESS: "form.result.delete.success";
29
+ readonly RESTORE_SUCCESS: "form.result.restore.success";
30
+ readonly NOT_FOUND: "form.result.not.found";
31
+ readonly SUBMIT_SUCCESS: "form.result.submit.success";
32
+ readonly EXPORT_SUCCESS: "form.result.export.success";
33
+ readonly STATS_SUCCESS: "form.result.stats.success";
34
+ readonly HAS_SUBMITTED_SUCCESS: "form.result.has.submitted.success";
35
+ readonly HAS_NOT_SUBMITTED_SUCCESS: "form.result.has.not.submitted.success";
36
+ };
37
+ export declare const FORM_BUILDER_MODULE_MESSAGES: {
38
+ readonly FORM: {
39
+ readonly CREATE_SUCCESS: "form.create.success";
40
+ readonly CREATE_MANY_SUCCESS: "form.create.many.success";
41
+ readonly GET_SUCCESS: "form.get.success";
42
+ readonly GET_ALL_SUCCESS: "form.get.all.success";
43
+ readonly UPDATE_SUCCESS: "form.update.success";
44
+ readonly UPDATE_MANY_SUCCESS: "form.update.many.success";
45
+ readonly DELETE_SUCCESS: "form.delete.success";
46
+ readonly RESTORE_SUCCESS: "form.restore.success";
47
+ readonly NOT_FOUND: "form.not.found";
48
+ readonly SCHEMA_SUCCESS: "form.schema.success";
49
+ readonly PUBLISH_SUCCESS: "form.publish.success";
50
+ readonly UNPUBLISH_SUCCESS: "form.unpublish.success";
51
+ readonly DUPLICATE_SUCCESS: "form.duplicate.success";
52
+ readonly NOT_PUBLIC: "form.not.public";
53
+ readonly AUTH_REQUIRED: "form.auth.required";
54
+ readonly ACCESS_DENIED: "form.access.denied";
55
+ readonly INVALID_ACCESS_TYPE: "form.invalid.access.type";
56
+ readonly PERMISSION_CHECK_FAILED: "form.permission.check.failed";
57
+ };
58
+ readonly FORM_RESULT: {
59
+ readonly CREATE_SUCCESS: "form.result.create.success";
60
+ readonly CREATE_MANY_SUCCESS: "form.result.create.many.success";
61
+ readonly GET_SUCCESS: "form.result.get.success";
62
+ readonly GET_ALL_SUCCESS: "form.result.get.all.success";
63
+ readonly UPDATE_SUCCESS: "form.result.update.success";
64
+ readonly UPDATE_MANY_SUCCESS: "form.result.update.many.success";
65
+ readonly DELETE_SUCCESS: "form.result.delete.success";
66
+ readonly RESTORE_SUCCESS: "form.result.restore.success";
67
+ readonly NOT_FOUND: "form.result.not.found";
68
+ readonly SUBMIT_SUCCESS: "form.result.submit.success";
69
+ readonly EXPORT_SUCCESS: "form.result.export.success";
70
+ readonly STATS_SUCCESS: "form.result.stats.success";
71
+ readonly HAS_SUBMITTED_SUCCESS: "form.result.has.submitted.success";
72
+ readonly HAS_NOT_SUBMITTED_SUCCESS: "form.result.has.not.submitted.success";
73
+ };
74
+ };
@@ -1 +1,2 @@
1
1
  export * from './form-builder.constants';
2
+ export * from './message-keys';
@@ -0,0 +1,42 @@
1
+ // ==================== FORM BUILDER MODULE MESSAGE KEYS ====================
2
+ export const FORM_MESSAGES = {
3
+ CREATE_SUCCESS: 'form.create.success',
4
+ CREATE_MANY_SUCCESS: 'form.create.many.success',
5
+ GET_SUCCESS: 'form.get.success',
6
+ GET_ALL_SUCCESS: 'form.get.all.success',
7
+ UPDATE_SUCCESS: 'form.update.success',
8
+ UPDATE_MANY_SUCCESS: 'form.update.many.success',
9
+ DELETE_SUCCESS: 'form.delete.success',
10
+ RESTORE_SUCCESS: 'form.restore.success',
11
+ NOT_FOUND: 'form.not.found',
12
+ SCHEMA_SUCCESS: 'form.schema.success',
13
+ PUBLISH_SUCCESS: 'form.publish.success',
14
+ UNPUBLISH_SUCCESS: 'form.unpublish.success',
15
+ DUPLICATE_SUCCESS: 'form.duplicate.success',
16
+ NOT_PUBLIC: 'form.not.public',
17
+ AUTH_REQUIRED: 'form.auth.required',
18
+ ACCESS_DENIED: 'form.access.denied',
19
+ INVALID_ACCESS_TYPE: 'form.invalid.access.type',
20
+ PERMISSION_CHECK_FAILED: 'form.permission.check.failed'
21
+ };
22
+ export const FORM_RESULT_MESSAGES = {
23
+ CREATE_SUCCESS: 'form.result.create.success',
24
+ CREATE_MANY_SUCCESS: 'form.result.create.many.success',
25
+ GET_SUCCESS: 'form.result.get.success',
26
+ GET_ALL_SUCCESS: 'form.result.get.all.success',
27
+ UPDATE_SUCCESS: 'form.result.update.success',
28
+ UPDATE_MANY_SUCCESS: 'form.result.update.many.success',
29
+ DELETE_SUCCESS: 'form.result.delete.success',
30
+ RESTORE_SUCCESS: 'form.result.restore.success',
31
+ NOT_FOUND: 'form.result.not.found',
32
+ SUBMIT_SUCCESS: 'form.result.submit.success',
33
+ EXPORT_SUCCESS: 'form.result.export.success',
34
+ STATS_SUCCESS: 'form.result.stats.success',
35
+ HAS_SUBMITTED_SUCCESS: 'form.result.has.submitted.success',
36
+ HAS_NOT_SUBMITTED_SUCCESS: 'form.result.has.not.submitted.success'
37
+ };
38
+ // Aggregated export for backward compatibility
39
+ export const FORM_BUILDER_MODULE_MESSAGES = {
40
+ FORM: FORM_MESSAGES,
41
+ FORM_RESULT: FORM_RESULT_MESSAGES
42
+ };
@@ -26,6 +26,7 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { createApiController, FORM_RESULT_PERMISSIONS } from '@flusys/nestjs-shared/classes';
29
+ import { FORM_RESULT_MESSAGES } from '../config';
29
30
  import { CurrentUser, Public, RequirePermission } from '@flusys/nestjs-shared/decorators';
30
31
  import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
31
32
  import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
@@ -34,6 +35,7 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
34
35
  import { CreateFormResultDto, FormResultResponseDto, GetMyDraftDto, GetResultsByFormDto, SubmitFormDto, UpdateDraftDto, UpdateFormResultDto } from '../dtos';
35
36
  import { FormResultService } from '../services/form-result.service';
36
37
  export class FormResultController extends createApiController(CreateFormResultDto, UpdateFormResultDto, FormResultResponseDto, {
38
+ entityName: 'formResult',
37
39
  security: {
38
40
  insert: {
39
41
  level: 'permission',
@@ -115,6 +117,7 @@ export class FormResultController extends createApiController(CreateFormResultDt
115
117
  return {
116
118
  success: true,
117
119
  message: 'Form results retrieved successfully',
120
+ messageKey: FORM_RESULT_MESSAGES.GET_ALL_SUCCESS,
118
121
  data: result.data,
119
122
  meta: {
120
123
  total: result.total,
@@ -132,6 +135,7 @@ export class FormResultController extends createApiController(CreateFormResultDt
132
135
  return {
133
136
  success: true,
134
137
  message: hasSubmitted ? 'User has submitted this form' : 'User has not submitted this form',
138
+ messageKey: hasSubmitted ? FORM_RESULT_MESSAGES.HAS_SUBMITTED_SUCCESS : FORM_RESULT_MESSAGES.HAS_NOT_SUBMITTED_SUCCESS,
135
139
  data: hasSubmitted
136
140
  };
137
141
  }
@@ -34,6 +34,7 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
34
34
  import { CreateFormDto, FormResponseDto, UpdateFormDto } from '../dtos';
35
35
  import { FormService } from '../services/form.service';
36
36
  export class FormController extends createApiController(CreateFormDto, UpdateFormDto, FormResponseDto, {
37
+ entityName: 'form',
37
38
  security: {
38
39
  insert: {
39
40
  level: 'permission',
@@ -26,7 +26,8 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
29
- import { Inject, Injectable, Logger, Optional, Scope } from '@nestjs/common';
29
+ import { Inject, Injectable, InternalServerErrorException, Optional, Scope } from '@nestjs/common';
30
+ import { SYSTEM_MESSAGES } from '@flusys/nestjs-shared/constants';
30
31
  import { REQUEST } from '@nestjs/core';
31
32
  import { Request } from 'express';
32
33
  import { FormBuilderConfigService } from './form-builder-config.service';
@@ -58,7 +59,10 @@ export class FormBuilderDataSourceProvider extends MultiTenantDataSourceService
58
59
  }
59
60
  const config = this.getDefaultDatabaseConfig();
60
61
  if (!config) {
61
- throw new Error('No database config available. Provide defaultDatabaseConfig in FormBuilderModule options.');
62
+ throw new InternalServerErrorException({
63
+ message: 'No database config available. Provide defaultDatabaseConfig in FormBuilderModule options.',
64
+ messageKey: SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
65
+ });
62
66
  }
63
67
  const connectionPromise = this.createDataSourceFromConfig(config);
64
68
  FormBuilderDataSourceProvider.singleConnectionLock = connectionPromise;
@@ -91,7 +95,7 @@ export class FormBuilderDataSourceProvider extends MultiTenantDataSourceService
91
95
  }
92
96
  }
93
97
  constructor(configService, request){
94
- super(FormBuilderDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), _define_property(this, "logger", void 0), this.configService = configService, this.logger = new Logger(FormBuilderDataSourceProvider.name);
98
+ super(FormBuilderDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), this.configService = configService;
95
99
  }
96
100
  }
97
101
  _define_property(FormBuilderDataSourceProvider, "tenantConnections", new Map());
@@ -26,10 +26,14 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { RequestScopedApiService, HybridCache } from '@flusys/nestjs-shared/classes';
29
+ import { FORM_MESSAGES, FORM_RESULT_MESSAGES } from '../config';
30
+ import { LogAction } from '@flusys/nestjs-shared/decorators';
31
+ import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
29
32
  import { UtilsService } from '@flusys/nestjs-shared/modules';
30
33
  import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException, Scope, UnauthorizedException } from '@nestjs/common';
31
34
  import { IsNull } from 'typeorm';
32
35
  import { FormBuilderConfigService } from './form-builder-config.service';
36
+ import { SubmitFormDto } from '../dtos/form-result.dto';
33
37
  import { Form, FormResult, FormWithCompany } from '../entities';
34
38
  import { FormAccessType } from '../enums/form-access-type.enum';
35
39
  import { FormBuilderDataSourceProvider } from './form-builder-datasource.provider';
@@ -59,7 +63,10 @@ export class FormResultService extends RequestScopedApiService {
59
63
  }
60
64
  });
61
65
  if (!form) {
62
- throw new NotFoundException('Form not found or inactive');
66
+ throw new NotFoundException({
67
+ message: 'Form not found or inactive',
68
+ messageKey: FORM_MESSAGES.NOT_FOUND
69
+ });
63
70
  }
64
71
  return form;
65
72
  }
@@ -73,21 +80,33 @@ export class FormResultService extends RequestScopedApiService {
73
80
  async validateSubmissionAccess(form, user, isPublic) {
74
81
  if (isPublic) {
75
82
  if (form.accessType !== FormAccessType.PUBLIC) {
76
- throw new UnauthorizedException('This form is not available for public submission');
83
+ throw new UnauthorizedException({
84
+ message: 'This form is not available for public submission',
85
+ messageKey: FORM_MESSAGES.NOT_PUBLIC
86
+ });
77
87
  }
78
88
  return;
79
89
  }
80
90
  if (form.accessType === FormAccessType.PUBLIC) return;
81
91
  if (!user) {
82
- throw new UnauthorizedException('Authentication required to submit this form');
92
+ throw new UnauthorizedException({
93
+ message: 'Authentication required to submit this form',
94
+ messageKey: FORM_MESSAGES.AUTH_REQUIRED
95
+ });
83
96
  }
84
97
  if (form.accessType === FormAccessType.ACTION_GROUP && form.actionGroups?.length) {
85
- const hasPermission = await validateUserPermissions(user, form.actionGroups, this.cacheManager, this.formBuilderConfig.isCompanyFeatureEnabled(), this.logger, 'submitting form', form.id);
98
+ const hasPermission = await validateUserPermissions(user, form.actionGroups, this.cacheManager, this.formBuilderConfig.isCompanyFeatureEnabled());
86
99
  if (!hasPermission) {
87
- throw new ForbiddenException('You do not have permission to submit this form');
100
+ throw new ForbiddenException({
101
+ message: 'You do not have permission to submit this form',
102
+ messageKey: FORM_MESSAGES.ACCESS_DENIED
103
+ });
88
104
  }
89
105
  } else if (form.accessType !== FormAccessType.AUTHENTICATED) {
90
- throw new BadRequestException('Invalid form access type');
106
+ throw new BadRequestException({
107
+ message: 'Invalid form access type',
108
+ messageKey: FORM_MESSAGES.INVALID_ACCESS_TYPE
109
+ });
91
110
  }
92
111
  }
93
112
  buildUserDraftWhere(formId, userId) {
@@ -220,7 +239,10 @@ export class FormResultService extends RequestScopedApiService {
220
239
  }
221
240
  });
222
241
  if (!draft) {
223
- throw new NotFoundException('Draft not found');
242
+ throw new NotFoundException({
243
+ message: 'Draft not found',
244
+ messageKey: FORM_RESULT_MESSAGES.NOT_FOUND
245
+ });
224
246
  }
225
247
  const form = await this.getActiveForm(dto.formId);
226
248
  const isDraft = dto.isDraft ?? false;
@@ -263,9 +285,35 @@ export class FormResultService extends RequestScopedApiService {
263
285
  return count > 0;
264
286
  }
265
287
  constructor(cacheManager, utilsService, formBuilderConfig, dataSourceProvider){
266
- super('form_result', null, cacheManager, utilsService, FormResultService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "formBuilderConfig", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "formRepository", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider, this.formRepository = null;
288
+ super('form_result', null, cacheManager, utilsService, FormResultService.name, true, 'form-builder'), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "formBuilderConfig", void 0), _define_property(this, "dataSourceProvider", void 0), _define_property(this, "formRepository", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider, this.formRepository = null;
267
289
  }
268
290
  }
291
+ _ts_decorate([
292
+ LogAction({
293
+ action: 'formResult.submit',
294
+ module: 'form-builder'
295
+ }),
296
+ _ts_metadata("design:type", Function),
297
+ _ts_metadata("design:paramtypes", [
298
+ typeof SubmitFormDto === "undefined" ? Object : SubmitFormDto,
299
+ Object,
300
+ void 0
301
+ ]),
302
+ _ts_metadata("design:returntype", Promise)
303
+ ], FormResultService.prototype, "submitForm", null);
304
+ _ts_decorate([
305
+ LogAction({
306
+ action: 'formResult.updateDraft',
307
+ module: 'form-builder'
308
+ }),
309
+ _ts_metadata("design:type", Function),
310
+ _ts_metadata("design:paramtypes", [
311
+ String,
312
+ typeof SubmitFormDto === "undefined" ? Object : SubmitFormDto,
313
+ typeof ILoggedUserInfo === "undefined" ? Object : ILoggedUserInfo
314
+ ]),
315
+ _ts_metadata("design:returntype", Promise)
316
+ ], FormResultService.prototype, "updateDraft", null);
269
317
  FormResultService = _ts_decorate([
270
318
  Injectable({
271
319
  scope: Scope.REQUEST
@@ -26,6 +26,7 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { RequestScopedApiService, HybridCache } from '@flusys/nestjs-shared/classes';
29
+ import { FORM_MESSAGES } from '../config';
29
30
  import { UtilsService } from '@flusys/nestjs-shared/modules';
30
31
  import { applyCompanyFilter } from '@flusys/nestjs-shared/utils';
31
32
  import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException, Scope, UnauthorizedException } from '@nestjs/common';
@@ -55,14 +56,16 @@ export class FormService extends RequestScopedApiService {
55
56
  }
56
57
  });
57
58
  if (!dbData) {
58
- throw new NotFoundException('Form not found');
59
+ throw new NotFoundException({
60
+ message: 'Form not found',
61
+ messageKey: FORM_MESSAGES.NOT_FOUND
62
+ });
59
63
  }
60
64
  form = dbData;
61
65
  isUpdate = true;
62
66
  // Auto-increment schema version if schema changed
63
67
  if (dto.schema && JSON.stringify(dto.schema) !== JSON.stringify(form.schema)) {
64
68
  dto.schemaVersion = form.schemaVersion + 1;
65
- this.logger.log(`Schema changed, incrementing version from ${form.schemaVersion} to ${dto.schemaVersion}`);
66
69
  }
67
70
  }
68
71
  // Merge DTO into form
@@ -160,7 +163,7 @@ export class FormService extends RequestScopedApiService {
160
163
  }
161
164
  async findPublicActiveForm(where) {
162
165
  await this.ensureRepositoryInitialized();
163
- return this.repository.findOne({
166
+ return await this.repository.findOne({
164
167
  where: {
165
168
  ...where,
166
169
  accessType: FormAccessType.PUBLIC,
@@ -175,7 +178,10 @@ export class FormService extends RequestScopedApiService {
175
178
  id: formId
176
179
  });
177
180
  if (!form) {
178
- throw new NotFoundException('Form not found or not available for public access');
181
+ throw new NotFoundException({
182
+ message: 'Form not found or not available for public access',
183
+ messageKey: FORM_MESSAGES.NOT_PUBLIC
184
+ });
179
185
  }
180
186
  return this.toPublicForm(form);
181
187
  }
@@ -189,7 +195,10 @@ export class FormService extends RequestScopedApiService {
189
195
  }
190
196
  });
191
197
  if (!form) {
192
- throw new NotFoundException('Form not found or inactive');
198
+ throw new NotFoundException({
199
+ message: 'Form not found or inactive',
200
+ messageKey: FORM_MESSAGES.NOT_FOUND
201
+ });
193
202
  }
194
203
  // Access validation based on accessType
195
204
  if (form.accessType === FormAccessType.PUBLIC) {
@@ -197,12 +206,18 @@ export class FormService extends RequestScopedApiService {
197
206
  }
198
207
  // All non-public forms require authentication
199
208
  if (!user) {
200
- throw new UnauthorizedException('Authentication required to submit this form');
209
+ throw new UnauthorizedException({
210
+ message: 'Authentication required to submit this form',
211
+ messageKey: FORM_MESSAGES.AUTH_REQUIRED
212
+ });
201
213
  }
202
214
  if (form.accessType === FormAccessType.AUTHENTICATED || form.accessType === FormAccessType.ACTION_GROUP) {
203
215
  return form; // Permission check for ACTION_GROUP is handled by the controller/guard
204
216
  }
205
- throw new BadRequestException('Invalid access type');
217
+ throw new BadRequestException({
218
+ message: 'Invalid access type',
219
+ messageKey: FORM_MESSAGES.INVALID_ACCESS_TYPE
220
+ });
206
221
  }
207
222
  async getBySlug(slug) {
208
223
  await this.ensureRepositoryInitialized();
@@ -240,7 +255,10 @@ export class FormService extends RequestScopedApiService {
240
255
  ]
241
256
  });
242
257
  if (!form) {
243
- throw new NotFoundException('Form not found');
258
+ throw new NotFoundException({
259
+ message: 'Form not found',
260
+ messageKey: FORM_MESSAGES.NOT_FOUND
261
+ });
244
262
  }
245
263
  return {
246
264
  id: form.id,
@@ -255,15 +273,18 @@ export class FormService extends RequestScopedApiService {
255
273
  const form = await this.getFormForSubmission(formId, user);
256
274
  // For action_group access, check permissions from cache
257
275
  if (form.accessType === FormAccessType.ACTION_GROUP && form.actionGroups?.length) {
258
- const hasPermission = await validateUserPermissions(user, form.actionGroups, this.cacheManager, this.formBuilderConfig.isCompanyFeatureEnabled(), this.logger, 'accessing form', form.id);
276
+ const hasPermission = await validateUserPermissions(user, form.actionGroups, this.cacheManager, this.formBuilderConfig.isCompanyFeatureEnabled());
259
277
  if (!hasPermission) {
260
- throw new ForbiddenException('You do not have permission to access this form');
278
+ throw new ForbiddenException({
279
+ message: 'You do not have permission to access this form',
280
+ messageKey: FORM_MESSAGES.ACCESS_DENIED
281
+ });
261
282
  }
262
283
  }
263
284
  return this.toPublicForm(form);
264
285
  }
265
286
  constructor(cacheManager, utilsService, formBuilderConfig, dataSourceProvider){
266
- super('form', null, cacheManager, utilsService, FormService.name, true), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "formBuilderConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider;
287
+ super('form', null, cacheManager, utilsService, FormService.name, true, 'form-builder'), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "formBuilderConfig", void 0), _define_property(this, "dataSourceProvider", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.formBuilderConfig = formBuilderConfig, this.dataSourceProvider = dataSourceProvider;
267
288
  }
268
289
  }
269
290
  FormService = _ts_decorate([
@@ -1,3 +1,4 @@
1
+ import { FORM_MESSAGES } from '../config';
1
2
  import { ForbiddenException } from '@nestjs/common';
2
3
  const CACHE_PREFIX = 'permissions';
3
4
  /**
@@ -5,50 +6,37 @@ const CACHE_PREFIX = 'permissions';
5
6
  * Uses same cache key format as PermissionGuard in nestjs-shared
6
7
  */ function buildPermissionCacheKey(user, enableCompanyFeature) {
7
8
  if (enableCompanyFeature && user.companyId) {
8
- // Company-based permissions
9
9
  return `${CACHE_PREFIX}:company:${user.companyId}:branch:${user.branchId || 'null'}:user:${user.id}`;
10
10
  }
11
- // User-based permissions
12
11
  return `${CACHE_PREFIX}:user:${user.id}`;
13
12
  }
14
13
  /**
15
14
  * Get user permissions from cache
16
15
  * Fail-closed: if cache fails, deny access rather than allowing potentially unauthorized access
17
- */ async function getUserPermissionsFromCache(user, cacheManager, enableCompanyFeature, logger) {
16
+ */ async function getUserPermissionsFromCache(user, cacheManager, enableCompanyFeature) {
18
17
  const cacheKey = buildPermissionCacheKey(user, enableCompanyFeature);
19
18
  try {
20
19
  const permissions = await cacheManager.get(cacheKey);
21
- if (!permissions) {
22
- logger.warn(`No permissions found in cache for user ${user.id} (key: ${cacheKey})`);
23
- return [];
24
- }
25
- return permissions;
26
- } catch (error) {
27
- // Fail-closed: if cache fails, deny access rather than allowing potentially unauthorized access
28
- logger.error(`Failed to get permissions from cache for user ${user.id}: ${error}`);
29
- throw new ForbiddenException('Unable to verify permissions. Please try again.');
20
+ return permissions ?? [];
21
+ } catch {
22
+ throw new ForbiddenException({
23
+ message: 'Unable to verify permissions. Please try again.',
24
+ messageKey: FORM_MESSAGES.PERMISSION_CHECK_FAILED
25
+ });
30
26
  }
31
27
  }
32
28
  /**
33
29
  * Validate user has at least one of the required permissions
34
- * Logs audit warning on permission denial
35
30
  *
36
31
  * @param user - The logged-in user
37
32
  * @param requiredPermissions - Array of required permission strings (user needs at least one)
38
33
  * @param cacheManager - Cache manager instance
39
34
  * @param enableCompanyFeature - Whether company feature is enabled
40
- * @param logger - Logger instance for audit logging
41
- * @param context - Context string for audit logging (e.g., "accessing form", "submitting form")
42
- * @param resourceId - Resource ID for audit logging
43
35
  * @returns true if user has permission, false otherwise
44
- */ export async function validateUserPermissions(user, requiredPermissions, cacheManager, enableCompanyFeature, logger, context, resourceId) {
36
+ */ export async function validateUserPermissions(user, requiredPermissions, cacheManager, enableCompanyFeature) {
45
37
  if (!requiredPermissions.length) {
46
- return true; // No permissions required
38
+ return true;
47
39
  }
48
- const userPermissions = await getUserPermissionsFromCache(user, cacheManager, enableCompanyFeature, logger);
49
- const hasPermission = requiredPermissions.some((perm)=>userPermissions.includes(perm));
50
- if (!hasPermission) {
51
- logger.warn(`Permission denied for user ${user.id} ${context} ${resourceId}. ` + `Required: [${requiredPermissions.join(', ')}], User has: [${userPermissions.join(', ')}]`);
52
- }
53
- return hasPermission;
40
+ const userPermissions = await getUserPermissionsFromCache(user, cacheManager, enableCompanyFeature);
41
+ return requiredPermissions.some((perm)=>userPermissions.includes(perm));
54
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flusys/nestjs-form-builder",
3
- "version": "3.0.1",
3
+ "version": "4.0.0-lts",
4
4
  "description": "Dynamic form builder module with schema versioning and access control",
5
5
  "main": "cjs/index.js",
6
6
  "module": "fesm/index.js",
@@ -80,10 +80,11 @@
80
80
  "@nestjs/typeorm": "^10.0.0 || ^11.0.0",
81
81
  "class-transformer": "^0.5.0",
82
82
  "class-validator": "^0.14.0",
83
- "typeorm": "^0.3.0"
83
+ "typeorm": "^0.3.0",
84
+ "express": "^4.18.0"
84
85
  },
85
86
  "dependencies": {
86
- "@flusys/nestjs-core": "3.0.1",
87
- "@flusys/nestjs-shared": "3.0.1"
87
+ "@flusys/nestjs-core": "4.0.0-lts",
88
+ "@flusys/nestjs-shared": "4.0.0-lts"
88
89
  }
89
90
  }
@@ -1,12 +1,10 @@
1
1
  import { MultiTenantDataSourceService } from '@flusys/nestjs-shared/modules';
2
2
  import { IDatabaseConfig, ITenantDatabaseConfig } from '@flusys/nestjs-core';
3
- import { Logger } from '@nestjs/common';
4
3
  import { Request } from 'express';
5
4
  import { DataSource } from 'typeorm';
6
5
  import { FormBuilderConfigService } from './form-builder-config.service';
7
6
  export declare class FormBuilderDataSourceProvider extends MultiTenantDataSourceService {
8
7
  private readonly configService;
9
- protected readonly logger: Logger;
10
8
  protected static readonly tenantConnections: Map<string, DataSource>;
11
9
  protected static singleDataSource: DataSource | null;
12
10
  protected static readonly tenantsRegistry: Map<string, ITenantDatabaseConfig>;
@@ -1,4 +1,3 @@
1
1
  import { HybridCache } from '@flusys/nestjs-shared/classes';
2
2
  import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
3
- import { Logger } from '@nestjs/common';
4
- export declare function validateUserPermissions(user: ILoggedUserInfo, requiredPermissions: string[], cacheManager: HybridCache, enableCompanyFeature: boolean, logger: Logger, context: string, resourceId: string): Promise<boolean>;
3
+ export declare function validateUserPermissions(user: ILoggedUserInfo, requiredPermissions: string[], cacheManager: HybridCache, enableCompanyFeature: boolean): Promise<boolean>;