@solidxai/core 0.1.13-beta.9 → 0.1.14-beta.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 (276) hide show
  1. package/CHANGELOG.md +9244 -0
  2. package/README.md +19 -0
  3. package/dist/commands/run-tests.command.d.ts +2 -0
  4. package/dist/commands/run-tests.command.d.ts.map +1 -1
  5. package/dist/commands/run-tests.command.js +20 -3
  6. package/dist/commands/run-tests.command.js.map +1 -1
  7. package/dist/config/cache.options.d.ts.map +1 -1
  8. package/dist/config/cache.options.js +2 -2
  9. package/dist/config/cache.options.js.map +1 -1
  10. package/dist/constants/error-messages.d.ts +7 -0
  11. package/dist/constants/error-messages.d.ts.map +1 -1
  12. package/dist/constants/error-messages.js +7 -0
  13. package/dist/constants/error-messages.js.map +1 -1
  14. package/dist/constants/media-file-types.d.ts +17 -0
  15. package/dist/constants/media-file-types.d.ts.map +1 -1
  16. package/dist/constants/media-file-types.js +129 -22
  17. package/dist/constants/media-file-types.js.map +1 -1
  18. package/dist/constants/success-messages.d.ts +3 -0
  19. package/dist/constants/success-messages.d.ts.map +1 -1
  20. package/dist/constants/success-messages.js +3 -0
  21. package/dist/constants/success-messages.js.map +1 -1
  22. package/dist/controllers/authentication.controller.d.ts +1 -1
  23. package/dist/controllers/authentication.controller.d.ts.map +1 -1
  24. package/dist/controllers/authentication.controller.js +4 -3
  25. package/dist/controllers/authentication.controller.js.map +1 -1
  26. package/dist/controllers/field-metadata.controller.d.ts +6 -2
  27. package/dist/controllers/field-metadata.controller.d.ts.map +1 -1
  28. package/dist/controllers/mpin-authentication.controller.d.ts +44 -0
  29. package/dist/controllers/mpin-authentication.controller.d.ts.map +1 -0
  30. package/dist/controllers/mpin-authentication.controller.js +102 -0
  31. package/dist/controllers/mpin-authentication.controller.js.map +1 -0
  32. package/dist/controllers/view-metadata.controller.d.ts +4 -0
  33. package/dist/controllers/view-metadata.controller.d.ts.map +1 -1
  34. package/dist/dtos/change-mpin.dto.d.ts +6 -0
  35. package/dist/dtos/change-mpin.dto.d.ts.map +1 -0
  36. package/dist/dtos/change-mpin.dto.js +39 -0
  37. package/dist/dtos/change-mpin.dto.js.map +1 -0
  38. package/dist/dtos/create-field-metadata.dto.d.ts +3 -6
  39. package/dist/dtos/create-field-metadata.dto.d.ts.map +1 -1
  40. package/dist/dtos/create-field-metadata.dto.js +15 -10
  41. package/dist/dtos/create-field-metadata.dto.js.map +1 -1
  42. package/dist/dtos/create-model-sequence.dto.d.ts +1 -1
  43. package/dist/dtos/create-model-sequence.dto.d.ts.map +1 -1
  44. package/dist/dtos/create-model-sequence.dto.js +1 -1
  45. package/dist/dtos/create-model-sequence.dto.js.map +1 -1
  46. package/dist/dtos/mpin-login.dto.d.ts +5 -0
  47. package/dist/dtos/mpin-login.dto.d.ts.map +1 -0
  48. package/dist/dtos/mpin-login.dto.js +33 -0
  49. package/dist/dtos/mpin-login.dto.js.map +1 -0
  50. package/dist/dtos/setup-mpin.dto.d.ts +7 -0
  51. package/dist/dtos/setup-mpin.dto.d.ts.map +1 -0
  52. package/dist/dtos/setup-mpin.dto.js +47 -0
  53. package/dist/dtos/setup-mpin.dto.js.map +1 -0
  54. package/dist/entities/dashboard-user-layout.entity.js +2 -2
  55. package/dist/entities/dashboard-user-layout.entity.js.map +1 -1
  56. package/dist/entities/field-metadata.entity.d.ts +1 -0
  57. package/dist/entities/field-metadata.entity.d.ts.map +1 -1
  58. package/dist/entities/field-metadata.entity.js +5 -1
  59. package/dist/entities/field-metadata.entity.js.map +1 -1
  60. package/dist/entities/user-device-credential.entity.d.ts +18 -0
  61. package/dist/entities/user-device-credential.entity.d.ts.map +1 -0
  62. package/dist/entities/user-device-credential.entity.js +91 -0
  63. package/dist/entities/user-device-credential.entity.js.map +1 -0
  64. package/dist/entities/user.entity.js +2 -2
  65. package/dist/entities/user.entity.js.map +1 -1
  66. package/dist/entities/view-metadata.entity.js.map +1 -1
  67. package/dist/entities/workflow-execution-artifact.entity.d.ts.map +1 -1
  68. package/dist/entities/workflow-execution-artifact.entity.js +4 -3
  69. package/dist/entities/workflow-execution-artifact.entity.js.map +1 -1
  70. package/dist/entities/workflow-execution-log.entity.d.ts.map +1 -1
  71. package/dist/entities/workflow-execution-log.entity.js +4 -3
  72. package/dist/entities/workflow-execution-log.entity.js.map +1 -1
  73. package/dist/entities/workflow-execution.entity.d.ts.map +1 -1
  74. package/dist/entities/workflow-execution.entity.js +3 -2
  75. package/dist/entities/workflow-execution.entity.js.map +1 -1
  76. package/dist/entities/workflow-secret.entity.d.ts.map +1 -1
  77. package/dist/entities/workflow-secret.entity.js +3 -2
  78. package/dist/entities/workflow-secret.entity.js.map +1 -1
  79. package/dist/entities/workflow-step-execution.entity.d.ts.map +1 -1
  80. package/dist/entities/workflow-step-execution.entity.js +3 -2
  81. package/dist/entities/workflow-step-execution.entity.js.map +1 -1
  82. package/dist/entities/workflow-trigger-execution.entity.d.ts.map +1 -1
  83. package/dist/entities/workflow-trigger-execution.entity.js +6 -5
  84. package/dist/entities/workflow-trigger-execution.entity.js.map +1 -1
  85. package/dist/filters/http-exception.filter.d.ts.map +1 -1
  86. package/dist/filters/http-exception.filter.js +5 -1
  87. package/dist/filters/http-exception.filter.js.map +1 -1
  88. package/dist/helpers/bootstrap.helper.d.ts +5 -2
  89. package/dist/helpers/bootstrap.helper.d.ts.map +1 -1
  90. package/dist/helpers/bootstrap.helper.js +7 -3
  91. package/dist/helpers/bootstrap.helper.js.map +1 -1
  92. package/dist/helpers/field-crud-managers/LongTextFieldCrudManager.d.ts.map +1 -1
  93. package/dist/helpers/field-crud-managers/LongTextFieldCrudManager.js +3 -2
  94. package/dist/helpers/field-crud-managers/LongTextFieldCrudManager.js.map +1 -1
  95. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts +8 -0
  96. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
  97. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +61 -76
  98. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
  99. package/dist/helpers/field-crud-managers/ShortTextFieldCrudManager.d.ts.map +1 -1
  100. package/dist/helpers/field-crud-managers/ShortTextFieldCrudManager.js +3 -2
  101. package/dist/helpers/field-crud-managers/ShortTextFieldCrudManager.js.map +1 -1
  102. package/dist/helpers/field-crud-managers/textFieldCrudManagerUtils.d.ts +3 -0
  103. package/dist/helpers/field-crud-managers/textFieldCrudManagerUtils.d.ts.map +1 -0
  104. package/dist/helpers/field-crud-managers/textFieldCrudManagerUtils.js +17 -0
  105. package/dist/helpers/field-crud-managers/textFieldCrudManagerUtils.js.map +1 -0
  106. package/dist/helpers/file-download.helper.d.ts.map +1 -1
  107. package/dist/helpers/file-download.helper.js +6 -1
  108. package/dist/helpers/file-download.helper.js.map +1 -1
  109. package/dist/helpers/security.helper.d.ts +5 -1
  110. package/dist/helpers/security.helper.d.ts.map +1 -1
  111. package/dist/helpers/security.helper.js +15 -4
  112. package/dist/helpers/security.helper.js.map +1 -1
  113. package/dist/helpers/solid-core-error-codes-provider.service.d.ts.map +1 -1
  114. package/dist/helpers/solid-core-error-codes-provider.service.js +45 -0
  115. package/dist/helpers/solid-core-error-codes-provider.service.js.map +1 -1
  116. package/dist/helpers/solid-microservice-adapter.service.d.ts.map +1 -1
  117. package/dist/helpers/solid-microservice-adapter.service.js +1 -1
  118. package/dist/helpers/solid-microservice-adapter.service.js.map +1 -1
  119. package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
  120. package/dist/helpers/typeorm-db-helper.js +9 -0
  121. package/dist/helpers/typeorm-db-helper.js.map +1 -1
  122. package/dist/index.d.ts +6 -0
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +6 -0
  125. package/dist/index.js.map +1 -1
  126. package/dist/interfaces/active-user-data.interface.d.ts +1 -0
  127. package/dist/interfaces/active-user-data.interface.d.ts.map +1 -1
  128. package/dist/interfaces/active-user-data.interface.js.map +1 -1
  129. package/dist/repository/user-device-credential.repository.d.ts +12 -0
  130. package/dist/repository/user-device-credential.repository.d.ts.map +1 -0
  131. package/dist/repository/user-device-credential.repository.js +34 -0
  132. package/dist/repository/user-device-credential.repository.js.map +1 -0
  133. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  134. package/dist/seeders/module-metadata-seeder.service.js +7 -3
  135. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  136. package/dist/seeders/seed-data/solid-core-metadata.json +168 -2
  137. package/dist/services/api-key.service.d.ts.map +1 -1
  138. package/dist/services/api-key.service.js +1 -1
  139. package/dist/services/api-key.service.js.map +1 -1
  140. package/dist/services/authentication.service.d.ts +5 -4
  141. package/dist/services/authentication.service.d.ts.map +1 -1
  142. package/dist/services/authentication.service.js +50 -19
  143. package/dist/services/authentication.service.js.map +1 -1
  144. package/dist/services/chatter-message.service.d.ts +5 -1
  145. package/dist/services/chatter-message.service.d.ts.map +1 -1
  146. package/dist/services/chatter-message.service.js +48 -13
  147. package/dist/services/chatter-message.service.js.map +1 -1
  148. package/dist/services/crud-helper.service.d.ts +1 -2
  149. package/dist/services/crud-helper.service.d.ts.map +1 -1
  150. package/dist/services/crud-helper.service.js +2 -16
  151. package/dist/services/crud-helper.service.js.map +1 -1
  152. package/dist/services/crud.service.d.ts.map +1 -1
  153. package/dist/services/crud.service.js +4 -2
  154. package/dist/services/crud.service.js.map +1 -1
  155. package/dist/services/draft-publish-helper.service.d.ts +1 -0
  156. package/dist/services/draft-publish-helper.service.d.ts.map +1 -1
  157. package/dist/services/draft-publish-helper.service.js +38 -4
  158. package/dist/services/draft-publish-helper.service.js.map +1 -1
  159. package/dist/services/field-metadata.service.d.ts +6 -2
  160. package/dist/services/field-metadata.service.d.ts.map +1 -1
  161. package/dist/services/field-metadata.service.js +5 -1
  162. package/dist/services/field-metadata.service.js.map +1 -1
  163. package/dist/services/media-storage.utils.d.ts +1 -0
  164. package/dist/services/media-storage.utils.d.ts.map +1 -1
  165. package/dist/services/media-storage.utils.js +12 -1
  166. package/dist/services/media-storage.utils.js.map +1 -1
  167. package/dist/services/media.service.d.ts.map +1 -1
  168. package/dist/services/media.service.js +1 -0
  169. package/dist/services/media.service.js.map +1 -1
  170. package/dist/services/module-metadata.service.d.ts.map +1 -1
  171. package/dist/services/module-metadata.service.js +9 -1
  172. package/dist/services/module-metadata.service.js.map +1 -1
  173. package/dist/services/mpin.service.d.ts +67 -0
  174. package/dist/services/mpin.service.d.ts.map +1 -0
  175. package/dist/services/mpin.service.js +282 -0
  176. package/dist/services/mpin.service.js.map +1 -0
  177. package/dist/services/refresh-token-ids-storage.service.d.ts +25 -6
  178. package/dist/services/refresh-token-ids-storage.service.d.ts.map +1 -1
  179. package/dist/services/refresh-token-ids-storage.service.js +80 -42
  180. package/dist/services/refresh-token-ids-storage.service.js.map +1 -1
  181. package/dist/services/setting.service.d.ts.map +1 -1
  182. package/dist/services/setting.service.js +6 -1
  183. package/dist/services/setting.service.js.map +1 -1
  184. package/dist/services/settings/default-settings-provider.service.d.ts +202 -6
  185. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  186. package/dist/services/settings/default-settings-provider.service.js +114 -3
  187. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  188. package/dist/services/view-metadata.service.d.ts +4 -0
  189. package/dist/services/view-metadata.service.d.ts.map +1 -1
  190. package/dist/services/view-metadata.service.js +2 -0
  191. package/dist/services/view-metadata.service.js.map +1 -1
  192. package/dist/solid-core.module.d.ts +1 -3
  193. package/dist/solid-core.module.d.ts.map +1 -1
  194. package/dist/solid-core.module.js +14 -39
  195. package/dist/solid-core.module.js.map +1 -1
  196. package/dist/testing/reporter/console-reporter.d.ts +3 -0
  197. package/dist/testing/reporter/console-reporter.d.ts.map +1 -1
  198. package/dist/testing/reporter/console-reporter.js +2 -0
  199. package/dist/testing/reporter/console-reporter.js.map +1 -1
  200. package/dist/testing/reporter/progress-reporter.d.ts +64 -0
  201. package/dist/testing/reporter/progress-reporter.d.ts.map +1 -0
  202. package/dist/testing/reporter/progress-reporter.js +224 -0
  203. package/dist/testing/reporter/progress-reporter.js.map +1 -0
  204. package/dist/testing/reporter/reporter.types.d.ts +3 -0
  205. package/dist/testing/reporter/reporter.types.d.ts.map +1 -1
  206. package/dist/testing/reporter/reporter.types.js.map +1 -1
  207. package/dist/testing/runner/run-from-metadata.d.ts.map +1 -1
  208. package/dist/testing/runner/run-from-metadata.js +1 -0
  209. package/dist/testing/runner/run-from-metadata.js.map +1 -1
  210. package/dist/theme/theme-registry.d.ts +14 -0
  211. package/dist/theme/theme-registry.d.ts.map +1 -0
  212. package/dist/theme/theme-registry.js +147 -0
  213. package/dist/theme/theme-registry.js.map +1 -0
  214. package/package.json +2 -2
  215. package/src/commands/run-tests.command.ts +17 -3
  216. package/src/config/cache.options.ts +9 -1
  217. package/src/constants/error-messages.ts +14 -0
  218. package/src/constants/media-file-types.ts +182 -19
  219. package/src/constants/success-messages.ts +5 -0
  220. package/src/controllers/authentication.controller.ts +5 -2
  221. package/src/controllers/mpin-authentication.controller.ts +67 -0
  222. package/src/dtos/change-mpin.dto.ts +18 -0
  223. package/src/dtos/create-field-metadata.dto.ts +17 -9
  224. package/src/dtos/create-model-sequence.dto.ts +1 -1
  225. package/src/dtos/mpin-login.dto.ts +24 -0
  226. package/src/dtos/setup-mpin.dto.ts +51 -0
  227. package/src/entities/dashboard-user-layout.entity.ts +2 -2
  228. package/src/entities/field-metadata.entity.ts +3 -0
  229. package/src/entities/user-device-credential.entity.ts +130 -0
  230. package/src/entities/user.entity.ts +2 -2
  231. package/src/entities/view-metadata.entity.ts +2 -2
  232. package/src/entities/workflow-execution-artifact.entity.ts +4 -3
  233. package/src/entities/workflow-execution-log.entity.ts +4 -3
  234. package/src/entities/workflow-execution.entity.ts +3 -2
  235. package/src/entities/workflow-secret.entity.ts +3 -2
  236. package/src/entities/workflow-step-execution.entity.ts +3 -2
  237. package/src/entities/workflow-trigger-execution.entity.ts +6 -5
  238. package/src/filters/http-exception.filter.ts +11 -1
  239. package/src/helpers/bootstrap.helper.ts +43 -6
  240. package/src/helpers/field-crud-managers/LongTextFieldCrudManager.ts +3 -2
  241. package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +83 -96
  242. package/src/helpers/field-crud-managers/ShortTextFieldCrudManager.ts +3 -2
  243. package/src/helpers/field-crud-managers/textFieldCrudManagerUtils.ts +14 -0
  244. package/src/helpers/file-download.helper.ts +15 -1
  245. package/src/helpers/security.helper.ts +35 -8
  246. package/src/helpers/solid-core-error-codes-provider.service.ts +53 -0
  247. package/src/helpers/solid-microservice-adapter.service.ts +6 -1
  248. package/src/helpers/typeorm-db-helper.ts +12 -1
  249. package/src/index.ts +6 -0
  250. package/src/interfaces/active-user-data.interface.ts +9 -0
  251. package/src/repository/user-device-credential.repository.ts +17 -0
  252. package/src/seeders/module-metadata-seeder.service.ts +11 -3
  253. package/src/seeders/seed-data/solid-core-metadata.json +168 -2
  254. package/src/services/api-key.service.ts +13 -1
  255. package/src/services/authentication.service.ts +127 -36
  256. package/src/services/chatter-message.service.ts +78 -15
  257. package/src/services/crud-helper.service.ts +19 -16
  258. package/src/services/crud.service.ts +5 -2
  259. package/src/services/draft-publish-helper.service.ts +56 -4
  260. package/src/services/field-metadata.service.ts +6 -2
  261. package/src/services/media-storage.utils.ts +26 -1
  262. package/src/services/media.service.ts +1 -0
  263. package/src/services/module-metadata.service.ts +10 -1
  264. package/src/services/mpin.service.ts +447 -0
  265. package/src/services/refresh-token-ids-storage.service.ts +229 -90
  266. package/src/services/setting.service.ts +8 -1
  267. package/src/services/settings/default-settings-provider.service.ts +119 -3
  268. package/src/services/view-metadata.service.ts +4 -0
  269. package/src/solid-core.module.ts +26 -12
  270. package/src/testing/README.md +2 -1
  271. package/src/testing/reporter/console-reporter.ts +4 -0
  272. package/src/testing/reporter/progress-reporter.ts +251 -0
  273. package/src/testing/reporter/reporter.types.ts +3 -0
  274. package/src/testing/runner/run-from-metadata.ts +1 -0
  275. package/src/theme/theme-registry.ts +181 -0
  276. package/CLAUDE.md +0 -26
@@ -36,12 +36,12 @@ export class User extends CommonEntity {
36
36
  @Expose()
37
37
  username: string;
38
38
 
39
- @Index({ unique: true })
39
+ @Index()
40
40
  @Column({ type: "varchar", nullable: true })
41
41
  @Expose()
42
42
  email: string;
43
43
 
44
- @Index({ unique: true })
44
+ @Index()
45
45
  @Column({ type: "varchar", nullable: true })
46
46
  @Expose()
47
47
  mobile: string;
@@ -19,11 +19,11 @@ export class ViewMetadata extends CommonEntity {
19
19
  @Column({ name: "type", type: "varchar" })
20
20
  type: string;
21
21
 
22
- //TODO: To make this truly cross db compatible, we should avoid setting a db type
22
+ // TODO: To make this truly cross db compatible, we should avoid setting a db type
23
23
  @Column({ name: "context", type: "text" })
24
24
  context: any = "{}";
25
25
 
26
- //TODO: To make this truly cross db compatible, we should avoid setting a db type
26
+ // TODO: To make this truly cross db compatible, we should avoid setting a db type
27
27
  @Column({ name: "layout", type: "text" })
28
28
  layout: any;
29
29
 
@@ -2,6 +2,7 @@ import { CommonEntity } from 'src/entities/common.entity';
2
2
  import { Entity, Column, Index, JoinColumn, ManyToOne } from 'typeorm';
3
3
  import { WorkflowExecution } from './workflow-execution.entity';
4
4
  import { WorkflowStepExecution } from './workflow-step-execution.entity'
5
+ import { getColumnType } from 'src/helpers/typeorm-db-helper';
5
6
 
6
7
  @Entity('ss_workflow_execution_artifact')
7
8
  export class WorkflowExecutionArtifact extends CommonEntity {
@@ -10,12 +11,12 @@ export class WorkflowExecutionArtifact extends CommonEntity {
10
11
  artifactKey: string;
11
12
 
12
13
  @Index()
13
- @ManyToOne(() => WorkflowExecution, { onDelete: "CASCADE", nullable: false })
14
+ @ManyToOne(() => WorkflowExecution, { nullable: false })
14
15
  @JoinColumn()
15
16
  workflowExecution: WorkflowExecution;
16
17
 
17
18
  @Index()
18
- @ManyToOne(() => WorkflowStepExecution, { onDelete: "CASCADE", nullable: true })
19
+ @ManyToOne(() => WorkflowStepExecution, { nullable: true })
19
20
  @JoinColumn()
20
21
  workflowStepExecution: WorkflowStepExecution;
21
22
 
@@ -61,7 +62,7 @@ export class WorkflowExecutionArtifact extends CommonEntity {
61
62
  checksum: string;
62
63
 
63
64
  @Index()
64
- @Column({ type: "timestamp", nullable: true })
65
+ @Column({ nullable: true, ...getColumnType('datetime') })
65
66
  producedAt: Date;
66
67
 
67
68
  @Column({ type: "simple-json", nullable: true })
@@ -2,6 +2,7 @@ import { CommonEntity } from 'src/entities/common.entity';
2
2
  import { Entity, Column, Index, JoinColumn, ManyToOne } from 'typeorm';
3
3
  import { WorkflowExecution } from './workflow-execution.entity';
4
4
  import { WorkflowStepExecution } from './workflow-step-execution.entity'
5
+ import { getColumnType } from 'src/helpers/typeorm-db-helper';
5
6
 
6
7
  @Entity('ss_workflow_execution_log')
7
8
  export class WorkflowExecutionLog extends CommonEntity {
@@ -10,12 +11,12 @@ export class WorkflowExecutionLog extends CommonEntity {
10
11
  logKey: string;
11
12
 
12
13
  @Index()
13
- @ManyToOne(() => WorkflowExecution, { onDelete: "CASCADE", nullable: false })
14
+ @ManyToOne(() => WorkflowExecution, { nullable: false })
14
15
  @JoinColumn()
15
16
  workflowExecution: WorkflowExecution;
16
17
 
17
18
  @Index()
18
- @ManyToOne(() => WorkflowStepExecution, { onDelete: "CASCADE", nullable: true })
19
+ @ManyToOne(() => WorkflowStepExecution, { nullable: true })
19
20
  @JoinColumn()
20
21
  workflowStepExecution: WorkflowStepExecution;
21
22
 
@@ -47,7 +48,7 @@ export class WorkflowExecutionLog extends CommonEntity {
47
48
  sequenceNumber: number;
48
49
 
49
50
  @Index()
50
- @Column({ type: "timestamp" })
51
+ @Column({ ...getColumnType('datetime') })
51
52
  occurredAt: Date;
52
53
 
53
54
  @Column({ type: "simple-json", nullable: true })
@@ -4,6 +4,7 @@ import { WorkflowDefinition } from './workflow-definition.entity'
4
4
  import { WorkflowStepExecution } from './workflow-step-execution.entity';
5
5
  import { WorkflowExecutionLog } from './workflow-execution-log.entity';
6
6
  import { WorkflowExecutionArtifact } from './workflow-execution-artifact.entity';
7
+ import { getColumnType } from 'src/helpers/typeorm-db-helper';
7
8
 
8
9
  @Entity('ss_workflow_execution')
9
10
  export class WorkflowExecution extends CommonEntity {
@@ -32,11 +33,11 @@ export class WorkflowExecution extends CommonEntity {
32
33
  triggerType: string = "manual";
33
34
 
34
35
  @Index()
35
- @Column({ type: "timestamp", nullable: true })
36
+ @Column({ nullable: true, ...getColumnType('datetime') })
36
37
  startedAt: Date;
37
38
 
38
39
  @Index()
39
- @Column({ type: "timestamp", nullable: true })
40
+ @Column({ nullable: true, ...getColumnType('datetime') })
40
41
  finishedAt: Date;
41
42
 
42
43
  @Column({ type: "bigint", nullable: true })
@@ -1,5 +1,6 @@
1
1
  import { CommonEntity } from 'src/entities/common.entity';
2
2
  import { Entity, Column, Index } from 'typeorm';
3
+ import { getColumnType } from 'src/helpers/typeorm-db-helper';
3
4
 
4
5
  @Entity('ss_workflow_secret')
5
6
  export class WorkflowSecret extends CommonEntity {
@@ -25,9 +26,9 @@ export class WorkflowSecret extends CommonEntity {
25
26
  @Column({ type: "varchar", default: "active" })
26
27
  status: string = "active";
27
28
 
28
- @Column({ type: "timestamp", nullable: true })
29
+ @Column({ nullable: true, ...getColumnType('datetime') })
29
30
  lastRotatedAt: Date;
30
31
 
31
- @Column({ type: "timestamp", nullable: true })
32
+ @Column({ nullable: true, ...getColumnType('datetime') })
32
33
  lastAccessedAt: Date;
33
34
  }
@@ -1,6 +1,7 @@
1
1
  import { CommonEntity } from 'src/entities/common.entity';
2
2
  import { Entity, Column, Index, JoinColumn, ManyToOne } from 'typeorm';
3
3
  import { WorkflowExecution } from './workflow-execution.entity'
4
+ import { getColumnType } from 'src/helpers/typeorm-db-helper';
4
5
 
5
6
  @Entity('ss_workflow_step_execution')
6
7
  export class WorkflowStepExecution extends CommonEntity {
@@ -55,11 +56,11 @@ export class WorkflowStepExecution extends CommonEntity {
55
56
  sequenceNumber: number;
56
57
 
57
58
  @Index()
58
- @Column({ type: "timestamp", nullable: true })
59
+ @Column({ nullable: true, ...getColumnType('datetime') })
59
60
  startedAt: Date;
60
61
 
61
62
  @Index()
62
- @Column({ type: "timestamp", nullable: true })
63
+ @Column({ nullable: true, ...getColumnType('datetime') })
63
64
  finishedAt: Date;
64
65
 
65
66
  @Column({ type: "bigint", nullable: true })
@@ -2,6 +2,7 @@ import { CommonEntity } from 'src/entities/common.entity';
2
2
  import { Entity, Column, Index, JoinColumn, ManyToOne } from 'typeorm';
3
3
  import { WorkflowDefinition } from './workflow-definition.entity';
4
4
  import { WorkflowExecution } from './workflow-execution.entity'
5
+ import { getColumnType } from 'src/helpers/typeorm-db-helper';
5
6
 
6
7
  @Entity('ss_workflow_trigger_execution')
7
8
  export class WorkflowTriggerExecution extends CommonEntity {
@@ -10,12 +11,12 @@ export class WorkflowTriggerExecution extends CommonEntity {
10
11
  triggerExecutionKey: string;
11
12
 
12
13
  @Index()
13
- @ManyToOne(() => WorkflowDefinition, { onDelete: "CASCADE", nullable: false })
14
+ @ManyToOne(() => WorkflowDefinition, { nullable: false })
14
15
  @JoinColumn()
15
16
  workflowDefinition: WorkflowDefinition;
16
17
 
17
18
  @Index()
18
- @ManyToOne(() => WorkflowExecution, { onDelete: "CASCADE", nullable: true })
19
+ @ManyToOne(() => WorkflowExecution, { nullable: true })
19
20
  @JoinColumn()
20
21
  workflowExecution: WorkflowExecution;
21
22
 
@@ -39,15 +40,15 @@ export class WorkflowTriggerExecution extends CommonEntity {
39
40
  matched: boolean = false;
40
41
 
41
42
  @Index()
42
- @Column({ type: "timestamp" })
43
+ @Column({ ...getColumnType('datetime') })
43
44
  firedAt: Date;
44
45
 
45
46
  @Index()
46
- @Column({ type: "timestamp", nullable: true })
47
+ @Column({ nullable: true, ...getColumnType('datetime') })
47
48
  startedAt: Date;
48
49
 
49
50
  @Index()
50
- @Column({ type: "timestamp", nullable: true })
51
+ @Column({ nullable: true, ...getColumnType('datetime') })
51
52
  finishedAt: Date;
52
53
 
53
54
  @Column({ type: "bigint", nullable: true })
@@ -30,12 +30,22 @@ export class HttpExceptionFilter implements ExceptionFilter {
30
30
  const isHttp = exception instanceof HttpException;
31
31
  const explicitStatus = isHttp ? exception.getStatus() : undefined;
32
32
 
33
+ // Errors from http-errors-style libraries (e.g. body-parser's PayloadTooLargeError)
34
+ // are plain Errors carrying their own status. Honour it rather than collapsing to 500.
35
+ const rawStatus = exception?.status ?? exception?.statusCode;
36
+ const derivedStatus =
37
+ typeof rawStatus === 'number' && rawStatus >= 400 && rawStatus < 600
38
+ ? rawStatus
39
+ : undefined;
40
+
33
41
  // Canonical code + static message
34
42
  const code: ErrorCode = this.errorMapper.mapException(exception);
35
43
  const defaultStatus = this.errorMapper.getHttpStatus(code);
36
44
  const message = code === 'solidx-unknown-error' ? `${exception?.message}` : this.errorMapper.getMessage(code);
37
45
 
38
- const status = explicitStatus ?? defaultStatus ?? 500;
46
+ // derivedStatus must precede defaultStatus: getHttpStatus() returns a hard 500
47
+ // for unmatched codes, so it is never undefined and would always win otherwise.
48
+ const status = explicitStatus ?? derivedStatus ?? defaultStatus ?? 500;
39
49
 
40
50
  // Logging
41
51
  this.logger.error(
@@ -1,5 +1,6 @@
1
1
  import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';
2
2
  import { NestFactory, Reflector } from '@nestjs/core';
3
+ import { NestExpressApplication } from '@nestjs/platform-express';
3
4
  import { WsAdapter } from '@nestjs/platform-ws';
4
5
  import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
5
6
  import { NextFunction, Request, Response } from 'express';
@@ -12,7 +13,12 @@ import { CommandFactory } from 'nest-commander';
12
13
  import { createWinstonLoggerConfig } from '../winston.logger';
13
14
  import { WrapResponseInterceptor } from '../interceptors/wrap-response.interceptor';
14
15
  import { buildDefaultCorsOptions } from './cors.helper';
15
- import { buildDefaultSecurityHeaderOptions, buildPermissionsPolicyHeader, PermissionsPolicyConfig } from './security.helper';
16
+ import {
17
+ buildDefaultSecurityHeaderOptions,
18
+ buildPermissionsPolicyHeader,
19
+ PermissionsPolicyConfig,
20
+ SolidSecurityOptions,
21
+ } from './security.helper';
16
22
  import { parseBooleanEnv } from './environment.helper';
17
23
 
18
24
  // ---- Shared process handlers ----
@@ -50,8 +56,20 @@ export interface SolidBootstrapOptions {
50
56
  swagger?: SolidSwaggerOptions | false;
51
57
  /** Permissions-Policy header overrides (merged with defaults). */
52
58
  permissionsPolicyOverrides?: Partial<PermissionsPolicyConfig>;
59
+ /** Security header overrides, including iframe frame-ancestor allowlists. */
60
+ security?: SolidSecurityOptions;
53
61
  /** Show full NestJS init logs during bootstrap (route mapping, module deps, pollers). Defaults to false. */
54
62
  verboseBootstrap?: boolean;
63
+ /**
64
+ * Max request body size for JSON and urlencoded payloads, as a `bytes`-compatible
65
+ * string (e.g. '10mb', '512kb'). Defaults to '10mb'.
66
+ *
67
+ * Does NOT apply to multipart/form-data uploads: body-parser matches on Content-Type,
68
+ * so multipart requests skip these parsers entirely and are handled by Multer. Raise
69
+ * this only for large structured JSON (e.g. /bulk arrays), bearing in mind the parsed
70
+ * object graph can retain several times the raw byte size.
71
+ */
72
+ bodyLimit?: string;
55
73
  }
56
74
 
57
75
  /**
@@ -64,19 +82,30 @@ export interface SolidBootstrapOptions {
64
82
  * // main.ts
65
83
  * bootstrapSolidApp(() => AppModule.forRoot(), {
66
84
  * swagger: { title: 'My API', description: 'My API description' },
85
+ * security: { frameAncestors: ["'self'", 'https://portal.example.com'] },
67
86
  * });
68
87
  */
69
88
  export async function bootstrapSolidApp(
70
89
  appModuleFactory: () => Promise<any>,
71
90
  options: SolidBootstrapOptions = {},
72
- ): Promise<void> {
91
+ ): Promise<NestExpressApplication> {
73
92
  registerGlobalProcessHandlers();
74
93
 
75
- const { globalPrefix = 'api', swagger = {}, permissionsPolicyOverrides = {}, verboseBootstrap = false } = options;
94
+ const {
95
+ globalPrefix = 'api',
96
+ swagger = {},
97
+ permissionsPolicyOverrides = {},
98
+ security = {},
99
+ verboseBootstrap = false,
100
+ bodyLimit = '10mb',
101
+ } = options;
76
102
 
77
103
  const startTime = Date.now();
78
104
  const appModule = await appModuleFactory();
79
- const app = await NestFactory.create(appModule, {
105
+ const app = await NestFactory.create<NestExpressApplication>(appModule, {
106
+ // Nest's default parsers are registered during init() with a 100kb limit, ahead of
107
+ // any module middleware. Disable them so the parsers below own the limit outright.
108
+ bodyParser: false,
80
109
  logger: WinstonModule.createLogger({ ...createWinstonLoggerConfig(), level: verboseBootstrap ? 'debug' : 'error' }),
81
110
  });
82
111
 
@@ -87,7 +116,7 @@ export async function bootstrapSolidApp(
87
116
  app
88
117
  .get(WINSTON_MODULE_NEST_PROVIDER)
89
118
  .log('API server disabled via API_ENABLED=false. Skipping HTTP listen.', 'Bootstrap');
90
- return;
119
+ return app;
91
120
  }
92
121
 
93
122
  // Health check at root path
@@ -95,7 +124,7 @@ export async function bootstrapSolidApp(
95
124
  server.get('/', (_req, res) => res.status(200).send('SOLID OK'));
96
125
 
97
126
  // Security headers
98
- app.use(helmet(buildDefaultSecurityHeaderOptions()));
127
+ app.use(helmet(buildDefaultSecurityHeaderOptions(security)));
99
128
 
100
129
  // Nest's Swagger UI HTML injects inline styles; keep CSP strict elsewhere.
101
130
  const isSwaggerPath = (path: string) =>
@@ -136,6 +165,12 @@ export async function bootstrapSolidApp(
136
165
  next();
137
166
  });
138
167
 
168
+ // Body parsers — replaces Nest's default 100kb parsers (disabled via bodyParser: false).
169
+ // Registered here so the middleware order Nest applied by default is preserved:
170
+ // helmet -> CSP -> Permissions-Policy -> qs -> body parsers -> routes.
171
+ app.useBodyParser('json', { limit: bodyLimit });
172
+ app.useBodyParser('urlencoded', { limit: bodyLimit, extended: true });
173
+
139
174
  // Global ValidationPipe
140
175
  app.useGlobalPipes(
141
176
  new ValidationPipe({
@@ -195,6 +230,8 @@ export async function bootstrapSolidApp(
195
230
 
196
231
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
197
232
  process.stdout.write(`\x1b[32mServer started on port ${port} in ${elapsed}s\x1b[0m\n`);
233
+
234
+ return app;
198
235
  }
199
236
 
200
237
  // ---- CLI bootstrap ----
@@ -1,5 +1,6 @@
1
1
  import { isEmpty, isNotEmpty, isString, matches } from "class-validator";
2
2
  import { FieldCrudManager, ValidationError } from "src/interfaces";
3
+ import { normalizeTextFieldInDto, normalizeTextFieldValue } from "./textFieldCrudManagerUtils";
3
4
 
4
5
  export interface LongTextFieldOptions {
5
6
  regexPattern: string | undefined | null;
@@ -15,7 +16,7 @@ export class LongTextFieldCrudManager implements FieldCrudManager {
15
16
  }
16
17
 
17
18
  validate(dto: any): ValidationError[] {
18
- const fieldValue: any = dto[this.options.fieldName];
19
+ const fieldValue: any = normalizeTextFieldValue(dto[this.options.fieldName]);
19
20
  return this.applyValidations(fieldValue);
20
21
  }
21
22
 
@@ -36,7 +37,7 @@ export class LongTextFieldCrudManager implements FieldCrudManager {
36
37
  }
37
38
 
38
39
  transformForCreate(dto: any): any {
39
- return dto;
40
+ return normalizeTextFieldInDto(dto, this.options.fieldName);
40
41
  }
41
42
 
42
43
  // Validation to be applied
@@ -1,5 +1,5 @@
1
1
  import { FieldCrudManager, ValidationError } from "src/interfaces";
2
- import { EXT_TO_MEDIA_TYPE, getLowercaseFileExtension, getUploadedFileName, isDangerousMediaFile, MediaCategory } from "src/constants/media-file-types";
2
+ import { EXT_TO_MEDIA_TYPE, getLowercaseFileExtension, getUploadedFileName, isDangerousMediaFile, MediaCategory, MIME_TO_MEDIA_TYPE } from "src/constants/media-file-types";
3
3
 
4
4
  export enum SolidMediaType {
5
5
  mediaSingle = 'mediaSingle',
@@ -10,75 +10,24 @@ export interface MediaFieldOptions {
10
10
  type: SolidMediaType;
11
11
  required: boolean | undefined | null;
12
12
  fieldName: string | undefined | null;
13
+ fieldLabel?: string | undefined | null;
13
14
  mediaMaxSizeKb: number | undefined | null;
14
15
  mediaTypes: string[];
16
+ mediaAllowedExtensions?: string[] | null;
15
17
  isUpdate: boolean | undefined | null;
16
18
  }
17
19
 
18
20
  type MediaType = MediaCategory;
19
21
 
20
- const MIME_TO_MEDIA_TYPE: Record<string, MediaType> = {
21
- // Images
22
- 'image/png': 'image',
23
- 'image/jpeg': 'image',
24
- 'image/jpg': 'image',
25
- 'image/webp': 'image',
26
- 'image/gif': 'image',
27
- 'image/bmp': 'image',
28
- 'image/tiff': 'image',
29
- 'image/heic': 'image',
30
- 'image/heif': 'image',
31
-
32
- // Audio
33
- 'audio/mpeg': 'audio', // mp3
34
- 'audio/mp3': 'audio',
35
- 'audio/wav': 'audio',
36
- 'audio/x-wav': 'audio',
37
- 'audio/webm': 'audio',
38
- 'audio/ogg': 'audio',
39
- 'audio/aac': 'audio',
40
- 'audio/mp4': 'audio', // m4a often shows as audio/mp4
41
- 'audio/x-m4a': 'audio',
42
- 'audio/flac': 'audio',
43
-
44
- // Video
45
- 'video/mp4': 'video',
46
- 'video/mpeg': 'video',
47
- 'video/webm': 'video',
48
- 'video/ogg': 'video',
49
- 'video/quicktime': 'video', // mov
50
- 'video/x-msvideo': 'video', // avi
51
- 'video/x-matroska': 'video',// mkv
52
- 'video/3gpp': 'video',
53
- 'video/3gpp2': 'video',
54
-
55
- // Documents / files (treat as "file")
56
- 'application/pdf': 'pdf',
57
- 'text/plain': 'file',
58
- 'text/markdown': 'file',
59
- 'application/json': 'file',
60
- 'text/csv': 'file',
61
-
62
- // Office
63
- 'application/msword': 'file', // doc
64
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'file', // docx
65
- 'application/vnd.ms-excel': 'file', // xls
66
- 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'file', // xlsx
67
- 'application/vnd.ms-powerpoint': 'file', // ppt
68
- 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'file', // pptx,
69
-
70
- // Archives (optional)
71
- 'application/zip': 'file',
72
- 'application/x-zip-compressed': 'file',
73
- 'application/x-rar-compressed': 'file',
74
- 'application/x-7z-compressed': 'file',
75
- };
76
-
77
22
  export class MediaFieldCrudManager implements FieldCrudManager {
78
23
 
79
24
  constructor(private readonly options: MediaFieldOptions) {
80
25
  }
81
26
 
27
+ private getFieldLabel(): string {
28
+ return this.options.fieldLabel || this.options.fieldName || 'File';
29
+ }
30
+
82
31
  private resolveMediaType(file: Express.Multer.File): MediaType | null {
83
32
  // Belt-and-braces: dangerous files are already rejected outright by
84
33
  // validateDangerousFiles, but never let one resolve to a usable category.
@@ -86,24 +35,28 @@ export class MediaFieldCrudManager implements FieldCrudManager {
86
35
  return null;
87
36
  }
88
37
 
38
+ // Extension is authoritative: a declared mimetype is fully attacker-controlled and
39
+ // proved insufficient on its own (a .msi uploaded as image/png previously resolved to
40
+ // 'image', since the mimetype match short-circuited before the extension was ever
41
+ // checked). The extension must independently resolve to a known-safe category before
42
+ // anything is accepted - not merely "not on the denylist".
89
43
  const ext = getLowercaseFileExtension(getUploadedFileName(file));
90
- const mt = (file.mimetype || '').toLowerCase().trim();
91
- if (mt && MIME_TO_MEDIA_TYPE[mt]) {
92
- return MIME_TO_MEDIA_TYPE[mt];
44
+ const extType = ext ? EXT_TO_MEDIA_TYPE[ext] : undefined;
45
+ if (!extType) {
46
+ return null;
93
47
  }
94
48
 
95
- // Some libs may send "image/*" etc. Treat broad families safely.
96
- if (mt.startsWith('image/')) return 'image';
97
- if (mt.startsWith('audio/')) return 'audio';
98
- if (mt.startsWith('video/')) return 'video';
99
-
100
- // Fallback to extension if provided (also covers generic/unrecognized
101
- // mimetypes such as application/octet-stream).
102
- if (ext && EXT_TO_MEDIA_TYPE[ext]) {
103
- return EXT_TO_MEDIA_TYPE[ext];
49
+ // The mimetype may only confirm or veto what the extension already established, never
50
+ // grant a category on its own. A mimetype that doesn't resolve to anything (e.g.
51
+ // application/octet-stream) has no opinion and is treated as agreeing.
52
+ const mt = (file.mimetype || '').toLowerCase().trim();
53
+ const mimeType = MIME_TO_MEDIA_TYPE[mt]
54
+ ?? (mt.startsWith('image/') ? 'image' : mt.startsWith('audio/') ? 'audio' : mt.startsWith('video/') ? 'video' : undefined);
55
+ if (mimeType && mimeType !== extType) {
56
+ return null;
104
57
  }
105
58
 
106
- return null;
59
+ return extType;
107
60
  }
108
61
 
109
62
  validate(dto: any, files: Array<Express.Multer.File>): ValidationError[] {
@@ -112,18 +65,17 @@ export class MediaFieldCrudManager implements FieldCrudManager {
112
65
  }
113
66
 
114
67
  private applyValidations(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
115
- // Runs for every media field type and regardless of the admin-configured mediaTypes
116
- // allowlist - a dangerous upload is never acceptable, so this must not sit behind
117
- // the optional type checks below.
118
- const errors = this.validateDangerousFiles(fieldFiles);
68
+ // A dangerous upload is never acceptable, regardless of admin config, so this is the
69
+ // only check that sits outside validateCommon() and can't be skipped either way below.
70
+ const dangerousFileErrors = this.validateDangerousFiles(fieldFiles);
119
71
 
120
72
  switch (this.options.type) {
121
73
  case SolidMediaType.mediaSingle:
122
- return [...errors, ...this.validateMediaSingle(fieldFiles)];
74
+ return [...dangerousFileErrors, ...this.validateMediaSingle(fieldFiles)];
123
75
  case SolidMediaType.mediaMultiple:
124
- return [...errors, ...this.validateMediaMultiple(fieldFiles)];
76
+ return [...dangerousFileErrors, ...this.validateMediaMultiple(fieldFiles)];
125
77
  default:
126
- return errors;
78
+ return dangerousFileErrors;
127
79
  }
128
80
  }
129
81
 
@@ -132,26 +84,35 @@ export class MediaFieldCrudManager implements FieldCrudManager {
132
84
  .filter(fieldFile => isDangerousMediaFile(fieldFile))
133
85
  .map(fieldFile => ({
134
86
  field: this.options.fieldName,
135
- error: `${this.options.fieldName} file type not allowed. ` +
87
+ error: `${this.getFieldLabel()} file type not allowed. ` +
136
88
  `${getUploadedFileName(fieldFile) || 'File'} is a potentially dangerous file type.`
137
89
  }));
138
90
  }
139
91
 
140
- private validateMediaSingle(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
92
+ // Every optional, admin-configured, per-field check - no-ops when its setting is unset -
93
+ // shared between mediaSingle and mediaMultiple so neither can drift out of sync with it.
94
+ private validateCommon(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
95
+ return [
96
+ ...this.validateRequired(fieldFiles),
97
+ ...this.validateSize(fieldFiles),
98
+ ...this.validateTypes(fieldFiles),
99
+ ...this.validateAllowedExtensions(fieldFiles),
100
+ ];
101
+ }
102
+
103
+ private validateRequired(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
141
104
  const errors: ValidationError[] = [];
142
105
  if (!this.options.isUpdate && this.options.required && fieldFiles.length === 0) {
143
106
  errors.push({
144
107
  field: this.options.fieldName,
145
- error: `${this.options.fieldName} is required`
108
+ error: `${this.getFieldLabel()} is required`
146
109
  });
147
110
  }
148
- if (fieldFiles.length > 1) {
149
- errors.push({
150
- field: this.options.fieldName,
151
- error: `${this.options.fieldName} must be a single file`
152
- });
153
- }
154
- // validate size
111
+ return errors;
112
+ }
113
+
114
+ private validateSize(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
115
+ const errors: ValidationError[] = [];
155
116
  if (this.options.mediaMaxSizeKb) {
156
117
  for (let i = 0; i < fieldFiles.length; i++) {
157
118
  const fieldFile = fieldFiles[i];
@@ -159,12 +120,16 @@ export class MediaFieldCrudManager implements FieldCrudManager {
159
120
  if (fieldFileSizeInBytes > this.options.mediaMaxSizeKb) {
160
121
  errors.push({
161
122
  field: this.options.fieldName,
162
- error: `${this.options.fieldName} with size ${fieldFileSizeInBytes} KB exceeds max size limit of ${this.options.mediaMaxSizeKb} KB`
123
+ error: `${this.getFieldLabel()} with size ${fieldFileSizeInBytes} KB exceeds max size limit of ${this.options.mediaMaxSizeKb} KB`
163
124
  });
164
125
  }
165
126
  }
166
127
  }
167
- // validate type
128
+ return errors;
129
+ }
130
+
131
+ private validateTypes(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
132
+ const errors: ValidationError[] = [];
168
133
  if (this.options.mediaTypes && this.options.mediaTypes.length > 0) {
169
134
  const allowedFileTypes = this.options.mediaTypes as MediaType[];
170
135
 
@@ -175,28 +140,50 @@ export class MediaFieldCrudManager implements FieldCrudManager {
175
140
  if (!resolvedType || !allowedFileTypes.includes(resolvedType)) {
176
141
  errors.push({
177
142
  field: this.options.fieldName,
178
- error: `${this.options.fieldName} file type not allowed. ` +
143
+ error: `${this.getFieldLabel()} file type not allowed. ` +
179
144
  `Allowed: ${allowedFileTypes.join(', ')}. ` +
180
145
  `Received mimetype: ${fieldFile.mimetype}${resolvedType ? ` (mapped to ${resolvedType})` : ''}`
181
146
  });
182
147
  }
183
148
  }
184
149
  }
185
-
186
150
  return errors;
187
151
  }
188
152
 
189
- private validateMediaMultiple(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
190
- const errors: ValidationError[] = [];
191
- if (!this.options.isUpdate && this.options.required && fieldFiles.length === 0) {
153
+ private validateAllowedExtensions(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
154
+ const allowed = this.options.mediaAllowedExtensions;
155
+ if (!allowed || allowed.length === 0) {
156
+ return [];
157
+ }
158
+
159
+ const normalizedAllowed = new Set(allowed.map(ext => ext.toLowerCase().replace(/^\./, '')));
160
+ return fieldFiles
161
+ .filter(fieldFile => {
162
+ const ext = getLowercaseFileExtension(getUploadedFileName(fieldFile));
163
+ return !ext || !normalizedAllowed.has(ext);
164
+ })
165
+ .map(fieldFile => ({
166
+ field: this.options.fieldName,
167
+ error: `${this.getFieldLabel()} file type not allowed. ` +
168
+ `Allowed extensions: ${[...normalizedAllowed].join(', ')}.`
169
+ }));
170
+ }
171
+
172
+ private validateMediaSingle(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
173
+ const errors = this.validateCommon(fieldFiles);
174
+ if (fieldFiles.length > 1) {
192
175
  errors.push({
193
176
  field: this.options.fieldName,
194
- error: `${this.options.fieldName} is required`
177
+ error: `${this.getFieldLabel()} must be a single file`
195
178
  });
196
179
  }
197
180
  return errors;
198
181
  }
199
182
 
183
+ private validateMediaMultiple(fieldFiles: Array<Express.Multer.File>): ValidationError[] {
184
+ return this.validateCommon(fieldFiles);
185
+ }
186
+
200
187
  transformForCreate(dto: any): any {
201
188
  return dto;
202
189
  }