@solidxai/core 0.1.4 → 0.1.5-beta.1

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 (251) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/dist/constants/error-messages.d.ts +1 -0
  3. package/dist/constants/error-messages.d.ts.map +1 -1
  4. package/dist/constants/error-messages.js +1 -0
  5. package/dist/constants/error-messages.js.map +1 -1
  6. package/dist/constants.d.ts +3 -3
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/constants.js +12 -12
  9. package/dist/constants.js.map +1 -1
  10. package/dist/controllers/otp-authentication.controller.d.ts +1 -4
  11. package/dist/controllers/otp-authentication.controller.d.ts.map +1 -1
  12. package/dist/controllers/otp-authentication.controller.js +1 -1
  13. package/dist/controllers/role-metadata.controller.d.ts +1 -0
  14. package/dist/controllers/role-metadata.controller.d.ts.map +1 -1
  15. package/dist/controllers/role-metadata.controller.js +15 -0
  16. package/dist/controllers/role-metadata.controller.js.map +1 -1
  17. package/dist/controllers/view-metadata.controller.d.ts +1 -0
  18. package/dist/controllers/view-metadata.controller.d.ts.map +1 -1
  19. package/dist/dtos/create-email-template.dto.d.ts.map +1 -1
  20. package/dist/dtos/create-email-template.dto.js.map +1 -1
  21. package/dist/dtos/create-list-of-values.dto.d.ts.map +1 -1
  22. package/dist/dtos/create-list-of-values.dto.js.map +1 -1
  23. package/dist/dtos/create-menu-item-metadata.dto.d.ts.map +1 -1
  24. package/dist/dtos/create-menu-item-metadata.dto.js.map +1 -1
  25. package/dist/dtos/create-role-metadata.dto.d.ts.map +1 -1
  26. package/dist/dtos/create-role-metadata.dto.js.map +1 -1
  27. package/dist/dtos/create-saved-filters.dto.d.ts +1 -0
  28. package/dist/dtos/create-saved-filters.dto.d.ts.map +1 -1
  29. package/dist/dtos/create-saved-filters.dto.js +8 -1
  30. package/dist/dtos/create-saved-filters.dto.js.map +1 -1
  31. package/dist/dtos/create-scheduled-job.dto.d.ts.map +1 -1
  32. package/dist/dtos/create-scheduled-job.dto.js.map +1 -1
  33. package/dist/dtos/create-security-rule.dto.d.ts.map +1 -1
  34. package/dist/dtos/create-security-rule.dto.js.map +1 -1
  35. package/dist/dtos/create-sms-template.dto.d.ts.map +1 -1
  36. package/dist/dtos/create-sms-template.dto.js.map +1 -1
  37. package/dist/dtos/create-view-metadata.dto.d.ts.map +1 -1
  38. package/dist/dtos/create-view-metadata.dto.js.map +1 -1
  39. package/dist/dtos/otp-sign-in.dto.d.ts +1 -1
  40. package/dist/dtos/otp-sign-in.dto.d.ts.map +1 -1
  41. package/dist/dtos/otp-sign-in.dto.js +2 -2
  42. package/dist/dtos/otp-sign-in.dto.js.map +1 -1
  43. package/dist/dtos/otp-sign-up.dto.d.ts +2 -2
  44. package/dist/dtos/otp-sign-up.dto.d.ts.map +1 -1
  45. package/dist/dtos/otp-sign-up.dto.js +2 -2
  46. package/dist/dtos/otp-sign-up.dto.js.map +1 -1
  47. package/dist/dtos/resolve-s3-url.dto.d.ts +2 -5
  48. package/dist/dtos/resolve-s3-url.dto.d.ts.map +1 -1
  49. package/dist/dtos/resolve-s3-url.dto.js +1 -13
  50. package/dist/dtos/resolve-s3-url.dto.js.map +1 -1
  51. package/dist/dtos/sign-up.dto.d.ts.map +1 -1
  52. package/dist/dtos/sign-up.dto.js.map +1 -1
  53. package/dist/dtos/update-email-template.dto.d.ts.map +1 -1
  54. package/dist/dtos/update-email-template.dto.js.map +1 -1
  55. package/dist/dtos/update-list-of-values.dto.d.ts.map +1 -1
  56. package/dist/dtos/update-list-of-values.dto.js.map +1 -1
  57. package/dist/dtos/update-menu-item-metadata.dto.d.ts.map +1 -1
  58. package/dist/dtos/update-menu-item-metadata.dto.js.map +1 -1
  59. package/dist/dtos/update-saved-filters.dto.d.ts +1 -0
  60. package/dist/dtos/update-saved-filters.dto.d.ts.map +1 -1
  61. package/dist/dtos/update-saved-filters.dto.js +10 -1
  62. package/dist/dtos/update-saved-filters.dto.js.map +1 -1
  63. package/dist/dtos/update-scheduled-job.dto.d.ts.map +1 -1
  64. package/dist/dtos/update-scheduled-job.dto.js.map +1 -1
  65. package/dist/dtos/update-security-rule.dto.d.ts.map +1 -1
  66. package/dist/dtos/update-security-rule.dto.js.map +1 -1
  67. package/dist/dtos/update-sms-template.dto.d.ts.map +1 -1
  68. package/dist/dtos/update-sms-template.dto.js.map +1 -1
  69. package/dist/dtos/update-view-metadata.dto.d.ts.map +1 -1
  70. package/dist/dtos/update-view-metadata.dto.js.map +1 -1
  71. package/dist/entities/chatter-message-details.entity.d.ts.map +1 -1
  72. package/dist/entities/chatter-message-details.entity.js +1 -0
  73. package/dist/entities/chatter-message-details.entity.js.map +1 -1
  74. package/dist/entities/chatter-message.entity.d.ts.map +1 -1
  75. package/dist/entities/chatter-message.entity.js +1 -0
  76. package/dist/entities/chatter-message.entity.js.map +1 -1
  77. package/dist/entities/common.entity.js +4 -4
  78. package/dist/entities/common.entity.js.map +1 -1
  79. package/dist/entities/legacy-common.entity.js +4 -4
  80. package/dist/entities/legacy-common.entity.js.map +1 -1
  81. package/dist/entities/saved-filters.entity.d.ts +1 -0
  82. package/dist/entities/saved-filters.entity.d.ts.map +1 -1
  83. package/dist/entities/saved-filters.entity.js +6 -1
  84. package/dist/entities/saved-filters.entity.js.map +1 -1
  85. package/dist/entities/user.entity.d.ts +1 -0
  86. package/dist/entities/user.entity.d.ts.map +1 -1
  87. package/dist/entities/user.entity.js +6 -1
  88. package/dist/entities/user.entity.js.map +1 -1
  89. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.d.ts +2 -0
  90. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.d.ts.map +1 -1
  91. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.js +33 -23
  92. package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.js.map +1 -1
  93. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.d.ts +3 -0
  94. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.d.ts.map +1 -1
  95. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.js +36 -23
  96. package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.js.map +1 -1
  97. package/dist/helpers/security.helper.js +1 -0
  98. package/dist/helpers/security.helper.js.map +1 -1
  99. package/dist/index.d.ts +0 -4
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +0 -4
  102. package/dist/index.js.map +1 -1
  103. package/dist/repository/solid-base.repository.d.ts +10 -1
  104. package/dist/repository/solid-base.repository.d.ts.map +1 -1
  105. package/dist/repository/solid-base.repository.js +109 -0
  106. package/dist/repository/solid-base.repository.js.map +1 -1
  107. package/dist/seeders/module-metadata-seeder.service.d.ts +2 -0
  108. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  109. package/dist/seeders/module-metadata-seeder.service.js +142 -72
  110. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  111. package/dist/seeders/permission-metadata-seeder.service.d.ts +1 -1
  112. package/dist/seeders/permission-metadata-seeder.service.d.ts.map +1 -1
  113. package/dist/seeders/permission-metadata-seeder.service.js +1 -1
  114. package/dist/seeders/permission-metadata-seeder.service.js.map +1 -1
  115. package/dist/seeders/seed-data/solid-core-metadata.json +23 -25
  116. package/dist/services/authentication.service.d.ts +22 -8
  117. package/dist/services/authentication.service.d.ts.map +1 -1
  118. package/dist/services/authentication.service.js +230 -214
  119. package/dist/services/authentication.service.js.map +1 -1
  120. package/dist/services/crud-helper.service.d.ts +4 -0
  121. package/dist/services/crud-helper.service.d.ts.map +1 -1
  122. package/dist/services/crud-helper.service.js +66 -32
  123. package/dist/services/crud-helper.service.js.map +1 -1
  124. package/dist/services/crud.service.d.ts.map +1 -1
  125. package/dist/services/crud.service.js +7 -4
  126. package/dist/services/crud.service.js.map +1 -1
  127. package/dist/services/field-metadata.service.d.ts.map +1 -1
  128. package/dist/services/field-metadata.service.js.map +1 -1
  129. package/dist/services/file/disk-file.service.d.ts +0 -2
  130. package/dist/services/file/disk-file.service.d.ts.map +1 -1
  131. package/dist/services/file/disk-file.service.js +7 -16
  132. package/dist/services/file/disk-file.service.js.map +1 -1
  133. package/dist/services/file/index.d.ts +1 -0
  134. package/dist/services/file/index.d.ts.map +1 -1
  135. package/dist/services/file/index.js +1 -0
  136. package/dist/services/file/index.js.map +1 -1
  137. package/dist/services/file/storage-path-builder.d.ts +17 -0
  138. package/dist/services/file/storage-path-builder.d.ts.map +1 -0
  139. package/dist/{seeders/sms-template-seeder.service.js → services/file/storage-path-builder.js} +45 -35
  140. package/dist/services/file/storage-path-builder.js.map +1 -0
  141. package/dist/services/media.service.d.ts +1 -1
  142. package/dist/services/media.service.d.ts.map +1 -1
  143. package/dist/services/media.service.js +45 -6
  144. package/dist/services/media.service.js.map +1 -1
  145. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
  146. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
  147. package/dist/services/mediaStorageProviders/file-storage-provider.js +46 -7
  148. package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
  149. package/dist/services/module-metadata.service.d.ts +4 -6
  150. package/dist/services/module-metadata.service.d.ts.map +1 -1
  151. package/dist/services/module-metadata.service.js +16 -14
  152. package/dist/services/module-metadata.service.js.map +1 -1
  153. package/dist/services/setting.service.d.ts +3 -2
  154. package/dist/services/setting.service.d.ts.map +1 -1
  155. package/dist/services/setting.service.js +7 -4
  156. package/dist/services/setting.service.js.map +1 -1
  157. package/dist/services/settings/default-settings-provider.service.d.ts +24 -2
  158. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  159. package/dist/services/settings/default-settings-provider.service.js +8 -6
  160. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  161. package/dist/services/view-metadata.service.d.ts +1 -0
  162. package/dist/services/view-metadata.service.d.ts.map +1 -1
  163. package/dist/services/view-metadata.service.js +25 -1
  164. package/dist/services/view-metadata.service.js.map +1 -1
  165. package/dist/solid-core.module.d.ts +3 -1
  166. package/dist/solid-core.module.d.ts.map +1 -1
  167. package/dist/solid-core.module.js +45 -9
  168. package/dist/solid-core.module.js.map +1 -1
  169. package/dist/testing/adapters/ui/playwright-adapter.d.ts.map +1 -1
  170. package/dist/testing/adapters/ui/playwright-adapter.js +35 -2
  171. package/dist/testing/adapters/ui/playwright-adapter.js.map +1 -1
  172. package/dist/transformers/typeorm/local-date-time-transformer.d.ts +4 -3
  173. package/dist/transformers/typeorm/local-date-time-transformer.d.ts.map +1 -1
  174. package/dist/transformers/typeorm/local-date-time-transformer.js +20 -2
  175. package/dist/transformers/typeorm/local-date-time-transformer.js.map +1 -1
  176. package/package.json +8 -2
  177. package/src/constants/error-messages.ts +1 -0
  178. package/src/constants.ts +3 -3
  179. package/src/controllers/role-metadata.controller.ts +26 -18
  180. package/src/dtos/create-email-template.dto.ts +7 -0
  181. package/src/dtos/create-list-of-values.dto.ts +7 -0
  182. package/src/dtos/create-menu-item-metadata.dto.ts +12 -1
  183. package/src/dtos/create-role-metadata.dto.ts +9 -0
  184. package/src/dtos/create-saved-filters.dto.ts +7 -0
  185. package/src/dtos/create-scheduled-job.dto.ts +14 -0
  186. package/src/dtos/create-security-rule.dto.ts +6 -0
  187. package/src/dtos/create-sms-template.dto.ts +6 -0
  188. package/src/dtos/create-view-metadata.dto.ts +11 -0
  189. package/src/dtos/otp-sign-in.dto.ts +3 -3
  190. package/src/dtos/otp-sign-up.dto.ts +3 -3
  191. package/src/dtos/resolve-s3-url.dto.ts +2 -12
  192. package/src/dtos/sign-up.dto.ts +0 -2
  193. package/src/dtos/update-email-template.dto.ts +6 -0
  194. package/src/dtos/update-list-of-values.dto.ts +8 -0
  195. package/src/dtos/update-menu-item-metadata.dto.ts +12 -0
  196. package/src/dtos/update-saved-filters.dto.ts +5 -1
  197. package/src/dtos/update-scheduled-job.dto.ts +15 -0
  198. package/src/dtos/update-security-rule.dto.ts +7 -0
  199. package/src/dtos/update-sms-template.dto.ts +32 -32
  200. package/src/dtos/update-view-metadata.dto.ts +12 -0
  201. package/src/entities/chatter-message-details.entity.ts +1 -0
  202. package/src/entities/chatter-message.entity.ts +1 -0
  203. package/src/entities/common.entity.ts +5 -5
  204. package/src/entities/legacy-common.entity.ts +5 -5
  205. package/src/entities/saved-filters.entity.ts +3 -0
  206. package/src/entities/user.entity.ts +4 -1
  207. package/src/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.ts +43 -32
  208. package/src/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.ts +45 -33
  209. package/src/helpers/security.helper.ts +1 -1
  210. package/src/index.ts +0 -4
  211. package/src/repository/solid-base.repository.ts +172 -1
  212. package/src/seeders/module-metadata-seeder.service.ts +189 -127
  213. package/src/seeders/permission-metadata-seeder.service.ts +1 -4
  214. package/src/seeders/seed-data/solid-core-metadata.json +30 -32
  215. package/src/services/authentication.service.ts +273 -269
  216. package/src/services/crud-helper.service.ts +79 -36
  217. package/src/services/crud.service.ts +9 -4
  218. package/src/services/field-metadata.service.ts +0 -71
  219. package/src/services/file/disk-file.service.ts +8 -18
  220. package/src/services/file/index.ts +1 -0
  221. package/src/services/file/storage-path-builder.ts +56 -0
  222. package/src/services/media.service.ts +13 -7
  223. package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +1 -1
  224. package/src/services/mediaStorageProviders/file-storage-provider.ts +13 -8
  225. package/src/services/module-metadata.service.ts +18 -15
  226. package/src/services/setting.service.ts +5 -3
  227. package/src/services/settings/default-settings-provider.service.ts +5 -3
  228. package/src/services/view-metadata.service.ts +29 -1
  229. package/src/solid-core.module.ts +16 -12
  230. package/src/testing/adapters/ui/playwright-adapter.ts +1 -1
  231. package/src/transformers/typeorm/local-date-time-transformer.ts +21 -3
  232. package/dist/passport-strategies/local.strategy.d.ts +0 -15
  233. package/dist/passport-strategies/local.strategy.d.ts.map +0 -1
  234. package/dist/passport-strategies/local.strategy.js +0 -44
  235. package/dist/passport-strategies/local.strategy.js.map +0 -1
  236. package/dist/seeders/email-template-seeder.service.d.ts +0 -10
  237. package/dist/seeders/email-template-seeder.service.d.ts.map +0 -1
  238. package/dist/seeders/email-template-seeder.service.js +0 -84
  239. package/dist/seeders/email-template-seeder.service.js.map +0 -1
  240. package/dist/seeders/sms-template-seeder.service.d.ts +0 -10
  241. package/dist/seeders/sms-template-seeder.service.d.ts.map +0 -1
  242. package/dist/seeders/sms-template-seeder.service.js.map +0 -1
  243. package/dist/seeders/user-seeder.service.d.ts +0 -10
  244. package/dist/seeders/user-seeder.service.d.ts.map +0 -1
  245. package/dist/seeders/user-seeder.service.js +0 -44
  246. package/dist/seeders/user-seeder.service.js.map +0 -1
  247. package/src/passport-strategies/local.strategy.ts +0 -28
  248. package/src/seeders/email-template-seeder.service.ts +0 -49
  249. package/src/seeders/sms-template-seeder.service.ts +0 -50
  250. package/src/seeders/user-seeder.service.ts +0 -33
  251. package/src/workflow.readme.md +0 -25
@@ -3,7 +3,7 @@ import { BasicFilterDto } from "../dtos/basic-filters.dto";
3
3
  import { classify } from "@angular-devkit/core/src/utils/strings";
4
4
  import { ActiveUserData } from "src/interfaces/active-user-data.interface";
5
5
  import { SolidRegistry } from "src/helpers/solid-registry";
6
- import { Logger } from "@nestjs/common";
6
+ import { BadRequestException, Logger } from "@nestjs/common";
7
7
  import { ERROR_MESSAGES } from "src/constants/error-messages";
8
8
 
9
9
  export enum FilterCombinator {
@@ -64,88 +64,102 @@ export class CrudHelperService {
64
64
  const primaryFilterObj = normalizedFilters[key];
65
65
  const normalizedPrimaryFilterObj = this.normalizeObjectKeys(primaryFilterObj);
66
66
 
67
+ const [rawField, funcAlias] = key.split(':');
68
+
67
69
  // Get the operator or field from the key
68
70
  const operatorOrField = Object.keys(normalizedPrimaryFilterObj)[0];
69
71
  // if the key is an operator, then build the query based on the operator
70
72
  if (operatorOrField.startsWith('$')) {
71
73
  const operator = operatorOrField;
72
- this.buildOperatorQuery(qb, alias, key, normalizedPrimaryFilterObj, operator);
74
+ let columnExpression: string | undefined;
75
+ if (funcAlias) {
76
+ try {
77
+ columnExpression = this.buildDateGranularityExpression(this.getDriver(selectQb), `${alias}.${rawField}`, funcAlias);
78
+ } catch {
79
+ throw new BadRequestException(`Unsupported field function '${funcAlias}'. Supported functions are: day, week, month, year.`);
80
+ }
81
+ }
82
+ this.buildOperatorQuery(qb, alias, rawField, normalizedPrimaryFilterObj, operator, columnExpression);
73
83
  return;
74
84
  }
75
85
  else { // Recursively call the applyFilters method to handle nested conditions
76
- const joinField = `${alias}.${key}`;
77
- if (!this.isRelationJoined(selectQb, joinField)) selectQb.leftJoin(joinField, key);
78
- this.applyFilters(qb, primaryFilterObj, key, selectQb);
86
+ if (funcAlias) {
87
+ throw new BadRequestException(`Function alias ':${funcAlias}' is not valid on relation field '${rawField}'. It can only be applied to scalar fields.`);
88
+ }
89
+ const joinField = `${alias}.${rawField}`;
90
+ if (!this.isRelationJoined(selectQb, joinField)) selectQb.leftJoin(joinField, rawField);
91
+ this.applyFilters(qb, primaryFilterObj, rawField, selectQb);
79
92
  }
80
93
  });
81
94
  }
82
95
  }
83
96
 
84
- private buildOperatorQuery(qb: any, alias: string, field: string, normalizedPrimaryOperatorObj: any, operator: string) {
97
+ private buildOperatorQuery(qb: any, alias: string, field: string, normalizedPrimaryOperatorObj: any, operator: string, columnExpression?: string) {
85
98
  const uniqueFieldAlias = `${alias}_${field}_${Math.floor(Math.random() * 1000)}`;
99
+ const colExpr = columnExpression ?? `${alias}.${field}`;
86
100
  switch (operator) {
87
101
  case '$eq':
88
- qb.andWhere(`${alias}.${field} = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eq });
102
+ qb.andWhere(`${colExpr} = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eq });
89
103
  break;
90
104
  case '$eqi':
91
- qb.andWhere(`LOWER(${alias}.${field}) = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eqi.toLowerCase() });
105
+ qb.andWhere(`LOWER(${colExpr}) = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eqi.toLowerCase() });
92
106
  break;
93
107
  case '$ne':
94
- qb.andWhere(`${alias}.${field} != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$ne });
108
+ qb.andWhere(`${colExpr} != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$ne });
95
109
  break;
96
110
  case '$nei':
97
- qb.andWhere(`LOWER(${alias}.${field}) != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$nei.toLowerCase() });
111
+ qb.andWhere(`LOWER(${colExpr}) != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$nei.toLowerCase() });
98
112
  break;
99
113
  case '$gt':
100
- qb.andWhere(`${alias}.${field} > :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gt });
114
+ qb.andWhere(`${colExpr} > :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gt });
101
115
  break;
102
116
  case '$gte':
103
- qb.andWhere(`${alias}.${field} >= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gte });
117
+ qb.andWhere(`${colExpr} >= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gte });
104
118
  break;
105
119
  case '$lt':
106
- qb.andWhere(`${alias}.${field} < :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lt });
120
+ qb.andWhere(`${colExpr} < :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lt });
107
121
  break;
108
122
  case '$lte':
109
- qb.andWhere(`${alias}.${field} <= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lte });
123
+ qb.andWhere(`${colExpr} <= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lte });
110
124
  break;
111
125
  case '$in':
112
- qb.andWhere(`${alias}.${field} IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$in });
126
+ qb.andWhere(`${colExpr} IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$in });
113
127
  break;
114
128
  case '$notIn':
115
- qb.andWhere(`${alias}.${field} NOT IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$notIn });
129
+ qb.andWhere(`${colExpr} NOT IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$notIn });
116
130
  break;
117
131
  case '$contains':
118
- qb.andWhere(`${alias}.${field} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$contains}%` });
132
+ qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$contains}%` });
119
133
  break;
120
134
  case '$notContains':
121
- qb.andWhere(`${alias}.${field} NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContains}%` });
135
+ qb.andWhere(`${colExpr} NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContains}%` });
122
136
  break;
123
137
  case '$containsi':
124
- qb.andWhere(`LOWER(${alias}.${field}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$containsi.toLowerCase()}%` });
138
+ qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$containsi.toLowerCase()}%` });
125
139
  break;
126
140
  case '$notContainsi':
127
- qb.andWhere(`LOWER(${alias}.${field}) NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContainsi.toLowerCase()}%` });
141
+ qb.andWhere(`LOWER(${colExpr}) NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContainsi.toLowerCase()}%` });
128
142
  break;
129
143
  case '$null':
130
- qb.andWhere(`${alias}.${field} IS NULL`);
144
+ qb.andWhere(`${colExpr} IS NULL`);
131
145
  break;
132
146
  case '$notNull':
133
- qb.andWhere(`${alias}.${field} IS NOT NULL`);
147
+ qb.andWhere(`${colExpr} IS NOT NULL`);
134
148
  break;
135
149
  case '$between':
136
- qb.andWhere(`${alias}.${field} BETWEEN :${uniqueFieldAlias}0 AND :${uniqueFieldAlias}1`, { [`${uniqueFieldAlias}0`]: normalizedPrimaryOperatorObj.$between[0], [`${uniqueFieldAlias}1`]: normalizedPrimaryOperatorObj.$between[1] });
150
+ qb.andWhere(`${colExpr} BETWEEN :${uniqueFieldAlias}0 AND :${uniqueFieldAlias}1`, { [`${uniqueFieldAlias}0`]: normalizedPrimaryOperatorObj.$between[0], [`${uniqueFieldAlias}1`]: normalizedPrimaryOperatorObj.$between[1] });
137
151
  break;
138
152
  case '$startsWith':
139
- qb.andWhere(`${alias}.${field} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWith}%` });
153
+ qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWith}%` });
140
154
  break;
141
155
  case '$startsWithi':
142
- qb.andWhere(`LOWER(${alias}.${field}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWithi.toLowerCase()}%` });
156
+ qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWithi.toLowerCase()}%` });
143
157
  break;
144
158
  case '$endsWith':
145
- qb.andWhere(`${alias}.${field} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWith}` });
159
+ qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWith}` });
146
160
  break;
147
161
  case '$endsWithi':
148
- qb.andWhere(`LOWER(${alias}.${field}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWithi.toLowerCase()}` });
162
+ qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWithi.toLowerCase()}` });
149
163
  break;
150
164
  default:
151
165
  throw new Error(`Operator ${operator} is not supported`);
@@ -588,6 +602,27 @@ export class CrudHelperService {
588
602
  }
589
603
  }
590
604
 
605
+ private getGroupFieldValues(
606
+ group: any,
607
+ groupByFields: string[],
608
+ groupAliasMap: Record<string, string>
609
+ ): Array<{ rawVal: any; alias: string; granularity?: string }> {
610
+ return groupByFields
611
+ .map(field => {
612
+ const parts = field.split(':');
613
+ const granularity = parts[1];
614
+ const alias = groupAliasMap[field] ?? this.sanitizeAlias(field.replace(/\./g, '_'));
615
+ const rawVal = group[alias] ?? group[field] ?? group[field.replace(/\./g, '_')];
616
+ return { rawVal, alias, granularity };
617
+ })
618
+ .filter(({ rawVal }) => rawVal !== undefined && rawVal !== null);
619
+ }
620
+
621
+ private normalizeGroupValue(value: any, granularity?: string): any {
622
+ if (!granularity) return value;
623
+ return this.formatGroupValue(value, 'YYYY-MM-DD');
624
+ }
625
+
591
626
  getGroupName(
592
627
  group: any,
593
628
  aggregateAliases: Set<string>,
@@ -595,22 +630,28 @@ export class CrudHelperService {
595
630
  groupAliasMap: Record<string, string>,
596
631
  groupFormatMap: Record<string, string | undefined>
597
632
  ): string {
598
- const orderedValues = groupByFields
599
- .map(field => {
600
- const alias = groupAliasMap[field] ?? this.sanitizeAlias(field.replace(/\./g, '_'));
601
- const rawVal = group[alias] ?? group[field] ?? group[field.replace(/\./g, '_')];
602
- return this.formatGroupValue(rawVal, groupFormatMap[alias]);
603
- })
604
- .filter(v => v !== undefined && v !== null);
633
+ const fieldValues = this.getGroupFieldValues(group, groupByFields, groupAliasMap);
605
634
 
606
- if (orderedValues.length === 0) {
635
+ if (fieldValues.length === 0) {
607
636
  return Object.keys(group)
608
637
  .filter(key => !this.isAggregateFieldKey(key, aggregateAliases))
609
638
  .map(key => group[key])
610
639
  .join('_');
611
640
  }
612
641
 
613
- return orderedValues.join('_');
642
+ return fieldValues
643
+ .map(({ rawVal, alias }) => this.formatGroupValue(rawVal, groupFormatMap[alias]))
644
+ .join('_');
645
+ }
646
+
647
+ getGroupValue(
648
+ group: any,
649
+ groupByFields: string[],
650
+ groupAliasMap: Record<string, string>
651
+ ): any {
652
+ const fieldValues = this.getGroupFieldValues(group, groupByFields, groupAliasMap);
653
+ if (fieldValues.length === 1) return this.normalizeGroupValue(fieldValues[0].rawVal, fieldValues[0].granularity);
654
+ return fieldValues.map(({ rawVal, granularity }) => this.normalizeGroupValue(rawVal, granularity)).join('_');
614
655
  }
615
656
 
616
657
  createGroupRecords(group: any, aggregateAliases: Set<string>, groupData: any, groupByFields: string[], groupAliasMap: Record<string, string>, groupFormatMap: Record<string, string | undefined>) {
@@ -622,6 +663,7 @@ export class CrudHelperService {
622
663
  }
623
664
  createGroupMeta(group: any, aggregateAliases: Set<string>, groupByFields: string[], groupAliasMap: Record<string, string>, groupFormatMap: Record<string, string | undefined>) {
624
665
  const groupName = this.getGroupName(group, aggregateAliases, groupByFields, groupAliasMap, groupFormatMap);
666
+ const groupValue = this.getGroupValue(group, groupByFields, groupAliasMap);
625
667
  const groupAggregateValues = {}
626
668
  for (const key in group) {
627
669
  if (group.hasOwnProperty(key) && this.isAggregateFieldKey(key, aggregateAliases)) {
@@ -631,6 +673,7 @@ export class CrudHelperService {
631
673
  }
632
674
  return {
633
675
  groupName,
676
+ groupValue,
634
677
  ...groupAggregateValues
635
678
  };
636
679
  }
@@ -129,8 +129,8 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
129
129
  });
130
130
  }
131
131
 
132
- private async validateAndTransformDto(field: FieldMetadata, dto: any, files: Express.Multer.File[], hasMediaFields: boolean, isPartialUpdate: boolean = false, isUpdate: boolean = false) {
133
- const fieldManager: FieldCrudManager = await this.fieldCrudManager(field, this.entityManager, isPartialUpdate, isUpdate);
132
+ private async validateAndTransformDto(field: FieldMetadata, dto: any, files: Express.Multer.File[], hasMediaFields: boolean, isPartialUpdate: boolean = false, isUpdate: boolean = false, entityId?: number) {
133
+ const fieldManager: FieldCrudManager = await this.fieldCrudManager(field, this.entityManager, isPartialUpdate, isUpdate, entityId);
134
134
  const validationErrors = fieldManager.validate(dto, files);
135
135
  const errors = (validationErrors instanceof Promise) ? await validationErrors : validationErrors;
136
136
  if (errors.length > 0) {
@@ -211,7 +211,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
211
211
  // 2. Loop through the fields with a switch statement
212
212
  // 3. Handle the fields based on field type
213
213
  for (const field of fieldsToProcess) {
214
- const transformed = await this.validateAndTransformDto(field, updateDto, files, hasMediaFields, isPartialUpdate, isUpdate);
214
+ const transformed = await this.validateAndTransformDto(field, updateDto, files, hasMediaFields, isPartialUpdate, isUpdate, id);
215
215
  updateDto = transformed.dto;
216
216
  hasMediaFields = transformed.hasMediaFields;
217
217
  }
@@ -221,6 +221,8 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
221
221
  const mergedEntity = this.repo.merge(entity, updateDto);
222
222
  const savedEntity = await this.repo.save(mergedEntity) as T;
223
223
 
224
+ //FIXME: Skip the many-to-many, and instead fire differential updates and avoid loading the entire association graph for the ids
225
+
224
226
  // 6. Save the media
225
227
  if (hasMediaFields) {
226
228
  await this.saveMedia(model, files, savedEntity);
@@ -287,7 +289,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
287
289
  }
288
290
  }
289
291
 
290
- private async fieldCrudManager(fieldMetadata: FieldMetadata, entityManager: EntityManager, isPartialUpdate: boolean = false, isUpdate: boolean = false) {
292
+ private async fieldCrudManager(fieldMetadata: FieldMetadata, entityManager: EntityManager, isPartialUpdate: boolean = false, isUpdate: boolean = false, entityId?: number) {
291
293
  const commonOptions = { required: fieldMetadata.required && !isPartialUpdate, fieldName: fieldMetadata.name, isUpdate };
292
294
  switch (fieldMetadata.type) {
293
295
  case SolidFieldType.shortText: {
@@ -378,6 +380,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
378
380
  entityManager,
379
381
  inverseFieldName: fieldMetadata.relationCoModelFieldName,
380
382
  inverseRelationCoModelFieldName: fieldMetadata.name,
383
+ entityId,
381
384
  }
382
385
  return new OneToManyRelationFieldCrudManager(oneToManyOptions);
383
386
  }
@@ -390,6 +393,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
390
393
  isInverseSide: false,
391
394
  entityManager,
392
395
  fieldName: fieldMetadata.name,
396
+ entityId,
393
397
  }
394
398
  return new ManyToManyRelationFieldCrudManager(manyToManyOptions);
395
399
  }
@@ -402,6 +406,7 @@ export class CRUDService<T extends CommonEntity> { // Add two generic value i.e
402
406
  entityManager,
403
407
  fieldName: fieldMetadata.relationCoModelFieldName,
404
408
  relationCoModelFieldName: fieldMetadata.name,
409
+ entityId,
405
410
  }
406
411
  return new ManyToManyRelationFieldCrudManager(inverseManyToManyOptions);
407
412
  }
@@ -1290,77 +1290,6 @@ export class FieldMetadataService implements OnApplicationBootstrap {
1290
1290
  return fieldObject;
1291
1291
  }
1292
1292
 
1293
- // async resolveS3Url(resolveS3UrlDto: ResolveS3UrlDto) {
1294
-
1295
- // const { modelName, fieldName, fieldValue, s3KeyFieldName } = resolveS3UrlDto;
1296
-
1297
- // // ------------------------------------------------
1298
- // // 1. Load model metadata
1299
- // // ------------------------------------------------
1300
- // const modelRepo = this.dataSource.getRepository(ModelMetadata);
1301
- // const model = await modelRepo.findOne({
1302
- // where: { singularName: modelName },
1303
- // relations: ['fields']
1304
- // });
1305
-
1306
- // if (!model) {
1307
- // throw new NotFoundException(`Model ${modelName} not found`);
1308
- // }
1309
-
1310
- // // ------------------------------------------------
1311
- // // 2. Validate the field we are filtering by
1312
- // // ------------------------------------------------
1313
- // const filterFieldMeta = model.fields.find(f => f.name === fieldName);
1314
- // if (!filterFieldMeta) {
1315
- // throw new NotFoundException(
1316
- // `Field ${fieldName} not found in model ${modelName}`
1317
- // );
1318
- // }
1319
-
1320
- // // ------------------------------------------------
1321
- // // 3. Load the actual entity repository
1322
- // // ------------------------------------------------
1323
- // const entityRepo = this.dataSource.getRepository(model.singularName);
1324
-
1325
- // // ------------------------------------------------
1326
- // // 4. Query using fieldName = fieldValue
1327
- // // ------------------------------------------------
1328
- // const record = await entityRepo.findOne({
1329
- // where: { [fieldName]: fieldValue }
1330
- // });
1331
-
1332
- // if (!record) {
1333
- // throw new NotFoundException(
1334
- // `${modelName} record not found for ${fieldName}="${fieldValue}"`
1335
- // );
1336
- // }
1337
-
1338
- // // ------------------------------------------------
1339
- // // 5. Extract S3 key from s3KeyFieldName
1340
- // // ------------------------------------------------
1341
- // const s3Key = record[s3KeyFieldName];
1342
-
1343
- // if (!s3Key) {
1344
- // throw new NotFoundException(
1345
- // `Field "${s3KeyFieldName}" has no value in ${modelName}.${fieldName}="${fieldValue}"`
1346
- // );
1347
- // }
1348
-
1349
- // // ------------------------------------------------
1350
- // // 6. Generate signed or public URL
1351
- // // ------------------------------------------------
1352
- // let url = "";
1353
-
1354
- // // TODO - get
1355
- // if (resolveS3UrlDto.isPrivate == "true") {
1356
- // const expiryInSeconds = 60 * 60;
1357
- // url = await this.fileService.getSignedUrl(s3Key, expiryInSeconds, resolveS3UrlDto.bucketName);
1358
- // } else {
1359
- // url = `https://${resolveS3UrlDto.bucketName}.s3.${this.configService.get('S3_AWS_REGION_NAME')}.amazonaws.com/${s3Key}`;
1360
- // }
1361
- // return { url: url }
1362
- // }
1363
-
1364
1293
  async resolveS3Url(resolveS3UrlDto: ResolveS3UrlDto) {
1365
1294
  let url = "";
1366
1295
  const normalizedKey = this.normalizeS3Key(resolveS3UrlDto.s3Key);
@@ -9,25 +9,17 @@ import { IFileService, WriteOptions, CopyOptions, UrlOptions } from './file-serv
9
9
  /**
10
10
  * Disk-based implementation of IFileService.
11
11
  * Handles file operations on the local filesystem.
12
+ * Callers are responsible for providing complete paths (use DiskStoragePathBuilder for primary storage paths).
12
13
  */
13
14
  @Injectable()
14
15
  export class DiskFileService implements IFileService {
15
16
  private readonly logger = new Logger(DiskFileService.name);
16
- private readonly fileStorageDir: string;
17
17
  private readonly baseUrl: string;
18
18
 
19
19
  constructor() {
20
- this.fileStorageDir = process.env.AB_MEDIA_FILE_STORAGE_DIR || 'uploads';
21
20
  this.baseUrl = process.env.BASE_URL || '';
22
21
  }
23
22
 
24
- private resolvePath(filePath: string): string {
25
- if (path.isAbsolute(filePath)) {
26
- return filePath;
27
- }
28
- return `${this.fileStorageDir}/${filePath}`;
29
- }
30
-
31
23
  /**
32
24
  * Read file contents as Buffer
33
25
  */
@@ -50,10 +42,9 @@ export class DiskFileService implements IFileService {
50
42
  * @returns Public URL of the written file
51
43
  */
52
44
  async write(filePath: string, data: Buffer | string, options?: WriteOptions): Promise<string> {
53
- const resolvedPath = this.resolvePath(filePath);
54
- await this.ensureDirectoryExists(resolvedPath);
55
- await fsPromises.writeFile(resolvedPath, data);
56
- return `${this.baseUrl}/${resolvedPath}`;
45
+ await this.ensureDirectoryExists(filePath);
46
+ await fsPromises.writeFile(filePath, data);
47
+ return `${this.baseUrl}/${filePath}`;
57
48
  }
58
49
 
59
50
  /**
@@ -62,12 +53,11 @@ export class DiskFileService implements IFileService {
62
53
  * @returns Public URL of the written file
63
54
  */
64
55
  async writeStream(filePath: string, stream: Readable, options?: WriteOptions): Promise<string> {
65
- const resolvedPath = this.resolvePath(filePath);
66
- await this.ensureDirectoryExists(resolvedPath);
67
- const writeStream = fs.createWriteStream(resolvedPath);
56
+ await this.ensureDirectoryExists(filePath);
57
+ const writeStream = fs.createWriteStream(filePath);
68
58
  await pipeline(stream, writeStream);
69
- this.logger.debug(`File saved via stream: ${resolvedPath}`);
70
- return `${this.baseUrl}/${resolvedPath}`;
59
+ this.logger.debug(`File saved via stream: ${filePath}`);
60
+ return `${this.baseUrl}/${filePath}`;
71
61
  }
72
62
 
73
63
  /**
@@ -2,3 +2,4 @@ export * from './file-service.interface';
2
2
  export * from './disk-file.service';
3
3
  export * from './s3-file.service';
4
4
  export * from './file-service.factory';
5
+ export * from './storage-path-builder';
@@ -0,0 +1,56 @@
1
+ import { Injectable, Provider } from '@nestjs/common';
2
+ import * as path from 'path';
3
+ import { DEFAULT_MEDIA_FILE_STORAGE_DIR } from '../settings/default-settings-provider.service';
4
+
5
+ export const FILE_STORAGE_PATH_BUILDER = Symbol('FILE_STORAGE_PATH_BUILDER');
6
+
7
+ export interface IStoragePathBuilder {
8
+ build(fileName: string): string;
9
+ }
10
+
11
+ @Injectable()
12
+ export class DiskStoragePathBuilder implements IStoragePathBuilder {
13
+ private readonly base: string;
14
+
15
+ constructor() {
16
+ this.base = process.env.AB_MEDIA_FILE_STORAGE_DIR || DEFAULT_MEDIA_FILE_STORAGE_DIR;
17
+ }
18
+
19
+ build(fileName: string): string {
20
+ if (path.isAbsolute(fileName) || fileName.startsWith(`${this.base}/`)) {
21
+ return fileName;
22
+ }
23
+ return `${this.base}/${fileName}`;
24
+ }
25
+ }
26
+
27
+ @Injectable()
28
+ export class S3StoragePathBuilder implements IStoragePathBuilder {
29
+ private readonly bucket: string;
30
+
31
+ constructor() {
32
+ this.bucket = process.env.S3_DEFAULT_BUCKET ?? '';
33
+ }
34
+
35
+ build(fileName: string): string {
36
+ if (fileName.includes(':')) {
37
+ return fileName;
38
+ }
39
+ return `${this.bucket}:${fileName}`;
40
+ }
41
+ }
42
+
43
+ export const StoragePathBuilderFactory: Provider = {
44
+ provide: FILE_STORAGE_PATH_BUILDER,
45
+ useFactory: (disk: DiskStoragePathBuilder, s3: S3StoragePathBuilder): IStoragePathBuilder => {
46
+ const defaultService = process.env.DEFAULT_FILE_SERVICE ?? 'disk';
47
+ switch (defaultService.toLowerCase()) {
48
+ case 's3':
49
+ return s3;
50
+ case 'disk':
51
+ default:
52
+ return disk;
53
+ }
54
+ },
55
+ inject: [DiskStoragePathBuilder, S3StoragePathBuilder],
56
+ };
@@ -2,6 +2,8 @@ import { forwardRef, Inject, Injectable, NotFoundException } from '@nestjs/commo
2
2
  import { ModuleRef } from "@nestjs/core";
3
3
  import { InjectEntityManager } from '@nestjs/typeorm';
4
4
  import { EntityManager, In } from 'typeorm';
5
+ import * as path from 'path';
6
+ import { DEFAULT_MEDIA_FILE_STORAGE_DIR } from "src/services/settings/default-settings-provider.service";
5
7
  import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service";
6
8
 
7
9
  import { ConfigService } from '@nestjs/config';
@@ -52,7 +54,7 @@ export class MediaService extends CRUDService<Media> {
52
54
 
53
55
  for (const media of data.records) {
54
56
  if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.Filesystem) {
55
- media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${await this.getFileSysytemFullFilePath(media.relativeUri)}`;
57
+ media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${this.getFullFilePathForDisk(media.relativeUri)}`;
56
58
  } else if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.AwsS3) {
57
59
  media.relativeUri = this.getAwsS3FullFilePath(
58
60
  media.relativeUri,
@@ -63,7 +65,7 @@ export class MediaService extends CRUDService<Media> {
63
65
  }
64
66
  // data.records.forEach((media: Media) => {
65
67
  // if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.Filesystem) {
66
- // media.relativeUri = `${process.env.BASE_URL}/${await this.getFileSysytemFullFilePath(media.relativeUri)}`;
68
+ // media.relativeUri = `${process.env.BASE_URL}/${this.getFileSysytemFullFilePath(media.relativeUri)}`;
67
69
  // } else if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.AwsS3) {
68
70
  // media.relativeUri = this.getAwsS3FullFilePath(
69
71
  // media.relativeUri,
@@ -78,7 +80,7 @@ export class MediaService extends CRUDService<Media> {
78
80
  for (const group of data.groupRecords) {
79
81
  for (const media of group.groupData.records) {
80
82
  if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.Filesystem) {
81
- media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${await this.getFileSysytemFullFilePath(media.relativeUri)}`;
83
+ media.relativeUri = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${this.getFullFilePathForDisk(media.relativeUri)}`;
82
84
  }
83
85
  else if (media.mediaStorageProviderMetadata?.type === MediaStorageProviderType.AwsS3) {
84
86
  media.relativeUri = this.getAwsS3FullFilePath(media.relativeUri, media.mediaStorageProviderMetadata.bucketName, media.mediaStorageProviderMetadata.region);
@@ -129,7 +131,7 @@ export class MediaService extends CRUDService<Media> {
129
131
 
130
132
  switch (createDto.mediaStorageProviderMetadata.type) {
131
133
  case MediaStorageProviderType.Filesystem:
132
- const fileStoragePath = await this.getFileSysytemFullFilePath(this.getFileName(file));
134
+ const fileStoragePath = this.getFullFilePathForDisk(this.getFileName(file));
133
135
  await this.diskFileService.copy(file.path, fileStoragePath);
134
136
  createDto['relativeUri'] = this.getFileName(file);
135
137
  break;
@@ -189,9 +191,13 @@ export class MediaService extends CRUDService<Media> {
189
191
  }
190
192
  //TODO: Move this to a app builder config
191
193
 
192
- private async getFileSysytemFullFilePath(fileName: string): Promise<string> {
193
- const fileStorageDir = this.settingService.getConfigValue<SolidCoreSetting>("fileStorageDir");
194
- return `${fileStorageDir}/${fileName}`;
194
+ private getFullFilePathForDisk(fileName: string): string {
195
+ const base = this.settingService.getConfigValue<SolidCoreSetting>("fileStorageDir")
196
+ || DEFAULT_MEDIA_FILE_STORAGE_DIR;
197
+ if (path.isAbsolute(fileName) || fileName.startsWith(`${base}/`)) {
198
+ return fileName;
199
+ }
200
+ return `${base}/${fileName}`;
195
201
  }
196
202
 
197
203
  private getAwsS3FullFilePath(awsMediaurl: string, bucketName: string, regionName: string): string {
@@ -43,7 +43,7 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
43
43
  const region = this.getEffectiveRegion(storageMeta.region);
44
44
  if (storageMeta.isPublic === false) {
45
45
  // Generate signed URL
46
- const expiryInSeconds = (storageMeta.signedUrlExpiry ?? 60) * 60; // default 5 min
46
+ const expiryInSeconds = (storageMeta.signedUrlExpiry ?? 60) * 60;
47
47
  m['_full_url'] = await this.s3FileService.getUrl(`${storageMeta?.bucketName}:${m.relativeUri}`, { expiresIn: expiryInSeconds, region });
48
48
  } else {
49
49
  // Public S3 or local filesystem: use normal URL
@@ -7,7 +7,9 @@ import { MediaStorageProvider } from "src/interfaces";
7
7
  import { MediaRepository } from "src/repository/media.repository";
8
8
  import { DiskFileService } from "src/services/file";
9
9
  import { Readable } from "stream";
10
+ import * as path from "path";
10
11
  import { SettingService } from "../setting.service";
12
+ import { DEFAULT_MEDIA_FILE_STORAGE_DIR } from "src/services/settings/default-settings-provider.service";
11
13
  import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service";
12
14
 
13
15
  @Injectable()
@@ -34,7 +36,7 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
34
36
  // media.forEach(m => {
35
37
  // });
36
38
  for (const m of media) {
37
- m['_full_url'] = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${await this.getFullFilePath(m.relativeUri)}`;
39
+ m['_full_url'] = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${this.getFullFilePath(m.relativeUri)}`;
38
40
  }
39
41
 
40
42
 
@@ -48,7 +50,7 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
48
50
  const result: Media[] = [];
49
51
  for (const file of files) {
50
52
  // Store the file in the configured file storage directory
51
- const fileStoragePath = await this.getFullFilePath(this.getFileName(file));
53
+ const fileStoragePath = this.getFullFilePath(this.getFileName(file));
52
54
  await this.fileService.copy(file.path, fileStoragePath);
53
55
  await this.fileService.delete(file.path);
54
56
 
@@ -78,7 +80,7 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
78
80
  for (const pair of streamPairs) {
79
81
  const stream = pair[0];
80
82
  const fileName = pair[1];
81
- await this.fileService.writeStream(await this.getFullFilePath(fileName), stream);
83
+ await this.fileService.writeStream(this.getFullFilePath(fileName), stream);
82
84
  const mediaEntity = await this.mediaRepository.createMedia({
83
85
  //@ts-ignore
84
86
  entityId: entity.id,
@@ -102,16 +104,19 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
102
104
  this.mediaRepository.deleteByEntityIdAndFieldIdAndModelMetadataId(entity.id, mediaFieldMetadata.id, mediaFieldMetadata.model.id);
103
105
 
104
106
  for (const media of existingMedia) {
105
- await this.fileService.delete(await this.getFullFilePath(media.relativeUri));
107
+ await this.fileService.delete(this.getFullFilePath(media.relativeUri));
106
108
  }
107
109
  // existingMedia.forEach(media => {
108
110
  // });
109
111
  }
110
112
 
111
- private async getFullFilePath(fileName: string): Promise<string> {
112
- const fileStorageDir = this.settingService.getConfigValue<SolidCoreSetting>("fileStorageDir")
113
- return `${fileStorageDir}/${fileName}`;
114
- // return `${this.configService.get('app-builder.fileStorageDir')}/${fileName}`;
113
+ private getFullFilePath(fileName: string): string {
114
+ const base = this.settingService.getConfigValue<SolidCoreSetting>("fileStorageDir")
115
+ || DEFAULT_MEDIA_FILE_STORAGE_DIR;
116
+ if (path.isAbsolute(fileName) || fileName.startsWith(`${base}/`)) {
117
+ return fileName;
118
+ }
119
+ return `${base}/${fileName}`;
115
120
  }
116
121
 
117
122
  private getFileName(file: Express.Multer.File): string {