@solidxai/core 0.1.12-beta.1 → 0.1.12-beta.3

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 (169) hide show
  1. package/dist/constants/media-file-types.d.ts +5 -0
  2. package/dist/constants/media-file-types.d.ts.map +1 -0
  3. package/dist/constants/media-file-types.js +27 -0
  4. package/dist/constants/media-file-types.js.map +1 -0
  5. package/dist/controllers/media.controller.d.ts +14 -11
  6. package/dist/controllers/media.controller.d.ts.map +1 -1
  7. package/dist/controllers/media.controller.js +72 -33
  8. package/dist/controllers/media.controller.js.map +1 -1
  9. package/dist/dtos/create-chatter-message.dto.d.ts +1 -0
  10. package/dist/dtos/create-chatter-message.dto.d.ts.map +1 -1
  11. package/dist/dtos/create-chatter-message.dto.js +7 -1
  12. package/dist/dtos/create-chatter-message.dto.js.map +1 -1
  13. package/dist/dtos/create-media.dto.d.ts +1 -0
  14. package/dist/dtos/create-media.dto.d.ts.map +1 -1
  15. package/dist/dtos/create-media.dto.js +7 -1
  16. package/dist/dtos/create-media.dto.js.map +1 -1
  17. package/dist/dtos/post-chatter-message.dto.d.ts +1 -0
  18. package/dist/dtos/post-chatter-message.dto.d.ts.map +1 -1
  19. package/dist/dtos/post-chatter-message.dto.js +6 -1
  20. package/dist/dtos/post-chatter-message.dto.js.map +1 -1
  21. package/dist/dtos/update-chatter-message.dto.d.ts +1 -0
  22. package/dist/dtos/update-chatter-message.dto.d.ts.map +1 -1
  23. package/dist/dtos/update-chatter-message.dto.js +8 -1
  24. package/dist/dtos/update-chatter-message.dto.js.map +1 -1
  25. package/dist/dtos/update-chatter-note-message.dto.d.ts +1 -0
  26. package/dist/dtos/update-chatter-note-message.dto.d.ts.map +1 -1
  27. package/dist/dtos/update-chatter-note-message.dto.js +7 -1
  28. package/dist/dtos/update-chatter-note-message.dto.js.map +1 -1
  29. package/dist/dtos/update-media.dto.d.ts +1 -0
  30. package/dist/dtos/update-media.dto.d.ts.map +1 -1
  31. package/dist/dtos/update-media.dto.js +7 -1
  32. package/dist/dtos/update-media.dto.js.map +1 -1
  33. package/dist/entities/chatter-message.entity.d.ts +1 -0
  34. package/dist/entities/chatter-message.entity.d.ts.map +1 -1
  35. package/dist/entities/chatter-message.entity.js +5 -1
  36. package/dist/entities/chatter-message.entity.js.map +1 -1
  37. package/dist/entities/media.entity.d.ts +1 -0
  38. package/dist/entities/media.entity.d.ts.map +1 -1
  39. package/dist/entities/media.entity.js +9 -1
  40. package/dist/entities/media.entity.js.map +1 -1
  41. package/dist/enums/auth-type.enum.d.ts +2 -1
  42. package/dist/enums/auth-type.enum.d.ts.map +1 -1
  43. package/dist/enums/auth-type.enum.js +1 -0
  44. package/dist/enums/auth-type.enum.js.map +1 -1
  45. package/dist/guards/authentication.guard.d.ts +3 -1
  46. package/dist/guards/authentication.guard.d.ts.map +1 -1
  47. package/dist/guards/authentication.guard.js +5 -1
  48. package/dist/guards/authentication.guard.js.map +1 -1
  49. package/dist/guards/media-signed-url.guard.d.ts +9 -0
  50. package/dist/guards/media-signed-url.guard.d.ts.map +1 -0
  51. package/dist/guards/media-signed-url.guard.js +49 -0
  52. package/dist/guards/media-signed-url.guard.js.map +1 -0
  53. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
  54. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +10 -13
  55. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
  56. package/dist/interfaces/chatter-mention-notification.interface.d.ts +27 -0
  57. package/dist/interfaces/chatter-mention-notification.interface.d.ts.map +1 -0
  58. package/dist/interfaces/chatter-mention-notification.interface.js +3 -0
  59. package/dist/interfaces/chatter-mention-notification.interface.js.map +1 -0
  60. package/dist/jobs/chatter-mention-notification-email-queue-handler.service.d.ts +17 -0
  61. package/dist/jobs/chatter-mention-notification-email-queue-handler.service.d.ts.map +1 -0
  62. package/dist/jobs/chatter-mention-notification-email-queue-handler.service.js +106 -0
  63. package/dist/jobs/chatter-mention-notification-email-queue-handler.service.js.map +1 -0
  64. package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.d.ts +12 -0
  65. package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.d.ts.map +1 -0
  66. package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.js +39 -0
  67. package/dist/jobs/database/chatter-mention-notification-email-publisher-database.service.js.map +1 -0
  68. package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.d.ts +8 -0
  69. package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.d.ts.map +1 -0
  70. package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.js +10 -0
  71. package/dist/jobs/database/chatter-mention-notification-email-queue-options-database.js.map +1 -0
  72. package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.d.ts +20 -0
  73. package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.d.ts.map +1 -0
  74. package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.js +48 -0
  75. package/dist/jobs/database/chatter-mention-notification-email-subscriber-database.service.js.map +1 -0
  76. package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.d.ts +12 -0
  77. package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.d.ts.map +1 -0
  78. package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.js +39 -0
  79. package/dist/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.js.map +1 -0
  80. package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.d.ts +8 -0
  81. package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.d.ts.map +1 -0
  82. package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.js +10 -0
  83. package/dist/jobs/rabbitmq/chatter-mention-notification-email-queue-options.js.map +1 -0
  84. package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.d.ts +18 -0
  85. package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.d.ts.map +1 -0
  86. package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.js +45 -0
  87. package/dist/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.js.map +1 -0
  88. package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.d.ts +12 -0
  89. package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.d.ts.map +1 -0
  90. package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.js +39 -0
  91. package/dist/jobs/redis/chatter-mention-notification-email-publisher-redis.service.js.map +1 -0
  92. package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.d.ts +8 -0
  93. package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.d.ts.map +1 -0
  94. package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.js +10 -0
  95. package/dist/jobs/redis/chatter-mention-notification-email-queue-options-redis.js.map +1 -0
  96. package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.d.ts +18 -0
  97. package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.d.ts.map +1 -0
  98. package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.js +45 -0
  99. package/dist/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.js.map +1 -0
  100. package/dist/repository/media.repository.d.ts.map +1 -1
  101. package/dist/repository/media.repository.js +2 -0
  102. package/dist/repository/media.repository.js.map +1 -1
  103. package/dist/seeders/seed-data/solid-core-metadata.json +109 -3
  104. package/dist/services/chatter-message.service.d.ts +7 -1
  105. package/dist/services/chatter-message.service.d.ts.map +1 -1
  106. package/dist/services/chatter-message.service.js +85 -4
  107. package/dist/services/chatter-message.service.js.map +1 -1
  108. package/dist/services/media-download-url.service.d.ts +21 -0
  109. package/dist/services/media-download-url.service.d.ts.map +1 -0
  110. package/dist/services/media-download-url.service.js +67 -0
  111. package/dist/services/media-download-url.service.js.map +1 -0
  112. package/dist/services/media-storage.utils.d.ts +27 -0
  113. package/dist/services/media-storage.utils.d.ts.map +1 -0
  114. package/dist/services/media-storage.utils.js +82 -0
  115. package/dist/services/media-storage.utils.js.map +1 -0
  116. package/dist/services/media.service.d.ts +20 -4
  117. package/dist/services/media.service.d.ts.map +1 -1
  118. package/dist/services/media.service.js +157 -55
  119. package/dist/services/media.service.js.map +1 -1
  120. package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts +4 -4
  121. package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts.map +1 -1
  122. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +37 -63
  123. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
  124. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts +5 -5
  125. package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
  126. package/dist/services/mediaStorageProviders/file-storage-provider.js +37 -38
  127. package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
  128. package/dist/solid-core.module.d.ts.map +1 -1
  129. package/dist/solid-core.module.js +25 -1
  130. package/dist/solid-core.module.js.map +1 -1
  131. package/dist/subscribers/audit.subscriber.js +1 -1
  132. package/dist/subscribers/audit.subscriber.js.map +1 -1
  133. package/package.json +1 -1
  134. package/src/constants/media-file-types.ts +44 -0
  135. package/src/controllers/media.controller.ts +44 -24
  136. package/src/dtos/create-chatter-message.dto.ts +5 -0
  137. package/src/dtos/create-media.dto.ts +8 -3
  138. package/src/dtos/post-chatter-message.dto.ts +4 -0
  139. package/src/dtos/update-chatter-message.dto.ts +6 -0
  140. package/src/dtos/update-chatter-note-message.dto.ts +5 -0
  141. package/src/dtos/update-media.dto.ts +8 -3
  142. package/src/entities/chatter-message.entity.ts +3 -0
  143. package/src/entities/media.entity.ts +4 -1
  144. package/src/enums/auth-type.enum.ts +1 -0
  145. package/src/guards/authentication.guard.ts +3 -0
  146. package/src/guards/media-signed-url.guard.ts +41 -0
  147. package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +12 -24
  148. package/src/interfaces/chatter-mention-notification.interface.ts +29 -0
  149. package/src/jobs/chatter-mention-notification-email-queue-handler.service.ts +114 -0
  150. package/src/jobs/database/chatter-mention-notification-email-publisher-database.service.ts +23 -0
  151. package/src/jobs/database/chatter-mention-notification-email-queue-options-database.ts +9 -0
  152. package/src/jobs/database/chatter-mention-notification-email-subscriber-database.service.ts +32 -0
  153. package/src/jobs/rabbitmq/chatter-mention-notification-email-publisher.service.ts +23 -0
  154. package/src/jobs/rabbitmq/chatter-mention-notification-email-queue-options.ts +9 -0
  155. package/src/jobs/rabbitmq/chatter-mention-notification-email-subscriber.service.ts +30 -0
  156. package/src/jobs/redis/chatter-mention-notification-email-publisher-redis.service.ts +23 -0
  157. package/src/jobs/redis/chatter-mention-notification-email-queue-options-redis.ts +9 -0
  158. package/src/jobs/redis/chatter-mention-notification-email-subscriber-redis.service.ts +30 -0
  159. package/src/repository/media.repository.ts +2 -0
  160. package/src/seeders/seed-data/email-templates/chatter-mention-notification.handlebars.html +200 -0
  161. package/src/seeders/seed-data/solid-core-metadata.json +109 -3
  162. package/src/services/chatter-message.service.ts +97 -2
  163. package/src/services/media-download-url.service.ts +67 -0
  164. package/src/services/media-storage.utils.ts +79 -0
  165. package/src/services/media.service.ts +189 -59
  166. package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +52 -76
  167. package/src/services/mediaStorageProviders/file-storage-provider.ts +44 -48
  168. package/src/solid-core.module.ts +35 -2
  169. package/src/subscribers/audit.subscriber.ts +1 -1
@@ -0,0 +1,200 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <title>You were mentioned in a note</title>
7
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8
+
9
+ <style>
10
+ @import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
11
+
12
+ body {
13
+ margin: 0;
14
+ padding: 0;
15
+ background-color: #f4f4f7;
16
+ font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
17
+ color: #333;
18
+ }
19
+
20
+ .email-wrapper {
21
+ max-width: 600px;
22
+ margin: 0 auto;
23
+ padding: 20px;
24
+ }
25
+
26
+ .logo {
27
+ text-align: center;
28
+ margin-bottom: 20px;
29
+ }
30
+
31
+ .logo img {
32
+ height: 50px;
33
+ }
34
+
35
+ .content-box {
36
+ background-color: #ffffff;
37
+ padding: 40px;
38
+ border-radius: 8px;
39
+ }
40
+
41
+ .title {
42
+ font-size: 22px;
43
+ margin-bottom: 10px;
44
+ }
45
+
46
+ .intro {
47
+ font-size: 16px;
48
+ line-height: 1.6;
49
+ margin: 0;
50
+ }
51
+
52
+ .note-box {
53
+ background-color: #f7f3ff;
54
+ border-left: 4px solid #722ED1;
55
+ padding: 16px;
56
+ border-radius: 5px;
57
+ margin: 24px 0;
58
+ }
59
+
60
+ .note-label {
61
+ margin: 0 0 8px;
62
+ font-size: 13px;
63
+ font-weight: 700;
64
+ color: #722ED1;
65
+ text-transform: uppercase;
66
+ letter-spacing: 0.04em;
67
+ }
68
+
69
+ .note-box p {
70
+ margin: 0;
71
+ font-size: 16px;
72
+ line-height: 1.6;
73
+ white-space: pre-wrap;
74
+ }
75
+
76
+ .note-body {
77
+ font-weight: 700;
78
+ font-style: italic;
79
+ color: #2f2450;
80
+ }
81
+
82
+ .record-summary {
83
+ background-color: #fbfbfd;
84
+ border: 1px solid #ececf1;
85
+ padding: 14px;
86
+ border-radius: 5px;
87
+ margin-bottom: 24px;
88
+ }
89
+
90
+ .record-summary p {
91
+ margin: 0 0 8px;
92
+ font-size: 14px;
93
+ line-height: 1.5;
94
+ }
95
+
96
+ .button-container {
97
+ text-align: center;
98
+ margin-bottom: 30px;
99
+ margin-top: 20px;
100
+ }
101
+
102
+ .button {
103
+ display: inline-block;
104
+ padding: 12px 24px;
105
+ background-color: #722ED1;
106
+ color: #ffffff;
107
+ text-decoration: none;
108
+ font-weight: bold;
109
+ border-radius: 5px;
110
+ }
111
+
112
+ .footer-text {
113
+ font-size: 16px;
114
+ line-height: 1.6;
115
+ }
116
+
117
+ .footer {
118
+ text-align: center;
119
+ font-size: 12px;
120
+ color: #6b6e76;
121
+ margin-top: 40px;
122
+ }
123
+
124
+ @media only screen and (max-width: 600px) {
125
+ .email-wrapper {
126
+ width: 100% !important;
127
+ padding: 20px 0 !important;
128
+ margin: 0;
129
+ }
130
+
131
+ .content-box {
132
+ padding: 20px !important;
133
+ border-radius: 0;
134
+ }
135
+
136
+ .title {
137
+ font-size: 18px !important;
138
+ }
139
+
140
+ .button {
141
+ padding: 10px !important;
142
+ font-size: 14px;
143
+ }
144
+ }
145
+ </style>
146
+ </head>
147
+
148
+ <body>
149
+ <div class="email-wrapper">
150
+ {{#if companyLogoUrl}}
151
+ <div class="logo">
152
+ <img src="{{companyLogoUrl}}" alt="Company Logo" />
153
+ </div>
154
+ {{/if}}
155
+
156
+ <div class="content-box">
157
+ <h2 class="title">Hi {{ mentionedUserDisplayName }},</h2>
158
+ <p class="intro">
159
+ <strong>{{ mentionedByDisplayName }}</strong> mentioned you in a note on
160
+ <strong>{{ entityDisplayName }}</strong>{{#if entityUserKey}} <strong>{{ entityUserKey }}</strong>{{/if}}.
161
+ </p>
162
+
163
+ <div class="note-box">
164
+ <p class="note-label" style="margin: 0 0 8px; font-size: 13px; font-weight: 700; color: #722ED1; text-transform: uppercase; letter-spacing: 0.04em;">
165
+ Note
166
+ </p>
167
+ <p class="note-body" style="margin: 0; font-size: 16px; line-height: 1.6; white-space: pre-wrap; font-weight: 700; font-style: italic; color: #2f2450;">
168
+ {{ noteBody }}
169
+ </p>
170
+ </div>
171
+
172
+ <div class="record-summary">
173
+ <p><strong>Record:</strong> {{ entityDisplayName }}{{#if entityUserKey}} - {{ entityUserKey }}{{/if}}</p>
174
+ <p><strong>Mentioned by:</strong> {{ mentionedByDisplayName }}{{#if mentionedByEmail}} ({{ mentionedByEmail }}){{/if}}</p>
175
+ </div>
176
+
177
+ <p class="intro">
178
+ To view the note and reply to {{ mentionedByDisplayName }}, open the record below.
179
+ </p>
180
+
181
+ <div class="button-container">
182
+ <a href="{{ entityFormUrl }}" class="button" target="_blank">
183
+ Open Record
184
+ </a>
185
+ </div>
186
+
187
+ <p class="intro">
188
+ Regards,
189
+ <br />
190
+ The {{ solidAppName }} Team.
191
+ </p>
192
+ </div>
193
+
194
+ <div class="footer">
195
+ © 2025 <a href="{{ solidAppWebsiteUrl }}" target="_blank">{{ solidAppName }}</a>. All rights reserved.
196
+ </div>
197
+ </div>
198
+ </body>
199
+
200
+ </html>
@@ -1704,6 +1704,18 @@
1704
1704
  "columnName": "original_file_name",
1705
1705
  "isSystem": true
1706
1706
  },
1707
+ {
1708
+ "name": "isPublic",
1709
+ "displayName": "Is Public",
1710
+ "type": "boolean",
1711
+ "required": false,
1712
+ "unique": false,
1713
+ "index": false,
1714
+ "private": false,
1715
+ "encrypt": false,
1716
+ "columnName": "is_public",
1717
+ "isSystem": true
1718
+ },
1707
1719
  {
1708
1720
  "name": "modelMetadata",
1709
1721
  "displayName": "Model Metadata",
@@ -3911,6 +3923,18 @@
3911
3923
  "encrypt": false,
3912
3924
  "isSystem": false
3913
3925
  },
3926
+ {
3927
+ "name": "messageBodyMentions",
3928
+ "displayName": "Message Body Mentions",
3929
+ "type": "longText",
3930
+ "ormType": "text",
3931
+ "required": false,
3932
+ "unique": false,
3933
+ "index": false,
3934
+ "private": false,
3935
+ "encrypt": false,
3936
+ "isSystem": false
3937
+ },
3914
3938
  {
3915
3939
  "name": "coModelEntityId",
3916
3940
  "displayName": "CoModel Entity Id",
@@ -5294,6 +5318,9 @@
5294
5318
  "FieldMetadataController.getSelectionDynamicValues",
5295
5319
  "FieldMetadataController.getSelectionDynamicValue",
5296
5320
  "FieldMetadataController.findFieldDefaultMetaData",
5321
+ "MediaController.findMany",
5322
+ "MediaController.findOne",
5323
+ "MediaController.download",
5297
5324
  "SavedFiltersController.delete",
5298
5325
  "SavedFiltersController.deleteMany",
5299
5326
  "SavedFiltersController.findOne",
@@ -8595,9 +8622,9 @@
8595
8622
  "attrs": {
8596
8623
  "name": "roles",
8597
8624
  "widget": "checkbox",
8598
- "inlineCreateAutoSave": "true",
8625
+ "inlineCreateAutoSave": "false",
8599
8626
  "renderModeCheckboxPreprocessor": "MenuItemMetadataRolesFormFieldPreprocessor",
8600
- "inlineCreate": "true",
8627
+ "inlineCreate": "false",
8601
8628
  "inlineCreateLayout": {
8602
8629
  "type": "form",
8603
8630
  "attrs": {
@@ -8746,6 +8773,13 @@
8746
8773
  "sortable": false
8747
8774
  }
8748
8775
  },
8776
+ {
8777
+ "type": "field",
8778
+ "attrs": {
8779
+ "name": "isPublic",
8780
+ "sortable": true
8781
+ }
8782
+ },
8749
8783
  {
8750
8784
  "type": "field",
8751
8785
  "attrs": {
@@ -8841,6 +8875,14 @@
8841
8875
  "isSearchable": true
8842
8876
  }
8843
8877
  },
8878
+ {
8879
+ "type": "field",
8880
+ "attrs": {
8881
+ "name": "isPublic",
8882
+ "label": "isPublic",
8883
+ "isSearchable": true
8884
+ }
8885
+ },
8844
8886
  {
8845
8887
  "type": "field",
8846
8888
  "attrs": {
@@ -8909,6 +8951,12 @@
8909
8951
  "name": "mediaStorageProviderMetadata"
8910
8952
  }
8911
8953
  },
8954
+ {
8955
+ "type": "field",
8956
+ "attrs": {
8957
+ "name": "isPublic"
8958
+ }
8959
+ },
8912
8960
  {
8913
8961
  "type": "field",
8914
8962
  "attrs": {
@@ -14461,6 +14509,15 @@
14461
14509
  "description": "Welcome email sent to users after signup.",
14462
14510
  "active": true,
14463
14511
  "type": "text"
14512
+ },
14513
+ {
14514
+ "name": "chatter-mention-notification",
14515
+ "displayName": "Default: Chatter Mention Notification",
14516
+ "body": "chatter-mention-notification.handlebars.html",
14517
+ "subject": "{{mentionedByDisplayName}} mentioned you in {{entityDisplayName}}",
14518
+ "description": "This template notifies users when they are mentioned in a custom chatter note.",
14519
+ "active": true,
14520
+ "type": "text"
14464
14521
  }
14465
14522
  ],
14466
14523
  "smsTemplates": [
@@ -14504,7 +14561,15 @@
14504
14561
  "mediaStorageProviders": [
14505
14562
  {
14506
14563
  "name": "default-filesystem",
14507
- "type": "filesystem"
14564
+ "type": "filesystem",
14565
+ "isPublic": true,
14566
+ "localPath": "media-files-storage"
14567
+ },
14568
+ {
14569
+ "name": "private-filesystem",
14570
+ "type": "filesystem",
14571
+ "isPublic": false,
14572
+ "localPath": "media-private-files-storage"
14508
14573
  },
14509
14574
  {
14510
14575
  "name": "default-aws-s3",
@@ -14540,6 +14605,47 @@
14540
14605
  }
14541
14606
  }
14542
14607
  }
14608
+ },
14609
+ {
14610
+ "name": "model:Media-role:Internal User-own",
14611
+ "description": "Show Media records owned by the current user",
14612
+ "roleUserKey": "Internal User",
14613
+ "modelMetadataUserKey": "media",
14614
+ "securityRuleConfig": {
14615
+ "filters": {
14616
+ "createdBy": {
14617
+ "$eq": "$activeUserId"
14618
+ }
14619
+ }
14620
+ }
14621
+ },
14622
+ {
14623
+ "name": "model:Media-role:Internal User-public",
14624
+ "description": "Show public Media records",
14625
+ "roleUserKey": "Internal User",
14626
+ "modelMetadataUserKey": "media",
14627
+ "securityRuleConfig": {
14628
+ "filters": {
14629
+ "mediaStorageProviderMetadata": {
14630
+ "isPublic": {
14631
+ "$eq": true
14632
+ }
14633
+ }
14634
+ }
14635
+ }
14636
+ },
14637
+ {
14638
+ "name": "model:Media-role:Admin",
14639
+ "description": "Show All Media records",
14640
+ "roleUserKey": "Admin",
14641
+ "modelMetadataUserKey": "media",
14642
+ "securityRuleConfig": {
14643
+ "filters": {
14644
+ "id": {
14645
+ "$ne": "0"
14646
+ }
14647
+ }
14648
+ }
14543
14649
  }
14544
14650
  ],
14545
14651
  "modelSequences": [],
@@ -11,6 +11,7 @@ import { PostChatterMessageDto } from 'src/dtos/post-chatter-message.dto';
11
11
  import { UpdateChatterNoteMessageDto } from 'src/dtos/update-chatter-note-message.dto';
12
12
  import { ModelMetadataHelperService } from 'src/helpers/model-metadata-helper.service';
13
13
  import { lowerFirst } from 'src/helpers/string.helper';
14
+ import { ChatterMentionNotificationPayload } from 'src/interfaces/chatter-mention-notification.interface';
14
15
  import { ChatterMessageDetailsRepository } from 'src/repository/chatter-message-details.repository';
15
16
  import { ChatterMessageRepository } from 'src/repository/chatter-message.repository';
16
17
  import { FieldMetadataRepository } from 'src/repository/field-metadata.repository';
@@ -24,6 +25,14 @@ import { getMediaStorageProvider } from './mediaStorageProviders';
24
25
  import { RequestContextService } from './request-context.service';
25
26
  import { Logger } from '@nestjs/common';
26
27
  import { SolidIntrospectService } from './solid-introspect.service';
28
+ import { PublisherFactory } from './queues/publisher-factory.service';
29
+
30
+ interface ChatterMention {
31
+ username: string;
32
+ display_name?: string;
33
+ displayName?: string;
34
+ id?: string | number;
35
+ }
27
36
 
28
37
  @Injectable()
29
38
  export class ChatterMessageService extends CRUDService<ChatterMessage> {
@@ -47,6 +56,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
47
56
  private readonly modelMetadataRepo: ModelMetadataRepository,
48
57
  readonly requestContextService: RequestContextService,
49
58
  private readonly modelMetadataHelperService: ModelMetadataHelperService,
59
+ private readonly publisherFactory: PublisherFactory<ChatterMentionNotificationPayload>,
50
60
  ) {
51
61
  super(entityManager, repo, 'chatterMessage', 'solid-core', moduleRef);
52
62
  }
@@ -106,6 +116,78 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
106
116
  .filter(v => Number.isInteger(v) && v > 0);
107
117
  }
108
118
 
119
+ private parseMessageBodyMentions(value?: string): ChatterMention[] {
120
+ if (!value || typeof value !== 'string') return [];
121
+ try {
122
+ const parsed = JSON.parse(value);
123
+ return Array.isArray(parsed) ? parsed : [];
124
+ } catch {
125
+ return [];
126
+ }
127
+ }
128
+
129
+ private renderMentionTokens(messageBody: string, mentions: ChatterMention[]) {
130
+ return mentions.reduce((nextMessage, mention, index) => {
131
+ const username = mention.username;
132
+ if (!username) return nextMessage;
133
+ return nextMessage.replace(new RegExp(`\\{\\{\\s*${index}\\s*\\}\\}`, 'g'), `@${username}`);
134
+ }, messageBody || '');
135
+ }
136
+
137
+ private async publishChatterMentionNotifications(message: ChatterMessage, model: any) {
138
+ if (message.messageType !== CHATTER_MESSAGE_TYPE.CUSTOM || message.messageSubType !== CHATTER_MESSAGE_SUBTYPE.NOTE) {
139
+ return;
140
+ }
141
+
142
+ const mentions = this.parseMessageBodyMentions(message.messageBodyMentions);
143
+ const mentionUserIds = mentions
144
+ .map(mention => Number(mention.id))
145
+ .filter(id => Number.isInteger(id) && id > 0);
146
+ const uniqueMentionUserIds = Array.from(new Set(mentionUserIds));
147
+ if (uniqueMentionUserIds.length === 0) return;
148
+
149
+ const activeUser = this.requestContextService.getActiveUser();
150
+
151
+ const payload: ChatterMentionNotificationPayload = {
152
+ templateName: 'chatter-mention-notification',
153
+ mentions: mentions.map(mention => ({
154
+ id: Number(mention.id) || undefined,
155
+ username: mention.username,
156
+ displayName: mention.display_name || mention.displayName || mention.username,
157
+ })),
158
+ actor: {
159
+ id: activeUser?.sub,
160
+ username: activeUser?.username,
161
+ email: activeUser?.email,
162
+ },
163
+ noteBody: this.renderMentionTokens(message.messageBody, mentions),
164
+ entity: {
165
+ id: message.coModelEntityId,
166
+ modelName: model?.singularName || message.coModelName,
167
+ moduleName: model?.module?.name,
168
+ displayName: model?.displayName || message.coModelName,
169
+ userKey: message.modelUserKey,
170
+ },
171
+ parentEntity: 'chatterMessage',
172
+ parentEntityId: message.id,
173
+ };
174
+
175
+ try {
176
+ await this.publisherFactory.publish(
177
+ {
178
+ payload,
179
+ parentEntity: 'chatterMessage',
180
+ parentEntityId: message.id,
181
+ retryCount: 3,
182
+ retryInterval: 5000,
183
+ },
184
+ 'ChatterMentionNotificationEmailQueuePublisher',
185
+ );
186
+ } catch (error: any) {
187
+ this._logger.error(`Failed to publish chatter mention notification email job: ${error.message}`, error.stack);
188
+ }
189
+ }
190
+
109
191
  async markCompleted(id: number) {
110
192
  const activeUser = this.requestContextService.getActiveUser();
111
193
  if (!activeUser) {
@@ -146,10 +228,11 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
146
228
 
147
229
  const removeAttachmentIds = this.parseAttachmentIds(updateDto?.removeAttachmentIds);
148
230
  const hasMessageBody = typeof updateDto?.messageBody === 'string';
231
+ const hasMessageBodyMentions = typeof updateDto?.messageBodyMentions === 'string';
149
232
  const trimmedMessageBody = (updateDto?.messageBody ?? '').trim();
150
233
  const hasNewFiles = Array.isArray(files) && files.length > 0;
151
234
 
152
- if (!hasMessageBody && removeAttachmentIds.length === 0 && !hasNewFiles) {
235
+ if (!hasMessageBody && !hasMessageBodyMentions && removeAttachmentIds.length === 0 && !hasNewFiles) {
153
236
  throw new BadRequestException('No note changes submitted.');
154
237
  }
155
238
 
@@ -160,6 +243,13 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
160
243
  if (hasMessageBody) {
161
244
  message.messageBody = trimmedMessageBody;
162
245
  }
246
+ if (hasMessageBodyMentions) {
247
+ message.messageBodyMentions = updateDto.messageBodyMentions;
248
+ }
249
+ const model = await this.modelMetadataRepo.findOne({
250
+ where: { singularName: message.coModelName },
251
+ relations: { module: true }
252
+ });
163
253
  message.updatedBy = activeUser.sub;
164
254
  // Ensure updatedAt changes even for attachment-only edits.
165
255
  message.updatedAt = new Date();
@@ -214,6 +304,8 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
214
304
  }
215
305
  }
216
306
 
307
+ await this.publishChatterMentionNotifications(savedMessage, model);
308
+
217
309
  return savedMessage;
218
310
  }
219
311
 
@@ -226,13 +318,14 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
226
318
  chatterMessage.messageSubType = postDto.messageSubType || CHATTER_MESSAGE_SUBTYPE.CUSTOM;
227
319
  chatterMessage.status = postDto.status ?? CHATTER_MESSAGE_STATUS.PENDING;
228
320
  chatterMessage.messageBody = postDto.messageBody;
321
+ chatterMessage.messageBodyMentions = postDto.messageBodyMentions;
229
322
  chatterMessage.coModelEntityId = postDto.coModelEntityId;
230
323
  chatterMessage.coModelName = coModelName;
231
324
  chatterMessage.modelUserKey = postDto.modelUserKey ?? null;
232
325
 
233
326
  const model = await this.modelMetadataRepo.findOne({
234
327
  where: { singularName: coModelName },
235
- relations: { userKeyField: true }
328
+ relations: { userKeyField: true, module: true }
236
329
  });
237
330
  chatterMessage.modelDisplayName = model?.displayName ?? null;
238
331
 
@@ -262,6 +355,8 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
262
355
  }
263
356
  }
264
357
 
358
+ await this.publishChatterMentionNotifications(savedMessage, model);
359
+
265
360
  return savedMessage;
266
361
  }
267
362
 
@@ -0,0 +1,67 @@
1
+ import { Injectable, Logger } from '@nestjs/common';
2
+ import { ConfigService } from '@nestjs/config';
3
+ import { JwtService } from '@nestjs/jwt';
4
+ import { MediaStorageProviderType } from 'src/dtos/create-media-storage-provider-metadata.dto';
5
+ import { MediaStorageProviderMetadata } from 'src/entities/media-storage-provider-metadata.entity';
6
+ import { S3FileService } from 'src/services/file';
7
+ import { getEffectiveS3Region } from 'src/services/media-storage.utils';
8
+ import { SettingService } from 'src/services/setting.service';
9
+ import type { SolidCoreSetting } from 'src/services/settings/default-settings-provider.service';
10
+
11
+ export const MEDIA_DOWNLOAD_TOKEN_AUDIENCE = 'media-download';
12
+ const DEFAULT_SIGNED_URL_EXPIRY_MINUTES = 60;
13
+
14
+ export interface MediaDownloadTokenPayload {
15
+ mediaId: number;
16
+ }
17
+
18
+ @Injectable()
19
+ export class MediaDownloadUrlService {
20
+ private readonly logger = new Logger(MediaDownloadUrlService.name);
21
+
22
+ constructor(
23
+ private readonly jwtService: JwtService,
24
+ private readonly s3FileService: S3FileService,
25
+ private readonly settingService: SettingService,
26
+ private readonly configService: ConfigService,
27
+ ) { }
28
+
29
+ async resolveDownloadUrl(mediaId: number, relativeUri: string, storageProvider?: MediaStorageProviderMetadata): Promise<string> {
30
+ const expiryMinutes = storageProvider?.signedUrlExpiry ?? DEFAULT_SIGNED_URL_EXPIRY_MINUTES;
31
+
32
+ if ((storageProvider?.type as MediaStorageProviderType) === MediaStorageProviderType.AwsS3) {
33
+ if (storageProvider?.bucketName) {
34
+ return this.s3FileService.getUrl(
35
+ `${storageProvider.bucketName}:${relativeUri}`,
36
+ { region: getEffectiveS3Region(this.configService, storageProvider.region), expiresIn: expiryMinutes * 60 },
37
+ );
38
+ }
39
+ this.logger.warn(`Media id ${mediaId}: AwsS3 provider missing bucketName; falling back to app-hosted token URL`);
40
+ }
41
+
42
+ // Filesystem (or misconfigured S3) has no native presigned-URL mechanism, so mint our
43
+ // own short-lived token and embed it in the download URL - the token alone authorizes
44
+ // the request (see MediaSignedUrlGuard), so it works in a plain <img src> with no
45
+ // Authorization header needed.
46
+ const token = await this.mintDownloadToken(mediaId, expiryMinutes);
47
+ const downloadPath = `/api/media/${mediaId}/download?token=${token}`;
48
+ const baseUrl = this.settingService.getConfigValue<SolidCoreSetting>('baseUrl');
49
+ return `${String(baseUrl).replace(/\/+$/, '')}${downloadPath}`;
50
+ }
51
+
52
+ async verifyDownloadToken(token: string): Promise<MediaDownloadTokenPayload> {
53
+ return this.jwtService.verifyAsync<MediaDownloadTokenPayload>(token, {
54
+ secret: this.settingService.getConfigValue<SolidCoreSetting>('secret'),
55
+ audience: MEDIA_DOWNLOAD_TOKEN_AUDIENCE,
56
+ });
57
+ }
58
+
59
+ private mintDownloadToken(mediaId: number, expiryMinutes: number): Promise<string> {
60
+ const payload: MediaDownloadTokenPayload = { mediaId };
61
+ return this.jwtService.signAsync(payload, {
62
+ secret: this.settingService.getConfigValue<SolidCoreSetting>('secret'),
63
+ audience: MEDIA_DOWNLOAD_TOKEN_AUDIENCE,
64
+ expiresIn: expiryMinutes * 60,
65
+ });
66
+ }
67
+ }