@solidxai/core 0.1.12-beta.0 → 0.1.12-beta.10

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 (208) hide show
  1. package/dist/commands/seed.command.d.ts.map +1 -1
  2. package/dist/commands/seed.command.js +10 -2
  3. package/dist/commands/seed.command.js.map +1 -1
  4. package/dist/constants/error-messages.d.ts +1 -0
  5. package/dist/constants/error-messages.d.ts.map +1 -1
  6. package/dist/constants/error-messages.js +1 -0
  7. package/dist/constants/error-messages.js.map +1 -1
  8. package/dist/constants/media-file-types.d.ts +5 -0
  9. package/dist/constants/media-file-types.d.ts.map +1 -0
  10. package/dist/constants/media-file-types.js +27 -0
  11. package/dist/constants/media-file-types.js.map +1 -0
  12. package/dist/controllers/media.controller.d.ts +14 -11
  13. package/dist/controllers/media.controller.d.ts.map +1 -1
  14. package/dist/controllers/media.controller.js +72 -33
  15. package/dist/controllers/media.controller.js.map +1 -1
  16. package/dist/dtos/create-chatter-message.dto.d.ts +1 -0
  17. package/dist/dtos/create-chatter-message.dto.d.ts.map +1 -1
  18. package/dist/dtos/create-chatter-message.dto.js +7 -1
  19. package/dist/dtos/create-chatter-message.dto.js.map +1 -1
  20. package/dist/dtos/create-media.dto.d.ts +1 -0
  21. package/dist/dtos/create-media.dto.d.ts.map +1 -1
  22. package/dist/dtos/create-media.dto.js +7 -1
  23. package/dist/dtos/create-media.dto.js.map +1 -1
  24. package/dist/dtos/create-role-metadata.dto.d.ts +2 -0
  25. package/dist/dtos/create-role-metadata.dto.d.ts.map +1 -1
  26. package/dist/dtos/create-role-metadata.dto.js +3 -1
  27. package/dist/dtos/create-role-metadata.dto.js.map +1 -1
  28. package/dist/dtos/post-chatter-message.dto.d.ts +1 -0
  29. package/dist/dtos/post-chatter-message.dto.d.ts.map +1 -1
  30. package/dist/dtos/post-chatter-message.dto.js +6 -1
  31. package/dist/dtos/post-chatter-message.dto.js.map +1 -1
  32. package/dist/dtos/update-chatter-message.dto.d.ts +1 -0
  33. package/dist/dtos/update-chatter-message.dto.d.ts.map +1 -1
  34. package/dist/dtos/update-chatter-message.dto.js +8 -1
  35. package/dist/dtos/update-chatter-message.dto.js.map +1 -1
  36. package/dist/dtos/update-chatter-note-message.dto.d.ts +1 -0
  37. package/dist/dtos/update-chatter-note-message.dto.d.ts.map +1 -1
  38. package/dist/dtos/update-chatter-note-message.dto.js +7 -1
  39. package/dist/dtos/update-chatter-note-message.dto.js.map +1 -1
  40. package/dist/dtos/update-media.dto.d.ts +1 -0
  41. package/dist/dtos/update-media.dto.d.ts.map +1 -1
  42. package/dist/dtos/update-media.dto.js +7 -1
  43. package/dist/dtos/update-media.dto.js.map +1 -1
  44. package/dist/entities/chatter-message.entity.d.ts +1 -0
  45. package/dist/entities/chatter-message.entity.d.ts.map +1 -1
  46. package/dist/entities/chatter-message.entity.js +5 -1
  47. package/dist/entities/chatter-message.entity.js.map +1 -1
  48. package/dist/entities/media.entity.js.map +1 -1
  49. package/dist/enums/auth-type.enum.d.ts +2 -1
  50. package/dist/enums/auth-type.enum.d.ts.map +1 -1
  51. package/dist/enums/auth-type.enum.js +1 -0
  52. package/dist/enums/auth-type.enum.js.map +1 -1
  53. package/dist/guards/authentication.guard.d.ts +3 -1
  54. package/dist/guards/authentication.guard.d.ts.map +1 -1
  55. package/dist/guards/authentication.guard.js +5 -1
  56. package/dist/guards/authentication.guard.js.map +1 -1
  57. package/dist/guards/media-signed-url.guard.d.ts +9 -0
  58. package/dist/guards/media-signed-url.guard.d.ts.map +1 -0
  59. package/dist/guards/media-signed-url.guard.js +49 -0
  60. package/dist/guards/media-signed-url.guard.js.map +1 -0
  61. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
  62. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +10 -13
  63. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
  64. package/dist/helpers/solid-core-error-codes-provider.service.d.ts.map +1 -1
  65. package/dist/helpers/solid-core-error-codes-provider.service.js +9 -0
  66. package/dist/helpers/solid-core-error-codes-provider.service.js.map +1 -1
  67. package/dist/interfaces/chatter-mention-notification.interface.d.ts +27 -0
  68. package/dist/interfaces/chatter-mention-notification.interface.d.ts.map +1 -0
  69. package/dist/interfaces/chatter-mention-notification.interface.js +3 -0
  70. package/dist/interfaces/chatter-mention-notification.interface.js.map +1 -0
  71. package/dist/interfaces.d.ts +19 -1
  72. package/dist/interfaces.d.ts.map +1 -1
  73. package/dist/interfaces.js +2 -0
  74. package/dist/interfaces.js.map +1 -1
  75. package/dist/jobs/chatter-mention-notification-email-queue-handler.service.d.ts +17 -0
  76. package/dist/jobs/chatter-mention-notification-email-queue-handler.service.d.ts.map +1 -0
  77. package/dist/jobs/chatter-mention-notification-email-queue-handler.service.js +106 -0
  78. package/dist/jobs/chatter-mention-notification-email-queue-handler.service.js.map +1 -0
  79. package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.d.ts +12 -0
  80. package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.d.ts.map +1 -0
  81. package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.js +39 -0
  82. package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.js.map +1 -0
  83. package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.d.ts +8 -0
  84. package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.d.ts.map +1 -0
  85. package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.js +10 -0
  86. package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.js.map +1 -0
  87. package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.d.ts +20 -0
  88. package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.d.ts.map +1 -0
  89. package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.js +48 -0
  90. package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.js.map +1 -0
  91. package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.d.ts +12 -0
  92. package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.d.ts.map +1 -0
  93. package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.js +39 -0
  94. package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.js.map +1 -0
  95. package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.d.ts +8 -0
  96. package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.d.ts.map +1 -0
  97. package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.js +10 -0
  98. package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.js.map +1 -0
  99. package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.d.ts +18 -0
  100. package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.d.ts.map +1 -0
  101. package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.js +45 -0
  102. package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.js.map +1 -0
  103. package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.d.ts +12 -0
  104. package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.d.ts.map +1 -0
  105. package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.js +39 -0
  106. package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.js.map +1 -0
  107. package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.d.ts +8 -0
  108. package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.d.ts.map +1 -0
  109. package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.js +10 -0
  110. package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.js.map +1 -0
  111. package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.d.ts +18 -0
  112. package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.d.ts.map +1 -0
  113. package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.js +45 -0
  114. package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.js.map +1 -0
  115. package/dist/repository/media.repository.d.ts.map +1 -1
  116. package/dist/repository/media.repository.js.map +1 -1
  117. package/dist/seeders/module-metadata-seeder.service.d.ts +7 -1
  118. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  119. package/dist/seeders/module-metadata-seeder.service.js +87 -3
  120. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  121. package/dist/seeders/permission-metadata-seeder.service.d.ts +1 -0
  122. package/dist/seeders/permission-metadata-seeder.service.d.ts.map +1 -1
  123. package/dist/seeders/permission-metadata-seeder.service.js +6 -1
  124. package/dist/seeders/permission-metadata-seeder.service.js.map +1 -1
  125. package/dist/seeders/seed-data/solid-core-metadata.json +95 -13
  126. package/dist/services/authentication.service.d.ts +2 -2
  127. package/dist/services/authentication.service.d.ts.map +1 -1
  128. package/dist/services/authentication.service.js +52 -43
  129. package/dist/services/authentication.service.js.map +1 -1
  130. package/dist/services/chatter-message.service.d.ts +7 -1
  131. package/dist/services/chatter-message.service.d.ts.map +1 -1
  132. package/dist/services/chatter-message.service.js +85 -4
  133. package/dist/services/chatter-message.service.js.map +1 -1
  134. package/dist/services/media-download-url.service.d.ts +22 -0
  135. package/dist/services/media-download-url.service.d.ts.map +1 -0
  136. package/dist/services/media-download-url.service.js +70 -0
  137. package/dist/services/media-download-url.service.js.map +1 -0
  138. package/dist/services/media-storage.utils.d.ts +24 -0
  139. package/dist/services/media-storage.utils.d.ts.map +1 -0
  140. package/dist/services/media-storage.utils.js +73 -0
  141. package/dist/services/media-storage.utils.js.map +1 -0
  142. package/dist/services/media.service.d.ts +20 -4
  143. package/dist/services/media.service.d.ts.map +1 -1
  144. package/dist/services/media.service.js +155 -55
  145. package/dist/services/media.service.js.map +1 -1
  146. package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts +4 -4
  147. package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts.map +1 -1
  148. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +34 -63
  149. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
  150. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts +5 -5
  151. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
  152. package/dist/services/mediaStorageProviders/file-storage-provider.js +34 -38
  153. package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
  154. package/dist/services/role-metadata.service.d.ts +1 -0
  155. package/dist/services/role-metadata.service.d.ts.map +1 -1
  156. package/dist/services/role-metadata.service.js +8 -5
  157. package/dist/services/role-metadata.service.js.map +1 -1
  158. package/dist/solid-core.module.d.ts.map +1 -1
  159. package/dist/solid-core.module.js +25 -1
  160. package/dist/solid-core.module.js.map +1 -1
  161. package/dist/subscribers/audit.subscriber.js +1 -1
  162. package/dist/subscribers/audit.subscriber.js.map +1 -1
  163. package/package.json +1 -1
  164. package/src/commands/seed.command.ts +12 -3
  165. package/src/constants/error-messages.ts +1 -0
  166. package/src/constants/media-file-types.ts +44 -0
  167. package/src/controllers/media.controller.ts +44 -24
  168. package/src/dtos/create-chatter-message.dto.ts +5 -0
  169. package/src/dtos/create-media.dto.ts +8 -3
  170. package/src/dtos/create-role-metadata.dto.ts +2 -0
  171. package/src/dtos/post-chatter-message.dto.ts +4 -0
  172. package/src/dtos/update-chatter-message.dto.ts +6 -0
  173. package/src/dtos/update-chatter-note-message.dto.ts +5 -0
  174. package/src/dtos/update-media.dto.ts +8 -3
  175. package/src/entities/chatter-message.entity.ts +3 -0
  176. package/src/entities/media.entity.ts +1 -1
  177. package/src/enums/auth-type.enum.ts +1 -0
  178. package/src/guards/authentication.guard.ts +3 -0
  179. package/src/guards/media-signed-url.guard.ts +41 -0
  180. package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +12 -24
  181. package/src/helpers/solid-core-error-codes-provider.service.ts +9 -0
  182. package/src/interfaces/chatter-mention-notification.interface.ts +29 -0
  183. package/src/interfaces.ts +20 -0
  184. package/src/jobs/chatter-mention-notification-email-queue-handler.service.ts +114 -0
  185. package/src/jobs/database/chatter-mention-notification-email-publisher-database.service.ts +23 -0
  186. package/src/jobs/database/chatter-mention-notification-email-queue-options-database.ts +9 -0
  187. package/src/jobs/database/chatter-mention-notification-email-subscriber-database.service.ts +32 -0
  188. package/src/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.ts +23 -0
  189. package/src/jobs/rabbitmq/chatter-mention-notification-email-queue-options.ts +9 -0
  190. package/src/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.ts +30 -0
  191. package/src/jobs/redis/chatter-mention-notification-email-publisher-redis.service.ts +23 -0
  192. package/src/jobs/redis/chatter-mention-notification-email-queue-options-redis.ts +9 -0
  193. package/src/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.ts +30 -0
  194. package/src/repository/media.repository.ts +1 -0
  195. package/src/seeders/module-metadata-seeder.service.ts +109 -3
  196. package/src/seeders/permission-metadata-seeder.service.ts +6 -1
  197. package/src/seeders/seed-data/email-templates/chatter-mention-notification.handlebars.html +200 -0
  198. package/src/seeders/seed-data/solid-core-metadata.json +95 -13
  199. package/src/services/authentication.service.ts +66 -55
  200. package/src/services/chatter-message.service.ts +97 -2
  201. package/src/services/media-download-url.service.ts +74 -0
  202. package/src/services/media-storage.utils.ts +70 -0
  203. package/src/services/media.service.ts +187 -59
  204. package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +49 -76
  205. package/src/services/mediaStorageProviders/file-storage-provider.ts +41 -48
  206. package/src/services/role-metadata.service.ts +17 -5
  207. package/src/solid-core.module.ts +35 -2
  208. package/src/subscribers/audit.subscriber.ts +1 -1
@@ -9,6 +9,13 @@ import { MediaStorageProvider } from "src/interfaces";
9
9
  import { DiskFileService, S3FileService } from "src/services/file";
10
10
  import { Readable } from "stream";
11
11
  import { MediaRepository } from "src/repository/media.repository";
12
+ import { MediaDownloadUrlService } from "src/services/media-download-url.service";
13
+ import {
14
+ buildMediaRecordCreateInput,
15
+ buildStoredMediaFileName,
16
+ getEffectiveS3Region,
17
+ resolveMediaIsPublic,
18
+ } from "src/services/media-storage.utils";
12
19
 
13
20
  @Injectable()
14
21
  export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
@@ -19,18 +26,14 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
19
26
  readonly diskFileService: DiskFileService,
20
27
  readonly s3FileService: S3FileService,
21
28
  readonly mediaRepository: MediaRepository,
29
+ private readonly mediaDownloadUrlService: MediaDownloadUrlService,
22
30
  ) { }
23
31
 
24
32
  async storeStreams(streamPairs: [Readable, string][], entity: T, mediaFieldMetadata: FieldMetadata): Promise<Media[]> {
25
- const isSupportedEntity = entity instanceof CommonEntity
26
- || entity instanceof LegacyCommonEntityWithExistingId
27
- || entity instanceof LegacyCommonEntityWithGeneratedId;
28
- if (!isSupportedEntity) {
29
- throw new Error("Entity must be an instance of CommonEntity, LegacyCommonEntityWithExistingId or LegacyCommonEntityWithGeneratedId");
30
- }
33
+ const entityId = this.getSupportedEntityId(entity);
31
34
  const result: Media[] = [];
32
35
  const storageProvider = mediaFieldMetadata.mediaStorageProvider;
33
- const region = this.getEffectiveRegion(storageProvider.region);
36
+ const region = getEffectiveS3Region(this.configService, storageProvider.region);
34
37
 
35
38
  for (const [stream, fileName] of streamPairs) {
36
39
  const bucketName = storageProvider.bucketName;
@@ -45,15 +48,12 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
45
48
 
46
49
  await this.s3FileService.write(`${bucketName}:${fileName}`, fileData, { region });
47
50
 
48
- const mediaEntity = await this.mediaRepository.createMedia({
49
- // @ts-ignore
50
- entityId: entity.id,
51
- modelMetadataId: mediaFieldMetadata.model.id,
52
- relativeUri: fileName,
53
- fileSize,
54
- mediaStorageProviderMetadataId: mediaFieldMetadata.mediaStorageProvider.id,
55
- fieldMetadataId: mediaFieldMetadata.id
56
- }) as unknown as Media;
51
+ const mediaEntity = await this.mediaRepository.createMedia(
52
+ buildMediaRecordCreateInput(entityId, mediaFieldMetadata, storageProvider, {
53
+ relativeUri: fileName,
54
+ fileSize,
55
+ })
56
+ ) as unknown as Media;
57
57
  result.push(mediaEntity);
58
58
  this.logger.debug(`Stored media with`, mediaEntity);
59
59
  }
@@ -61,29 +61,22 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
61
61
  }
62
62
 
63
63
  async retrieve(entity: T, mediaFieldMetadata: FieldMetadata): Promise<Media[]> {
64
- const isSupportedEntity = entity instanceof CommonEntity
65
- || entity instanceof LegacyCommonEntityWithExistingId
66
- || entity instanceof LegacyCommonEntityWithGeneratedId;
67
- if (!isSupportedEntity) {
68
- throw new Error("Entity must be an instance of CommonEntity, LegacyCommonEntityWithExistingId or LegacyCommonEntityWithGeneratedId"); // FIXME This needs to be handled through generics. e.g T extends CommonEntity
69
- }
64
+ const entityId = this.getSupportedEntityId(entity);
70
65
  // @ts-ignore
71
- const media = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(entity.id, mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
66
+ const media = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(entityId, mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
72
67
 
73
68
  // TODO: Check if the mediaStorageProvider (s3 in this case) is configured with a public bucket or not.
74
69
  // If private bucket then we need to return a "signed-url", the timeout for the signed url can be configured in the media storage provider entity and modified using the CRUD interface.
75
70
  // Add the full URL to the media
76
71
  for (const m of media) {
77
72
  const storageMeta = m.mediaStorageProviderMetadata;
78
- const region = this.getEffectiveRegion(storageMeta.region);
79
- if (storageMeta.isPublic === false) {
80
- // Generate signed URL
81
- const expiryInSeconds = (storageMeta.signedUrlExpiry ?? 60) * 60;
82
- m['_full_url'] = await this.s3FileService.getUrl(`${storageMeta?.bucketName}:${m.relativeUri}`, { expiresIn: expiryInSeconds, region });
83
- } else {
84
- // Public S3 or local filesystem: use normal URL
85
- m['_full_url'] = this.getFullFilePath(m);
86
- }
73
+ const isPublic = resolveMediaIsPublic(storageMeta);
74
+ m['_full_url'] = isPublic === false
75
+ ? await this.mediaDownloadUrlService.getPrivateUrl(m.id, m.relativeUri, storageMeta)
76
+ : await this.s3FileService.getUrl(
77
+ `${storageMeta.bucketName}:${m.relativeUri}`,
78
+ { region: getEffectiveS3Region(this.configService, storageMeta.region), expiresIn: 0 },
79
+ );
87
80
  }
88
81
 
89
82
  return media;
@@ -94,18 +87,13 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
94
87
  }
95
88
 
96
89
  async store(files: Express.Multer.File[], entity: T, mediaFieldMetadata: FieldMetadata): Promise<Media[]> {
97
- const isSupportedEntity = entity instanceof CommonEntity
98
- || entity instanceof LegacyCommonEntityWithExistingId
99
- || entity instanceof LegacyCommonEntityWithGeneratedId;
100
- if (!isSupportedEntity) {
101
- throw new Error("Entity must be an instance of CommonEntity, LegacyCommonEntityWithExistingId or LegacyCommonEntityWithGeneratedId"); // FIXME This needs to be handled through generics. e.g T extends CommonEntity
102
- }
90
+ const entityId = this.getSupportedEntityId(entity);
103
91
  const result: Media[] = [];
104
92
  const storageProvider = mediaFieldMetadata.mediaStorageProvider;
105
- const region = this.getEffectiveRegion(storageProvider.region);
93
+ const region = getEffectiveS3Region(this.configService, storageProvider.region);
106
94
 
107
95
  for (const file of files) {
108
- const fileName = this.getFileName(file);
96
+ const fileName = buildStoredMediaFileName(file);
109
97
  const bucketName = storageProvider.bucketName;
110
98
 
111
99
  // Read file from disk and upload to S3
@@ -118,17 +106,14 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
118
106
  const awsFileUrl = fileName;
119
107
 
120
108
  // Create an entry in the media table
121
- const mediaEntity = await this.mediaRepository.createMedia({
122
- // @ts-ignore
123
- entityId: entity.id,
124
- modelMetadataId: mediaFieldMetadata.model.id,
125
- relativeUri: awsFileUrl,
126
- mimeType: file.mimetype,
127
- fileSize: file.size,
128
- originalFileName: file.originalname,
129
- mediaStorageProviderMetadataId: mediaFieldMetadata.mediaStorageProvider.id,
130
- fieldMetadataId: mediaFieldMetadata.id
131
- }) as unknown as Media;
109
+ const mediaEntity = await this.mediaRepository.createMedia(
110
+ buildMediaRecordCreateInput(entityId, mediaFieldMetadata, storageProvider, {
111
+ relativeUri: awsFileUrl,
112
+ mimeType: file.mimetype,
113
+ fileSize: file.size,
114
+ originalFileName: file.originalname,
115
+ })
116
+ ) as unknown as Media;
132
117
  result.push(mediaEntity);
133
118
  this.logger.debug(`Stored media with`, mediaEntity);
134
119
  };
@@ -136,19 +121,14 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
136
121
  }
137
122
 
138
123
  async delete(entity: T, mediaFieldMetadata: FieldMetadata): Promise<void> {
139
- const isSupportedEntity = entity instanceof CommonEntity
140
- || entity instanceof LegacyCommonEntityWithExistingId
141
- || entity instanceof LegacyCommonEntityWithGeneratedId;
142
- if (!isSupportedEntity) {
143
- throw new Error("Entity must be an instance of CommonEntity, LegacyCommonEntityWithExistingId or LegacyCommonEntityWithGeneratedId"); // FIXME This needs to be handled through generics. e.g T extends CommonEntity
144
- }
124
+ const entityId = this.getSupportedEntityId(entity);
145
125
  const storageProvider = mediaFieldMetadata.mediaStorageProvider;
146
- const region = this.getEffectiveRegion(storageProvider.region);
126
+ const region = getEffectiveS3Region(this.configService, storageProvider.region);
147
127
 
148
128
  // @ts-ignore
149
- const existingMedia = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(entity.id, mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
129
+ const existingMedia = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(entityId, mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
150
130
  // @ts-ignore
151
- this.mediaRepository.deleteByEntityIdAndFieldIdAndModelMetadataId(entity.id, mediaFieldMetadata.id, mediaFieldMetadata.model.id);
131
+ this.mediaRepository.deleteByEntityIdAndFieldIdAndModelMetadataId(entityId, mediaFieldMetadata.id, mediaFieldMetadata.model.id);
152
132
  for (const media of existingMedia) {
153
133
  const bucketName = storageProvider.bucketName;
154
134
  await this.s3FileService.delete(`${bucketName}:${media.relativeUri}`, { region });
@@ -163,26 +143,19 @@ export class FileS3StorageProvider<T> implements MediaStorageProvider<T> {
163
143
  if (!storageProvider?.bucketName || !media?.relativeUri) {
164
144
  return;
165
145
  }
166
- const region = this.getEffectiveRegion(storageProvider.region);
146
+ const region = getEffectiveS3Region(this.configService, storageProvider.region);
167
147
  await this.s3FileService.delete(`${storageProvider.bucketName}:${media.relativeUri}`, { region });
168
148
  }
169
149
 
170
- /**
171
- * Get the effective region to use for S3 operations.
172
- * Uses the provider-specific region if configured, otherwise falls back to env variable.
173
- */
174
- private getEffectiveRegion(providerRegion?: string): string | undefined {
175
- return providerRegion || this.configService.get('S3_AWS_REGION_NAME');
176
- }
150
+ private getSupportedEntityId(entity: T): number {
151
+ const isSupportedEntity = entity instanceof CommonEntity
152
+ || entity instanceof LegacyCommonEntityWithExistingId
153
+ || entity instanceof LegacyCommonEntityWithGeneratedId;
177
154
 
178
- private getFullFilePath(media: Media): string {
179
- // Use provider region if available, fallback to env variable
180
- const region = this.getEffectiveRegion(media.mediaStorageProviderMetadata.region);
181
- // https://lunarismedia.s3.ap-south-1.amazonaws.com/LUNARIS_CP_REGISTRATION_CREATIVE.jpg
182
- return `https://${media.mediaStorageProviderMetadata.bucketName}.s3.${region}.amazonaws.com/${media.relativeUri}`;
183
- }
155
+ if (!isSupportedEntity) {
156
+ throw new Error("Entity must be an instance of CommonEntity, LegacyCommonEntityWithExistingId or LegacyCommonEntityWithGeneratedId");
157
+ }
184
158
 
185
- private getFileName(file: Express.Multer.File): string {
186
- return `${file.filename}-${file.originalname}`;
159
+ return (entity as any).id;
187
160
  }
188
161
  }
@@ -1,29 +1,25 @@
1
1
  import { Injectable, Logger } from "@nestjs/common";
2
- import { ConfigService } from "@nestjs/config";
3
- import { CommonEntity } from "src/entities/common.entity";
4
2
  import { FieldMetadata } from "src/entities/field-metadata.entity";
5
3
  import { Media } from "src/entities/media.entity";
4
+ import { MediaStorageProviderMetadata } from "src/entities/media-storage-provider-metadata.entity";
6
5
  import { MediaStorageProvider } from "src/interfaces";
7
6
  import { MediaRepository } from "src/repository/media.repository";
8
7
  import { DiskFileService } from "src/services/file";
9
8
  import { Readable } from "stream";
10
- import * as path from "path";
11
9
  import * as fs from "fs";
12
10
  import { SettingService } from "../setting.service";
13
- import { DEFAULT_MEDIA_FILE_STORAGE_DIR } from "src/services/settings/default-settings-provider.service";
14
- import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service";
11
+ import { MediaDownloadUrlService } from "src/services/media-download-url.service";
12
+ import {buildDiskMediaPath,buildMediaRecordCreateInput,buildStoredMediaFileName,resolveMediaIsPublic,} from "src/services/media-storage.utils";
15
13
 
16
14
  @Injectable()
17
15
  export class FileStorageProvider<T> implements MediaStorageProvider<T> {
18
16
  private logger = new Logger(FileStorageProvider.name);
19
17
 
20
18
  constructor(
21
- // @Inject(appBuilderConfig.KEY)
22
- // private readonly appBuilderConfiguration: ConfigType<typeof appBuilderConfig>,
23
- private readonly configService: ConfigService,
24
19
  readonly fileService: DiskFileService,
25
20
  readonly mediaRepository: MediaRepository,
26
- private readonly settingService: SettingService
21
+ private readonly settingService: SettingService,
22
+ private readonly mediaDownloadUrlService: MediaDownloadUrlService,
27
23
 
28
24
  ) { }
29
25
 
@@ -32,12 +28,16 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
32
28
  // throw new Error("Entity must be an instance of CommonEntity"); //FIXME This needs to be handled through generics. e.g T extends CommonEntity
33
29
  // }
34
30
  //@ts-ignore
35
- const media = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(entity.id, mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
31
+ const media = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(this.getEntityId(entity), mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
36
32
  // Add the full URL to the media
37
33
  // media.forEach(m => {
38
34
  // });
39
35
  for (const m of media) {
40
- m['_full_url'] = `${this.settingService.getConfigValue<SolidCoreSetting>("baseUrl")}/${this.getFullFilePath(m.relativeUri)}`;
36
+ const storageProvider = this.resolveMediaStorageProvider(m, mediaFieldMetadata);
37
+ const isPublic = resolveMediaIsPublic(storageProvider);
38
+ m['_full_url'] = isPublic === false
39
+ ? await this.mediaDownloadUrlService.getPrivateUrl(m.id, m.relativeUri, storageProvider)
40
+ : await this.fileService.getUrl(buildDiskMediaPath(m.relativeUri, this.settingService, storageProvider));
41
41
  }
42
42
 
43
43
 
@@ -49,24 +49,23 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
49
49
  // throw new Error("Entity must be an instance of CommonEntity"); //FIXME This needs to be handled through generics. e.g T extends CommonEntity
50
50
  // }
51
51
  const result: Media[] = [];
52
+ const storageProvider = mediaFieldMetadata.mediaStorageProvider;
52
53
  for (const file of files) {
53
54
  // Store the file in the configured file storage directory
54
- const fileStoragePath = this.getFullFilePath(this.getFileName(file));
55
+ const fileName = buildStoredMediaFileName(file);
56
+ const fileStoragePath = buildDiskMediaPath(fileName, this.settingService, storageProvider);
55
57
  await this.fileService.copy(file.path, fileStoragePath);
56
58
  await this.fileService.delete(file.path);
57
59
 
58
60
  // Create an entry in the media table
59
- const mediaEntity = await this.mediaRepository.createMedia({
60
- //@ts-ignore
61
- entityId: entity.id,
62
- modelMetadataId: mediaFieldMetadata.model.id,
63
- relativeUri: this.getFileName(file),
64
- mimeType: file.mimetype,
65
- fileSize: file.size,
66
- originalFileName: file.originalname,
67
- mediaStorageProviderMetadataId: mediaFieldMetadata.mediaStorageProvider.id,
68
- fieldMetadataId: mediaFieldMetadata.id
69
- }) as unknown as Media;
61
+ const mediaEntity = await this.mediaRepository.createMedia(
62
+ buildMediaRecordCreateInput(this.getEntityId(entity), mediaFieldMetadata, storageProvider, {
63
+ relativeUri: fileName,
64
+ mimeType: file.mimetype,
65
+ fileSize: file.size,
66
+ originalFileName: file.originalname,
67
+ })
68
+ ) as unknown as Media;
70
69
  result.push(mediaEntity);
71
70
  this.logger.debug(`Stored media with`, mediaEntity);
72
71
  };
@@ -78,21 +77,20 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
78
77
  // throw new Error("Entity must be an instance of CommonEntity"); //FIXME This needs to be handled through generics. e.g T extends CommonEntity
79
78
  // }
80
79
  const result: Media[] = [];
80
+ const storageProvider = mediaFieldMetadata.mediaStorageProvider;
81
81
  for (const pair of streamPairs) {
82
82
  const stream = pair[0];
83
83
  const fileName = pair[1];
84
- const fullPath = this.getFullFilePath(fileName);
84
+ const fullPath = buildDiskMediaPath(fileName, this.settingService, storageProvider);
85
85
  await this.fileService.writeStream(fullPath, stream);
86
86
  const { size: fileSize } = await fs.promises.stat(fullPath);
87
- const mediaEntity = await this.mediaRepository.createMedia({
88
- //@ts-ignore
89
- entityId: entity.id,
90
- modelMetadataId: mediaFieldMetadata.model.id,
91
- relativeUri: fileName,
92
- fileSize,
93
- mediaStorageProviderMetadataId: mediaFieldMetadata.mediaStorageProvider.id,
94
- fieldMetadataId: mediaFieldMetadata.id
95
- }) as unknown as Media;
87
+ const mediaEntity = await this.mediaRepository.createMedia(
88
+ buildMediaRecordCreateInput(this.getEntityId(entity), mediaFieldMetadata, storageProvider, {
89
+ relativeUri: fileName,
90
+ fileSize,
91
+ })
92
+ ) as unknown as Media;
93
+ result.push(mediaEntity);
96
94
  this.logger.debug(`Stored media with`, mediaEntity);
97
95
  };
98
96
  return result;
@@ -102,13 +100,13 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
102
100
  // if (!(entity instanceof CommonEntity)) {
103
101
  // throw new Error("Entity must be an instance of CommonEntity"); //FIXME This needs to be handled through generics. e.g T extends CommonEntity
104
102
  // }
105
- //@ts-ignore
106
- const existingMedia = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(entity.id, mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
107
- //@ts-ignore
108
- this.mediaRepository.deleteByEntityIdAndFieldIdAndModelMetadataId(entity.id, mediaFieldMetadata.id, mediaFieldMetadata.model.id);
103
+ const entityId = this.getEntityId(entity);
104
+ const existingMedia = await this.mediaRepository.findByEntityIdAndFieldIdAndModelMetadataId(entityId, mediaFieldMetadata.id, mediaFieldMetadata.model.id, ['mediaStorageProviderMetadata']);
105
+ await this.mediaRepository.deleteByEntityIdAndFieldIdAndModelMetadataId(entityId, mediaFieldMetadata.id, mediaFieldMetadata.model.id);
109
106
 
110
107
  for (const media of existingMedia) {
111
- await this.fileService.delete(this.getFullFilePath(media.relativeUri));
108
+ const storageProvider = this.resolveMediaStorageProvider(media, mediaFieldMetadata);
109
+ await this.fileService.delete(buildDiskMediaPath(media.relativeUri, this.settingService, storageProvider));
112
110
  }
113
111
  // existingMedia.forEach(media => {
114
112
  // });
@@ -118,19 +116,14 @@ export class FileStorageProvider<T> implements MediaStorageProvider<T> {
118
116
  if (!media?.relativeUri) {
119
117
  return;
120
118
  }
121
- await this.fileService.delete(this.getFullFilePath(media.relativeUri));
119
+ await this.fileService.delete(buildDiskMediaPath(media.relativeUri, this.settingService, media.mediaStorageProviderMetadata));
122
120
  }
123
121
 
124
- private getFullFilePath(fileName: string): string {
125
- const base = this.settingService.getConfigValue<SolidCoreSetting>("fileStorageDir")
126
- || DEFAULT_MEDIA_FILE_STORAGE_DIR;
127
- if (path.isAbsolute(fileName) || fileName.startsWith(`${base}/`)) {
128
- return fileName;
129
- }
130
- return `${base}/${fileName}`;
122
+ private resolveMediaStorageProvider(media: Media, mediaFieldMetadata?: FieldMetadata): MediaStorageProviderMetadata | undefined {
123
+ return media.mediaStorageProviderMetadata || mediaFieldMetadata?.mediaStorageProvider;
131
124
  }
132
125
 
133
- private getFileName(file: Express.Multer.File): string {
134
- return `${file.filename}-${file.originalname}`;
126
+ private getEntityId(entity: T): number {
127
+ return (entity as any).id;
135
128
  }
136
129
  }
@@ -140,6 +140,10 @@ export class RoleMetadataService extends CRUDService<RoleMetadata> {
140
140
  return await this._addPermissionsToRole(roleName, null);
141
141
  }
142
142
 
143
+ async addAllPermissionsToRoleExceptPrefixes(roleName: string, excludedPermissionPrefixes: string[]): Promise<RoleMetadata> {
144
+ return await this._addPermissionsToRole(roleName, null, excludedPermissionPrefixes);
145
+ }
146
+
143
147
  async addPermissionsToRole(roleName: string, permissionNames: string[]): Promise<RoleMetadata> {
144
148
  return await this._addPermissionsToRole(roleName, permissionNames);
145
149
  }
@@ -148,7 +152,11 @@ export class RoleMetadataService extends CRUDService<RoleMetadata> {
148
152
  return await this._addPermissionsToRole(roleName, permissionName);
149
153
  }
150
154
 
151
- private async _addPermissionsToRole(roleName: string, permissionNames: string[]): Promise<RoleMetadata> {
155
+ private async _addPermissionsToRole(
156
+ roleName: string,
157
+ permissionNames: string[],
158
+ excludedPermissionPrefixes: string[] = [],
159
+ ): Promise<RoleMetadata> {
152
160
  const role = await this.repo.findOne({
153
161
  where: { name: roleName },
154
162
  select: {
@@ -161,7 +169,7 @@ export class RoleMetadataService extends CRUDService<RoleMetadata> {
161
169
  }
162
170
 
163
171
  const joinInfo = this.getRolePermissionJoinInfo();
164
- const targetPermissions = await this.loadTargetPermissions(permissionNames);
172
+ const targetPermissions = await this.loadTargetPermissions(permissionNames, excludedPermissionPrefixes);
165
173
 
166
174
  if (!permissionNames || permissionNames.length === 0) {
167
175
  await this.entityManager
@@ -226,21 +234,25 @@ export class RoleMetadataService extends CRUDService<RoleMetadata> {
226
234
  return existingPermission;
227
235
  }
228
236
 
229
- private async loadTargetPermissions(permissionNames: string[] | null): Promise<PermissionMetadata[]> {
237
+ private async loadTargetPermissions(permissionNames: string[] | null, excludedPermissionPrefixes: string[] = []): Promise<PermissionMetadata[]> {
230
238
  if (permissionNames && permissionNames.length !== 0) {
231
239
  const uniquePermissionNames = [...new Set(permissionNames)];
232
240
  const permissions = await this.permissionRepository.find({ where: { name: In(uniquePermissionNames) } });
233
241
  if (permissions.length !== uniquePermissionNames.length) {
234
242
  throw new Error(`One or more permissions not found.`);
235
243
  }
236
- return permissions;
244
+ return permissions.filter((permission) =>
245
+ !excludedPermissionPrefixes.some((prefix) => permission.name.startsWith(prefix)),
246
+ );
237
247
  }
238
248
 
239
249
  const permissions = await this.permissionRepository.find();
240
250
  if (permissions.length === 0) {
241
251
  throw new Error(`No permissions configured in the system. Did you forget to run the PermissionSeederService?`);
242
252
  }
243
- return permissions;
253
+ return permissions.filter((permission) =>
254
+ !excludedPermissionPrefixes.some((prefix) => permission.name.startsWith(prefix)),
255
+ );
244
256
  }
245
257
 
246
258
  private getRolePermissionJoinInfo(): { tableName: string; roleIdColumn: string; permissionIdColumn: string } {
@@ -47,6 +47,7 @@ import { ListOfValuesService } from "./services/list-of-values.service";
47
47
  // import { MediaStorageProviderMetadataSeederService } from './services/media-storage-provider-metadata-seeder.service';
48
48
  import { MediaStorageProviderMetadataService } from "./services/media-storage-provider-metadata.service";
49
49
  import { MediaService } from "./services/media.service";
50
+ import { MediaDownloadUrlService } from "./services/media-download-url.service";
50
51
  import { ModelMetadataService } from "./services/model-metadata.service";
51
52
  import { RemovedFieldMigrationService } from "./services/removed-field-migration.service";
52
53
  import { ModuleMetadataExplorerService } from "./services/module-metadata-explorer.service";
@@ -55,9 +56,10 @@ import { ModulePackageService } from "./services/module-package.service";
55
56
  import { SolidIntrospectService } from "./services/solid-introspect.service";
56
57
  // import { ListOfComputedFieldProvider } from './providers/list-of-computed-field-provider.service';
57
58
  import { ServeStaticModule } from "@nestjs/serve-static";
58
- import { join } from "path";
59
+ import { extname, join } from "path";
59
60
  import { RefreshModelCommand } from "./commands/refresh-model.command";
60
61
  import { MediaController } from "./controllers/media.controller";
62
+ import { INLINE_SAFE_EXTENSIONS } from "./constants/media-file-types";
61
63
 
62
64
  import { RefreshModuleCommand } from "./commands/refresh-module.command";
63
65
  import { ModelMetadataSubscriber } from "./subscribers/model-metadata.subscriber";
@@ -106,6 +108,7 @@ import { MqMessage } from "./entities/mq-message.entity";
106
108
  import { SmsTemplate } from "./entities/sms-template.entity";
107
109
  import { AccessTokenGuard } from "./guards/access-token.guard";
108
110
  import { ApiKeyGuard } from "./guards/api-key.guard";
111
+ import { MediaSignedUrlGuard } from "./guards/media-signed-url.guard";
109
112
  import { AuthenticationGuard } from "./guards/authentication.guard";
110
113
  import { PermissionsGuard } from "./guards/permissions.guard";
111
114
  import { SolidRegistry } from "./helpers/solid-registry";
@@ -130,10 +133,17 @@ import { ChatterQueuePublisherRabbitmq } from "./jobs/rabbitmq/chatter-queue-pub
130
133
  import { ChatterQueueSubscriberRabbitmq } from "./jobs/rabbitmq/chatter-queue-subscriber.service";
131
134
  import { ChatterQueuePublisherDatabase } from "./jobs/database/chatter-queue-publisher-database.service";
132
135
  import { ChatterQueueSubscriberDatabase } from "./jobs/database/chatter-queue-subscriber-database.service";
136
+ import { ChatterMentionNotificationEmailQueueHandler } from "./jobs/chatter-mention-notification-email-queue-handler.service";
137
+ import { ChatterMentionNotificationEmailQueuePublisherDatabase } from "./jobs/database/chatter-mention-notification-email-publisher-database.service";
138
+ import { ChatterMentionNotificationEmailQueueSubscriberDatabase } from "./jobs/database/chatter-mention-notification-email-subscriber-database.service";
139
+ import { ChatterMentionNotificationEmailQueuePublisherRabbitmq } from "./jobs/rabbitmq/chatter-mention-notification-email-publisher.service";
140
+ import { ChatterMentionNotificationEmailQueueSubscriberRabbitmq } from "./jobs/rabbitmq/chatter-mention-notification-email-subscriber.service";
133
141
  import { ApiEmailQueuePublisherRedis } from "./jobs/redis/api-email-publisher-redis.service";
134
142
  import { ApiEmailQueueSubscriberRedis } from "./jobs/redis/api-email-subscriber-redis.service";
135
143
  import { ChatterQueuePublisherRedis } from "./jobs/redis/chatter-queue-publisher-redis.service";
136
144
  import { ChatterQueueSubscriberRedis } from "./jobs/redis/chatter-queue-subscriber-redis.service";
145
+ import { ChatterMentionNotificationEmailQueuePublisherRedis } from "./jobs/redis/chatter-mention-notification-email-publisher-redis.service";
146
+ import { ChatterMentionNotificationEmailQueueSubscriberRedis } from "./jobs/redis/chatter-mention-notification-email-subscriber-redis.service";
137
147
  import { ComputedFieldEvaluationPublisherRedis } from "./jobs/redis/computed-field-evaluation-publisher-redis.service";
138
148
  import { ComputedFieldEvaluationSubscriberRedis } from "./jobs/redis/computed-field-evaluation-subscriber-redis.service";
139
149
  import { GenerateCodePublisherRedis } from "./jobs/redis/generate-code-publisher-redis.service";
@@ -437,7 +447,7 @@ import { PostgresDatasourceIntrospectionProviderService } from "./services/datas
437
447
  rootPath: join(process.cwd(), "media-files-storage"),
438
448
  serveRoot: "/media-files-storage",
439
449
  serveStaticOptions: {
440
- setHeaders: (res /*, path, stat*/) => {
450
+ setHeaders: (res, path) => {
441
451
  // Allow use of these files from a different origin (e.g., :3000 UI)
442
452
  // Use 'same-site' if both origins are on the same site (localhost:* counts as same-site)
443
453
  res.setHeader("Cross-Origin-Resource-Policy", "cross-origin"); // or 'same-site'
@@ -445,6 +455,20 @@ import { PostgresDatasourceIntrospectionProviderService } from "./services/datas
445
455
  // If you need to load into <canvas> without tainting or fetch images via XHR,
446
456
  // you can also expose CORS here (not needed for simple <img>):
447
457
  // res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
458
+
459
+ // Prevent the browser from ever content-sniffing a served file into something
460
+ // more dangerous than its declared Content-Type (e.g. rendering a mislabeled
461
+ // upload as HTML and executing embedded script).
462
+ res.setHeader("X-Content-Type-Options", "nosniff");
463
+
464
+ // Only a small allowlist of media types is ever safe to render inline. Everything
465
+ // else (including svg, html, and any other uploaded file) is forced to download
466
+ // rather than be displayed/executed by the browser, regardless of what mimetype or
467
+ // extension it was uploaded with.
468
+ if (!INLINE_SAFE_EXTENSIONS.has(extname(path).toLowerCase().replace(/^\./, ''))) {
469
+ res.setHeader("Content-Type", "application/octet-stream");
470
+ res.setHeader("Content-Disposition", "attachment");
471
+ }
448
472
  },
449
473
  },
450
474
  }),
@@ -563,6 +587,7 @@ import { PostgresDatasourceIntrospectionProviderService } from "./services/datas
563
587
  SchematicService,
564
588
  MediaStorageProviderMetadataService,
565
589
  MediaService,
590
+ MediaDownloadUrlService,
566
591
  // MediaStorageProviderMetadataSeederService,
567
592
  ModuleMetadataSeederService,
568
593
  ModuleTestDataService,
@@ -647,6 +672,7 @@ import { PostgresDatasourceIntrospectionProviderService } from "./services/datas
647
672
  SoftDeleteAwareEventSubscriber,
648
673
  AccessTokenGuard,
649
674
  ApiKeyGuard,
675
+ MediaSignedUrlGuard,
650
676
  ApiKeyService,
651
677
  ActiveSessionStorageService,
652
678
  AuthenticationService,
@@ -665,6 +691,11 @@ import { PostgresDatasourceIntrospectionProviderService } from "./services/datas
665
691
  ChatterQueueSubscriberRabbitmq,
666
692
  ChatterQueuePublisherDatabase,
667
693
  ChatterQueueSubscriberDatabase,
694
+ ChatterMentionNotificationEmailQueueHandler,
695
+ ChatterMentionNotificationEmailQueuePublisherRabbitmq,
696
+ ChatterMentionNotificationEmailQueueSubscriberRabbitmq,
697
+ ChatterMentionNotificationEmailQueuePublisherDatabase,
698
+ ChatterMentionNotificationEmailQueueSubscriberDatabase,
668
699
 
669
700
  TestQueuePublisherDatabase,
670
701
  TestQueueSubscriberDatabase,
@@ -674,6 +705,8 @@ import { PostgresDatasourceIntrospectionProviderService } from "./services/datas
674
705
  ApiEmailQueueSubscriberRedis,
675
706
  ChatterQueuePublisherRedis,
676
707
  ChatterQueueSubscriberRedis,
708
+ ChatterMentionNotificationEmailQueuePublisherRedis,
709
+ ChatterMentionNotificationEmailQueueSubscriberRedis,
677
710
  ComputedFieldEvaluationPublisherRedis,
678
711
  ComputedFieldEvaluationSubscriberRedis,
679
712
  GenerateCodePublisherRedis,
@@ -106,7 +106,7 @@ export class AuditSubscriber implements EntitySubscriberInterface {
106
106
 
107
107
  const relationBefore = event.entity?.[AUDIT_BEFORE_SNAPSHOT] ?? null;
108
108
 
109
- const relationAfter = await this.dataSource.getRepository(event.metadata.target as any).findOne({
109
+ const relationAfter = await event.queryRunner.manager.getRepository(event.metadata.target as any).findOne({
110
110
  where: { id: entityId } as any,
111
111
  relations: relations as any,
112
112
  });