@lilaquadrat/studio 10.0.0-beta.9 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/eslint.config.js +146 -0
  2. package/lib/fastify-plugins.d.ts +6 -0
  3. package/lib/fastify-plugins.js +7 -0
  4. package/lib/fastify-plugins.js.map +1 -0
  5. package/lib/helpers.d.ts +4 -2
  6. package/lib/helpers.js +13 -2
  7. package/lib/helpers.js.map +1 -1
  8. package/lib/main.d.ts +1 -3
  9. package/lib/main.js +7 -3
  10. package/lib/main.js.map +1 -1
  11. package/lib/models.d.ts +4 -4
  12. package/lib/models.js +4 -4
  13. package/lib/models.js.map +1 -1
  14. package/lib/services.d.ts +5 -5
  15. package/lib/services.js +5 -13
  16. package/lib/services.js.map +1 -1
  17. package/lib/src/Immutable.class.d.ts +8 -1
  18. package/lib/src/Immutable.class.js +52 -8
  19. package/lib/src/Immutable.class.js.map +1 -1
  20. package/lib/src/ShareClientFactory.class.d.ts +1 -3
  21. package/lib/src/ShareClientFactory.class.js +1 -9
  22. package/lib/src/ShareClientFactory.class.js.map +1 -1
  23. package/lib/src/classes/models.class.js.map +1 -1
  24. package/lib/src/classes/modelv2.class.d.ts +2 -0
  25. package/lib/src/classes/modelv2.class.js +1 -1
  26. package/lib/src/classes/modelv2.class.js.map +1 -1
  27. package/lib/src/classes/mongo.class.js +4 -14
  28. package/lib/src/classes/mongo.class.js.map +1 -1
  29. package/lib/src/functions/handleError.d.ts +2 -3
  30. package/lib/src/functions/handleError.js +3 -16
  31. package/lib/src/functions/handleError.js.map +1 -1
  32. package/lib/src/functions/optionsHelper.d.ts +4 -4
  33. package/lib/src/functions/optionsHelper.js +5 -4
  34. package/lib/src/functions/optionsHelper.js.map +1 -1
  35. package/lib/src/functions/respondCode.d.ts +2 -1
  36. package/lib/src/functions/respondCode.js +1 -1
  37. package/lib/src/functions/respondCode.js.map +1 -1
  38. package/lib/src/helpers/ControllerHelper.d.ts +73 -0
  39. package/lib/src/helpers/ControllerHelper.js +242 -0
  40. package/lib/src/helpers/ControllerHelper.js.map +1 -0
  41. package/lib/src/helpers/EnvMapper.js +1 -0
  42. package/lib/src/helpers/EnvMapper.js.map +1 -0
  43. package/lib/src/helpers/auth0config.d.ts +6 -0
  44. package/lib/src/helpers/auth0config.js +23 -0
  45. package/lib/src/helpers/auth0config.js.map +1 -0
  46. package/lib/src/helpers/authPlugin.d.ts +29 -0
  47. package/lib/src/helpers/authPlugin.js +77 -0
  48. package/lib/src/helpers/authPlugin.js.map +1 -0
  49. package/lib/src/helpers/cacheHelper.d.ts +69 -0
  50. package/lib/src/helpers/cacheHelper.js +235 -0
  51. package/lib/src/helpers/cacheHelper.js.map +1 -0
  52. package/lib/src/helpers/createSasToken.d.ts +0 -2
  53. package/lib/src/helpers/createSasToken.js +35 -32
  54. package/lib/src/helpers/createSasToken.js.map +1 -1
  55. package/lib/src/helpers/getSecrets.d.ts +1 -1
  56. package/lib/src/helpers/getSecrets.js +10 -12
  57. package/lib/src/helpers/getSecrets.js.map +1 -1
  58. package/lib/src/helpers/limiterPlugin.d.ts +9 -0
  59. package/lib/src/helpers/limiterPlugin.js +72 -0
  60. package/lib/src/helpers/limiterPlugin.js.map +1 -0
  61. package/lib/src/helpers/loggingPlugin.d.ts +30 -0
  62. package/lib/src/helpers/loggingPlugin.js +87 -0
  63. package/lib/src/helpers/loggingPlugin.js.map +1 -0
  64. package/lib/src/helpers/queryAssertionPlugin.d.ts +3 -0
  65. package/lib/src/helpers/queryAssertionPlugin.js +20 -0
  66. package/lib/src/helpers/queryAssertionPlugin.js.map +1 -0
  67. package/lib/src/helpers/safeObjectId.d.ts +1 -1
  68. package/lib/src/helpers/safeObjectId.js +5 -1
  69. package/lib/src/helpers/safeObjectId.js.map +1 -1
  70. package/lib/src/helpers/storageSdkFactory.d.ts +2 -0
  71. package/lib/src/helpers/storageSdkFactory.js +11 -0
  72. package/lib/src/helpers/storageSdkFactory.js.map +1 -0
  73. package/lib/src/helpers/studioAppPlugin.d.ts +3 -0
  74. package/lib/src/helpers/studioAppPlugin.js +16 -0
  75. package/lib/src/helpers/studioAppPlugin.js.map +1 -0
  76. package/lib/src/logger.js +40 -2
  77. package/lib/src/logger.js.map +1 -1
  78. package/lib/src/models/access.model.d.ts +14 -3
  79. package/lib/src/models/access.model.js +7 -9
  80. package/lib/src/models/access.model.js.map +1 -1
  81. package/lib/src/models/customers.model.js +14 -4
  82. package/lib/src/models/customers.model.js.map +1 -1
  83. package/lib/src/models/design.model.d.ts +4 -0
  84. package/lib/src/models/design.model.js +58 -0
  85. package/lib/src/models/design.model.js.map +1 -0
  86. package/lib/src/models/domain.model.js +1 -1
  87. package/lib/src/models/domain.model.js.map +1 -1
  88. package/lib/src/models/editor.model.js +7 -0
  89. package/lib/src/models/editor.model.js.map +1 -1
  90. package/lib/src/models/emailLimit.model.d.ts +4 -0
  91. package/lib/src/models/emailLimit.model.js +31 -0
  92. package/lib/src/models/emailLimit.model.js.map +1 -0
  93. package/lib/src/models/hosting.model.js +1 -3
  94. package/lib/src/models/hosting.model.js.map +1 -1
  95. package/lib/src/models/hostingSettings.model.js +6 -4
  96. package/lib/src/models/hostingSettings.model.js.map +1 -1
  97. package/lib/src/models/invoice.model.d.ts +4 -0
  98. package/lib/src/models/invoice.model.js +235 -0
  99. package/lib/src/models/invoice.model.js.map +1 -0
  100. package/lib/src/models/mailFrom.model.js +51 -10
  101. package/lib/src/models/mailFrom.model.js.map +1 -1
  102. package/lib/src/models/project.model.js +2 -4
  103. package/lib/src/models/project.model.js.map +1 -1
  104. package/lib/src/models/publish-method.model.js +79 -430
  105. package/lib/src/models/publish-method.model.js.map +1 -1
  106. package/lib/src/models/publish.model.js +6 -0
  107. package/lib/src/models/publish.model.js.map +1 -1
  108. package/lib/src/models/storage.model.js +23 -5
  109. package/lib/src/models/storage.model.js.map +1 -1
  110. package/lib/src/models/structure.model.js +40 -0
  111. package/lib/src/models/structure.model.js.map +1 -1
  112. package/lib/src/models/upload.model.js +38 -2
  113. package/lib/src/models/upload.model.js.map +1 -1
  114. package/lib/src/prompts/textGeneration.js +88 -0
  115. package/lib/src/prompts/textGeneration.js.map +1 -1
  116. package/lib/src/prompts/textGenerationMulti.js +78 -44
  117. package/lib/src/prompts/textGenerationMulti.js.map +1 -1
  118. package/lib/src/services/access.service.d.ts +132 -33
  119. package/lib/src/services/access.service.js +270 -92
  120. package/lib/src/services/access.service.js.map +1 -1
  121. package/lib/src/services/ai.service.d.ts +4 -3
  122. package/lib/src/services/ai.service.js +22 -29
  123. package/lib/src/services/ai.service.js.map +1 -1
  124. package/lib/src/services/auth.service.d.ts +11 -0
  125. package/lib/src/services/auth.service.js +70 -0
  126. package/lib/src/services/auth.service.js.map +1 -0
  127. package/lib/src/services/conf.service.d.ts +3 -31
  128. package/lib/src/services/conf.service.js +58 -167
  129. package/lib/src/services/conf.service.js.map +1 -1
  130. package/lib/src/services/customers.service.d.ts +8 -4
  131. package/lib/src/services/customers.service.js +34 -7
  132. package/lib/src/services/customers.service.js.map +1 -1
  133. package/lib/src/services/designs.service.d.ts +7 -0
  134. package/lib/src/services/designs.service.js +10 -0
  135. package/lib/src/services/designs.service.js.map +1 -0
  136. package/lib/src/services/domains.service.d.ts +18 -84
  137. package/lib/src/services/domains.service.js +91 -583
  138. package/lib/src/services/domains.service.js.map +1 -1
  139. package/lib/src/services/editor.service.d.ts +4 -0
  140. package/lib/src/services/editor.service.js +28 -0
  141. package/lib/src/services/editor.service.js.map +1 -1
  142. package/lib/src/services/emailLimit.service.d.ts +21 -0
  143. package/lib/src/services/emailLimit.service.js +51 -0
  144. package/lib/src/services/emailLimit.service.js.map +1 -0
  145. package/lib/src/services/hosting.service.d.ts +12 -24
  146. package/lib/src/services/hosting.service.js +32 -122
  147. package/lib/src/services/hosting.service.js.map +1 -1
  148. package/lib/src/services/hostingAdmin.service.d.ts +1 -1
  149. package/lib/src/services/hostingAdmin.service.js +2 -2
  150. package/lib/src/services/hostingAdmin.service.js.map +1 -1
  151. package/lib/src/services/import.service.d.ts +6 -22
  152. package/lib/src/services/import.service.js +63 -65
  153. package/lib/src/services/import.service.js.map +1 -1
  154. package/lib/src/services/invoices.service.d.ts +30 -0
  155. package/lib/src/services/invoices.service.js +265 -0
  156. package/lib/src/services/invoices.service.js.map +1 -0
  157. package/lib/src/services/jetstream.service.d.ts +5 -3
  158. package/lib/src/services/jetstream.service.js +63 -7
  159. package/lib/src/services/jetstream.service.js.map +1 -1
  160. package/lib/src/services/listParticipants.service.d.ts +3 -5
  161. package/lib/src/services/listParticipants.service.js +76 -16
  162. package/lib/src/services/listParticipants.service.js.map +1 -1
  163. package/lib/src/services/mailFrom.service.d.ts +14 -1
  164. package/lib/src/services/mailFrom.service.js +59 -0
  165. package/lib/src/services/mailFrom.service.js.map +1 -1
  166. package/lib/src/services/me.service.d.ts +23 -12
  167. package/lib/src/services/me.service.js +65 -88
  168. package/lib/src/services/me.service.js.map +1 -1
  169. package/lib/src/services/publish.service.d.ts +6 -8
  170. package/lib/src/services/publish.service.js +34 -32
  171. package/lib/src/services/publish.service.js.map +1 -1
  172. package/lib/src/services/publishData.service.d.ts +10 -7
  173. package/lib/src/services/publishData.service.js +32 -75
  174. package/lib/src/services/publishData.service.js.map +1 -1
  175. package/lib/src/services/spamAnalasys.service.d.ts +4 -4
  176. package/lib/src/services/spamAnalasys.service.js +36 -44
  177. package/lib/src/services/spamAnalasys.service.js.map +1 -1
  178. package/lib/src/services/storage.service.d.ts +68 -39
  179. package/lib/src/services/storage.service.js +382 -209
  180. package/lib/src/services/storage.service.js.map +1 -1
  181. package/lib/src/services/structures.service.d.ts +8 -1
  182. package/lib/src/services/structures.service.js +26 -1
  183. package/lib/src/services/structures.service.js.map +1 -1
  184. package/lib/src/services/upload.service.d.ts +8 -1
  185. package/lib/src/services/upload.service.js +76 -3
  186. package/lib/src/services/upload.service.js.map +1 -1
  187. package/lib/tests/groupStructuresByModel.spec.d.ts +1 -0
  188. package/lib/tests/groupStructuresByModel.spec.js +33 -0
  189. package/lib/tests/groupStructuresByModel.spec.js.map +1 -0
  190. package/lib/tests/listParticipantsServiceJoin.spec.d.ts +1 -0
  191. package/lib/tests/listParticipantsServiceJoin.spec.js +151 -0
  192. package/lib/tests/listParticipantsServiceJoin.spec.js.map +1 -0
  193. package/lib/tests/storageServiceHandleFile.spec.d.ts +1 -0
  194. package/lib/tests/storageServiceHandleFile.spec.js +94 -0
  195. package/lib/tests/storageServiceHandleFile.spec.js.map +1 -0
  196. package/lib/tests/storageServiceToken.spec.d.ts +1 -0
  197. package/lib/tests/storageServiceToken.spec.js +104 -0
  198. package/lib/tests/storageServiceToken.spec.js.map +1 -0
  199. package/lib/tests/uploadServiceCreate.spec.d.ts +1 -0
  200. package/lib/tests/uploadServiceCreate.spec.js +81 -0
  201. package/lib/tests/uploadServiceCreate.spec.js.map +1 -0
  202. package/package.json +30 -26
  203. package/lib/src/AzureBlobStorage.share.d.ts +0 -19
  204. package/lib/src/AzureBlobStorage.share.js +0 -162
  205. package/lib/src/AzureBlobStorage.share.js.map +0 -1
  206. package/lib/src/AzureFileStorage.share.d.ts +0 -22
  207. package/lib/src/AzureFileStorage.share.js +0 -139
  208. package/lib/src/AzureFileStorage.share.js.map +0 -1
  209. package/lib/src/AzureVault.d.ts +0 -14
  210. package/lib/src/AzureVault.js +0 -28
  211. package/lib/src/AzureVault.js.map +0 -1
  212. package/lib/src/dns.challenge.class.d.ts +0 -17
  213. package/lib/src/dns.challenge.class.js +0 -41
  214. package/lib/src/dns.challenge.class.js.map +0 -1
  215. package/lib/src/http.challenge.class.d.ts +0 -33
  216. package/lib/src/http.challenge.class.js +0 -58
  217. package/lib/src/http.challenge.class.js.map +0 -1
  218. package/lib/src/models/certificate-action.model.d.ts +0 -5
  219. package/lib/src/models/certificate-action.model.js +0 -230
  220. package/lib/src/models/certificate-action.model.js.map +0 -1
  221. package/lib/src/models/certificate.model.d.ts +0 -4
  222. package/lib/src/models/certificate.model.js +0 -96
  223. package/lib/src/models/certificate.model.js.map +0 -1
  224. package/lib/src/models/editorBase.model.d.ts +0 -4
  225. package/lib/src/models/editorBase.model.js +0 -39
  226. package/lib/src/models/editorBase.model.js.map +0 -1
  227. package/lib/src/services/certificates.service.js +0 -199
  228. package/lib/src/services/certificates.service.js.map +0 -1
  229. package/lib/src/services/certificatesAction.service.d.ts +0 -0
  230. package/lib/src/services/certificatesAction.service.js +0 -237
  231. package/lib/src/services/certificatesAction.service.js.map +0 -1
  232. package/lib/src/services/editorBase.service.d.ts +0 -46
  233. package/lib/src/services/editorBase.service.js +0 -161
  234. package/lib/src/services/editorBase.service.js.map +0 -1
  235. package/lib/src/services/handleFile.service.d.ts +0 -9
  236. package/lib/src/services/handleFile.service.js +0 -45
  237. package/lib/src/services/handleFile.service.js.map +0 -1
  238. package/lib/src/services/media.service.d.ts +0 -35
  239. package/lib/src/services/media.service.js +0 -418
  240. package/lib/src/services/media.service.js.map +0 -1
  241. package/lib/src/services/share.service.d.ts +0 -6
  242. package/lib/src/services/share.service.js +0 -4
  243. package/lib/src/services/share.service.js.map +0 -1
  244. /package/lib/src/{services/certificates.service.d.ts → helpers/EnvMapper.d.ts} +0 -0
@@ -8,31 +8,10 @@ import StorageModel from '../models/storage.model.js';
8
8
  import JetStreamService from './jetstream.service.js';
9
9
  import UploadService from './upload.service.js';
10
10
  import filenameHelper from '../helpers/filenameHelper.js';
11
+ import jetstreamService from './jetstream.service.js';
11
12
  export class StorageService extends Immutable {
12
13
  constructor(options) {
13
14
  super();
14
- this.buckets = {
15
- cdn: {
16
- public: true,
17
- overwrite: false,
18
- cache: 31536000,
19
- },
20
- hosting: {
21
- public: false,
22
- overwrite: true,
23
- cache: 0,
24
- },
25
- secure: {
26
- public: false,
27
- overwrite: false,
28
- cache: 31536000,
29
- },
30
- internal: {
31
- public: false,
32
- overwrite: false,
33
- cache: 31536000,
34
- },
35
- };
36
15
  this.model = StorageModel;
37
16
  this.options = options;
38
17
  }
@@ -42,40 +21,48 @@ export class StorageService extends Immutable {
42
21
  * The token is cryptographically signed using HMAC-SHA256 and base64url-encoded to be
43
22
  * non-human readable and URL-safe.
44
23
  *
45
- * @param fileId - The file identifier (ObjectId or string) to grant access to
46
24
  * @param expiresIn - Time in seconds until the token expires (default: 3600 = 1 hour)
47
25
  * @param limits - Optional restrictions to apply to the token
26
+ * @param limits.fileId - Restrict token to specific file (storage _id)
48
27
  * @param limits.company - Restrict token to specific company
49
28
  * @param limits.project - Restrict token to specific project
50
29
  * @param limits.app - Restrict token to specific app
51
30
  * @param limits.url - Restrict token to specific URL path prefix
52
31
  * @param limits.prefix - Restrict token to specific file prefix
32
+ * @param limits.customer - Restrict token to specific customer
33
+ * @param limits.list - Restrict token to specific list
53
34
  *
54
- * @returns Base64url-encoded token containing: fileId:expires[:limits]:signature
35
+ * @returns Base64url-encoded token containing: expires[:limits]:signature
55
36
  *
56
37
  * @example
57
38
  * // Generate token valid for 1 hour with no restrictions
58
- * const { token } = getSecureToken('507f1f77bcf86cd799439011', 3600);
39
+ * const { token } = getSecureToken(3600);
59
40
  *
60
41
  * @example
61
42
  * // Generate token limited to specific company and project
62
- * const { token } = getSecureToken('507f1f77bcf86cd799439011', 3600, {
43
+ * const { token } = getSecureToken(3600, {
63
44
  * company: 'acme',
64
45
  * project: 'website'
65
46
  * });
66
47
  *
67
48
  * @example
49
+ * // Generate token limited to specific file
50
+ * const { token } = getSecureToken(3600, {
51
+ * fileId: '507f1f77bcf86cd799439011'
52
+ * });
53
+ *
54
+ * @example
68
55
  * // Generate token limited to specific URL path
69
- * const { token } = getSecureToken('507f1f77bcf86cd799439011', 3600, {
56
+ * const { token } = getSecureToken(3600, {
70
57
  * url: '/company/project/files/'
71
58
  * });
72
59
  */
73
- getSecureToken(fileId, expiresIn = 3600, limits) {
60
+ getSecureToken(expiresIn = 3600, limits) {
74
61
  const secret = process.env.SERVER_SECRET || 'secret';
75
62
  const now = Math.floor(Date.now() / 1000);
76
63
  const expires = now + expiresIn;
77
- // Build payload: fileId:expires[:base64(limits)]
78
- let payload = `${fileId}:${expires}`;
64
+ // Build payload: expires[:base64(limits)]
65
+ let payload = `${expires}`;
79
66
  if (limits && Object.keys(limits).length > 0) {
80
67
  // Base64 encode the JSON to avoid : separator conflicts
81
68
  const limitsJson = JSON.stringify(limits);
@@ -131,57 +118,101 @@ export class StorageService extends Immutable {
131
118
  decoded = Buffer.from(token, 'base64url').toString('utf-8');
132
119
  }
133
120
  catch {
121
+ console.debug('[validateSecureToken] failed to decode token from base64url');
134
122
  return false;
135
123
  }
136
124
  // Extract payload and signature (signature is after last colon)
137
125
  const lastColonIndex = decoded.lastIndexOf(':');
138
- if (lastColonIndex === -1)
126
+ if (lastColonIndex === -1) {
127
+ console.debug('[validateSecureToken] decoded payload has no colon separator');
139
128
  return false;
129
+ }
140
130
  const payload = decoded.substring(0, lastColonIndex);
141
131
  const signature = decoded.substring(lastColonIndex + 1);
142
132
  // Verify signature matches expected HMAC
143
133
  const expectedSignature = createHmac('sha256', secret).update(payload).digest('hex');
144
- if (signature !== expectedSignature)
134
+ if (signature !== expectedSignature) {
135
+ console.debug('[validateSecureToken] signature mismatch', {
136
+ expected: expectedSignature.substring(0, 8) + '...',
137
+ received: signature.substring(0, 8) + '...',
138
+ });
145
139
  return false;
146
- // Parse payload structure: fileId:expires[:base64(limits)]
140
+ }
141
+ // Parse payload structure: expires[:base64(limits)]
147
142
  const parts = payload.split(':');
148
- // parts[0] is fileId
149
- // parts[1] is expires timestamp
150
- // parts[2] is optional base64-encoded JSON limits object
151
- if (parts.length < 2)
143
+ // parts[0] is expires timestamp
144
+ // parts[1] is optional base64-encoded JSON limits object
145
+ if (parts.length < 1) {
146
+ console.debug('[validateSecureToken] payload is empty', { parts });
152
147
  return false;
148
+ }
153
149
  // Check if token has expired
154
- const expires = parseInt(parts[1], 10);
155
- if (Date.now() / 1000 > expires)
150
+ const expires = parseInt(parts[0], 10);
151
+ if (Number.isNaN(expires)) {
152
+ console.debug('[validateSecureToken] expires is not a number', { parts });
156
153
  return false;
154
+ }
155
+ if (Date.now() / 1000 > expires) {
156
+ console.debug('[validateSecureToken] token expired', {
157
+ expires: new Date(expires * 1000).toISOString(),
158
+ now: new Date().toISOString(),
159
+ });
160
+ return false;
161
+ }
157
162
  // If token has limits, validate against checkLimits
158
- if (parts.length > 2) {
163
+ if (parts.length > 1) {
159
164
  let tokenLimits;
160
165
  try {
161
166
  // Decode from base64 first, then parse JSON
162
- const limitsJson = Buffer.from(parts[2], 'base64').toString('utf-8');
167
+ const limitsJson = Buffer.from(parts[1], 'base64').toString('utf-8');
163
168
  tokenLimits = JSON.parse(limitsJson);
164
169
  }
165
170
  catch {
171
+ console.debug('[validateSecureToken] failed to decode or parse token limits');
166
172
  return false;
167
173
  }
168
174
  // Token has limits but no values provided to check
169
- if (!checkLimits)
175
+ if (!checkLimits) {
176
+ console.debug('[validateSecureToken] token has limits but no checkLimits provided', { tokenLimits });
170
177
  return false;
178
+ }
171
179
  // Validate each limit field if present in token
172
- if (tokenLimits.company && tokenLimits.company !== checkLimits.company)
180
+ if (tokenLimits.fileId && tokenLimits.fileId !== checkLimits.fileId) {
181
+ console.debug('[validateSecureToken] fileId mismatch', { token: tokenLimits.fileId, provided: checkLimits.fileId });
182
+ return false;
183
+ }
184
+ if (tokenLimits.company && tokenLimits.company !== checkLimits.company) {
185
+ console.debug('[validateSecureToken] company mismatch', { token: tokenLimits.company, provided: checkLimits.company });
186
+ return false;
187
+ }
188
+ if (tokenLimits.project && tokenLimits.project !== checkLimits.project) {
189
+ console.debug('[validateSecureToken] project mismatch', { token: tokenLimits.project, provided: checkLimits.project });
190
+ return false;
191
+ }
192
+ if (tokenLimits.app && tokenLimits.app !== checkLimits.app) {
193
+ console.debug('[validateSecureToken] app mismatch', { token: tokenLimits.app, provided: checkLimits.app });
173
194
  return false;
174
- if (tokenLimits.project && tokenLimits.project !== checkLimits.project)
195
+ }
196
+ if (tokenLimits.prefix && tokenLimits.prefix !== checkLimits.prefix) {
197
+ console.debug('[validateSecureToken] prefix mismatch', { token: tokenLimits.prefix, provided: checkLimits.prefix });
175
198
  return false;
176
- if (tokenLimits.app && tokenLimits.app !== checkLimits.app)
199
+ }
200
+ if (tokenLimits.customer && tokenLimits.customer !== checkLimits.customer) {
201
+ console.debug('[validateSecureToken] customer mismatch', { token: tokenLimits.customer, provided: checkLimits.customer });
177
202
  return false;
178
- if (tokenLimits.prefix && tokenLimits.prefix !== checkLimits.prefix)
203
+ }
204
+ if (tokenLimits.list && tokenLimits.list !== checkLimits.list) {
205
+ console.debug('[validateSecureToken] list mismatch', { token: tokenLimits.list, provided: checkLimits.list });
179
206
  return false;
207
+ }
180
208
  // For URL limit, check if requested URL starts with the token's allowed prefix
181
- if (tokenLimits.url && checkLimits.url && !checkLimits.url.startsWith(tokenLimits.url))
209
+ if (tokenLimits.url && checkLimits.url && !checkLimits.url.startsWith(tokenLimits.url)) {
210
+ console.debug('[validateSecureToken] url mismatch', { tokenUrl: tokenLimits.url, providedUrl: checkLimits.url });
182
211
  return false;
212
+ }
183
213
  }
184
214
  else if (checkLimits) {
215
+ console.debug('[validateSecureToken] checkLimits provided but token has no limits');
185
216
  return false;
186
217
  }
187
218
  return true;
@@ -211,6 +242,7 @@ export class StorageService extends Immutable {
211
242
  throw new Error('STORAGE_FILE_NOT_FOUND');
212
243
  // Merge provided limits with storage document context
213
244
  const tokenLimits = {
245
+ fileId: fileId.toString(),
214
246
  company: limits?.company || storage.company,
215
247
  project: limits?.project || storage.project,
216
248
  app: limits?.app || storage.app,
@@ -218,7 +250,7 @@ export class StorageService extends Immutable {
218
250
  url: limits?.url,
219
251
  };
220
252
  // Generate secure token
221
- const tokenData = this.getSecureToken(fileId, expiresIn, tokenLimits);
253
+ const tokenData = this.getSecureToken(expiresIn, tokenLimits);
222
254
  // Construct download URL
223
255
  const baseUrl = process.env.SECURE_URL;
224
256
  const path = storage.prefix ? `${storage.prefix}/${storage.filename}` : storage.filename;
@@ -246,6 +278,21 @@ export class StorageService extends Immutable {
246
278
  await fs.promises.unlink(path.file);
247
279
  throw new Error('FILE_SIZE');
248
280
  }
281
+ // this is not correct.
282
+ // the idea is, the development does not work like that.
283
+ // needs to be reintroduced
284
+ // if (uploadFile.size) {
285
+ //
286
+ // const chunkStat = await fs.promises.stat(path.file);
287
+ // const existingSize = uploadFile.paths?.length
288
+ // ? (await Promise.all(uploadFile.paths.map((p) => fs.promises.stat(p.path).then((s) => s.size).catch(() => 0))))
289
+ // .reduce((sum, s) => sum + s, 0)
290
+ // : 0;
291
+ // if (existingSize + chunkStat.size > uploadFile.size) {
292
+ // await fs.promises.unlink(path.file);
293
+ // throw new Error('FILE_SIZE');
294
+ // }
295
+ // }
249
296
  const updloadState = await UploadService.addPathToUpload(uploadInternalId, index, path.file);
250
297
  /**
251
298
  * if all chunks are there, complete the file and the upload
@@ -272,10 +319,13 @@ export class StorageService extends Immutable {
272
319
  await this.concatenateFilesWithCat(data, pathAndFile.file);
273
320
  }
274
321
  }
275
- else {
322
+ else if (type !== 'copy') {
276
323
  const writeStream = fs.createWriteStream(pathAndFile.file);
277
324
  await pipeline(data, writeStream);
278
325
  }
326
+ else {
327
+ await fs.promises.copyFile(data, pathAndFile.file);
328
+ }
279
329
  return pathAndFile;
280
330
  }
281
331
  async writeFileVersion(data, internalId, version) {
@@ -369,9 +419,7 @@ export class StorageService extends Immutable {
369
419
  catch (error) {
370
420
  if (error.code === 'ENOENT') {
371
421
  console.warn(`File not found: ${fullPath}`);
372
- return;
373
422
  }
374
- throw error;
375
423
  }
376
424
  }
377
425
  /**
@@ -399,12 +447,6 @@ export class StorageService extends Immutable {
399
447
  const file = [baseFolder, filename].join('/');
400
448
  return { dir: baseFolder, file, relativePath: file };
401
449
  }
402
- // async createUpload(filename: string, size: number, chunks: number, mimetype: string, options: UserAppOptionsRequired) {
403
- // const useFilename = this.sanitizeName(filename);
404
- // if (await this.exists(options.company, options.project, useFilename)) throw new Error('FILE_EXISTS');
405
- // if (await UploadService.uploadExists(options.company, options.project, useFilename)) throw new Error('UPLOAD_EXISTS');
406
- // return UploadService.createUpload(useFilename, size, chunks, mimetype, options);
407
- // }
408
450
  static sanitizeName(name) {
409
451
  return name.replace(/[^a-z0-9-_.]/gi, '').toLowerCase();
410
452
  }
@@ -426,10 +468,17 @@ export class StorageService extends Immutable {
426
468
  }
427
469
  return useMetadata;
428
470
  }
429
- get(company, project, app, query, limit) {
471
+ async get(select, limit) {
472
+ const { company, project, app, list, customer, assetId, query } = select;
430
473
  const realQuery = { company, project, app, parent: { $exists: false } };
474
+ if (list)
475
+ realQuery.list = list;
476
+ if (customer)
477
+ realQuery.customer = customer;
478
+ if (assetId?.length)
479
+ realQuery.assetId = { $in: assetId };
431
480
  let tags;
432
- if (query.tags) {
481
+ if (query?.tags) {
433
482
  if (!Array.isArray(query.tags)) {
434
483
  tags = [query.tags];
435
484
  }
@@ -438,15 +487,34 @@ export class StorageService extends Immutable {
438
487
  }
439
488
  realQuery['metadata.tags'] = { $in: tags };
440
489
  }
441
- if (query.search) {
490
+ if (query?.search) {
442
491
  realQuery.filename = { $regex: query.search, $options: 'i' };
443
492
  }
493
+ const structureLookupStages = (assetId?.length || query?.populateAssetId)
494
+ ? [
495
+ { $lookup: { from: 'structures', localField: 'assetId', foreignField: 'assetId', as: '_structure' } },
496
+ { $unwind: { path: '$_structure', preserveNullAndEmptyArrays: true } },
497
+ { $addFields: { assetId: { id: '$assetId', title: { $ifNull: ['$_structure.title', '$_structure.question', ''] } } } },
498
+ { $project: { _structure: 0 } },
499
+ ]
500
+ : [];
501
+ let result;
444
502
  // If ignorePrefix is given, return all matching files without prefix filtering
445
- if (query.ignorePrefix) {
446
- return StorageModel.db.aggregate([
503
+ if (query?.ignorePrefix) {
504
+ // If prefix is provided with ignorePrefix, match all files where prefix starts with the given value
505
+ if (query?.prefix) {
506
+ const prefixWithSlash = query.prefix.endsWith('/') ? query.prefix : `${query.prefix}/`;
507
+ const escapedPrefix = prefixWithSlash.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
508
+ realQuery.$or = [
509
+ { prefix: query.prefix }, // Exact match
510
+ { prefix: { $regex: `^${escapedPrefix}` } }, // Starts with prefix/
511
+ ];
512
+ }
513
+ result = await StorageModel.db.aggregate([
447
514
  {
448
515
  $match: realQuery,
449
516
  },
517
+ ...structureLookupStages,
450
518
  {
451
519
  $facet: {
452
520
  metadata: [{ $count: 'all' }],
@@ -454,7 +522,7 @@ export class StorageService extends Immutable {
454
522
  { $sort: { _id: -1 } },
455
523
  { $skip: limit?.skip || 0 },
456
524
  { $limit: limit?.limit || 50 },
457
- { $project: { path: 0, bucket: 0 } },
525
+ { $project: { path: 0 } },
458
526
  { $addFields: { type: 'file' } },
459
527
  ],
460
528
  },
@@ -468,11 +536,11 @@ export class StorageService extends Immutable {
468
536
  },
469
537
  ])
470
538
  .toArray()
471
- .then((result) => result[0] || { all: 0, data: [], count: 0 });
539
+ .then((r) => r[0] || { all: 0, data: [], count: 0 });
472
540
  }
473
541
  // If search or tags are given, show only files at the current level (no folders)
474
- if (query.search || query.tags) {
475
- const isRootLevel = query.prefix === undefined || query.prefix === '';
542
+ if (!result && (query?.search || query?.tags)) {
543
+ const isRootLevel = query?.prefix === undefined || query?.prefix === '';
476
544
  if (isRootLevel) {
477
545
  // Root level: only files without prefix
478
546
  realQuery.$or = [
@@ -485,10 +553,11 @@ export class StorageService extends Immutable {
485
553
  // Non-root: only files with exact prefix match
486
554
  realQuery.prefix = query.prefix;
487
555
  }
488
- return StorageModel.db.aggregate([
556
+ result = await StorageModel.db.aggregate([
489
557
  {
490
558
  $match: realQuery,
491
559
  },
560
+ ...structureLookupStages,
492
561
  {
493
562
  $facet: {
494
563
  metadata: [{ $count: 'all' }],
@@ -496,7 +565,7 @@ export class StorageService extends Immutable {
496
565
  { $sort: { _id: -1 } },
497
566
  { $skip: limit?.skip || 0 },
498
567
  { $limit: limit?.limit || 50 },
499
- { $project: { path: 0, bucket: 0 } },
568
+ { $project: { path: 0 } },
500
569
  { $addFields: { type: 'file' } },
501
570
  ],
502
571
  },
@@ -510,151 +579,213 @@ export class StorageService extends Immutable {
510
579
  },
511
580
  ])
512
581
  .toArray()
513
- .then((result) => result[0] || { all: 0, data: [], count: 0 });
582
+ .then((r) => r[0] || { all: 0, data: [], count: 0 });
514
583
  }
515
584
  // Treat undefined prefix same as empty string (root level)
516
- const isRootLevel = query.prefix === undefined || query.prefix === '';
517
- if (isRootLevel) {
518
- // Root level: show files without prefix and extract virtual folders
519
- return StorageModel.db.aggregate([
520
- {
521
- $match: realQuery,
522
- },
523
- {
524
- $addFields: {
525
- // Extract first segment of prefix for root-level folders
526
- immediateSubfolder: {
527
- $cond: {
528
- if: {
529
- $or: [
530
- { $eq: ['$prefix', null] },
531
- { $eq: ['$prefix', ''] },
532
- { $eq: [{ $type: '$prefix' }, 'missing'] },
533
- ],
534
- },
535
- then: null, // File at root level (no prefix)
536
- else: {
537
- // Extract first segment of prefix
538
- $arrayElemAt: [{ $split: ['$prefix', '/'] }, 0],
585
+ if (!result) {
586
+ const isRootLevel = query?.prefix === undefined || query?.prefix === '';
587
+ if (isRootLevel) {
588
+ // Root level: show files without prefix and extract virtual folders
589
+ result = await StorageModel.db.aggregate([
590
+ {
591
+ $match: realQuery,
592
+ },
593
+ ...structureLookupStages,
594
+ {
595
+ $addFields: {
596
+ // Extract first segment of prefix for root-level folders
597
+ immediateSubfolder: {
598
+ $cond: {
599
+ if: {
600
+ $or: [
601
+ { $eq: ['$prefix', null] },
602
+ { $eq: ['$prefix', ''] },
603
+ { $eq: [{ $type: '$prefix' }, 'missing'] },
604
+ ],
605
+ },
606
+ then: null, // File at root level (no prefix)
607
+ else: {
608
+ // Extract first segment of prefix
609
+ $arrayElemAt: [{ $split: ['$prefix', '/'] }, 0],
610
+ },
539
611
  },
540
612
  },
541
613
  },
542
614
  },
543
- },
544
- {
545
- $facet: {
546
- files: [
547
- { $match: { immediateSubfolder: null } }, // Files at root level
548
- { $sort: { _id: -1 } },
549
- { $skip: limit?.skip || 0 },
550
- { $limit: limit?.limit || 50 },
551
- { $project: { path: 0, bucket: 0, immediateSubfolder: 0 } },
552
- { $addFields: { type: 'file' } },
553
- ],
554
- folders: [
555
- { $match: { immediateSubfolder: { $ne: null } } },
556
- { $group: { _id: '$immediateSubfolder' } }, // Unique folder names
557
- { $sort: { _id: 1 } },
558
- {
559
- $project: {
560
- _id: 0,
561
- prefix: '$_id',
562
- name: '$_id',
563
- type: { $literal: 'folder' },
615
+ {
616
+ $facet: {
617
+ files: [
618
+ { $match: { immediateSubfolder: null } }, // Files at root level
619
+ { $sort: { _id: -1 } },
620
+ { $skip: limit?.skip || 0 },
621
+ { $limit: limit?.limit || 50 },
622
+ { $project: { path: 0, immediateSubfolder: 0 } },
623
+ { $addFields: { type: 'file' } },
624
+ ],
625
+ folders: [
626
+ { $match: { immediateSubfolder: { $ne: null } } },
627
+ { $group: { _id: '$immediateSubfolder' } }, // Unique folder names
628
+ { $sort: { _id: 1 } },
629
+ {
630
+ $project: {
631
+ _id: 0,
632
+ prefix: '$_id',
633
+ name: '$_id',
634
+ type: { $literal: 'folder' },
635
+ },
636
+ },
637
+ ],
638
+ metadata: [
639
+ { $match: { immediateSubfolder: null } },
640
+ { $count: 'all' },
641
+ ],
642
+ },
643
+ },
644
+ {
645
+ $project: {
646
+ all: { $arrayElemAt: ['$metadata.all', 0] },
647
+ data: { $concatArrays: ['$folders', '$files'] },
648
+ count: { $add: [{ $size: '$folders' }, { $size: '$files' }] },
649
+ },
650
+ },
651
+ ])
652
+ .toArray()
653
+ .then((r) => r[0] || { all: 0, data: [], count: 0 });
654
+ }
655
+ else {
656
+ // Non-root: show exact prefix match and extract subfolders
657
+ const prefix = query.prefix;
658
+ const prefixWithSlash = prefix.endsWith('/') ? prefix : `${prefix}/`;
659
+ const escapedPrefix = prefixWithSlash.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
660
+ // Build non-root query by adding prefix conditions to realQuery
661
+ const nonRootQuery = {
662
+ ...realQuery,
663
+ $or: [
664
+ { prefix }, // Exact match files
665
+ { prefix: { $regex: `^${escapedPrefix}` } }, // Deeper files for folder extraction
666
+ ],
667
+ };
668
+ result = await StorageModel.db.aggregate([
669
+ {
670
+ $match: nonRootQuery,
671
+ },
672
+ ...structureLookupStages,
673
+ {
674
+ $addFields: {
675
+ // Extract immediate subfolder name after current prefix
676
+ immediateSubfolder: {
677
+ $cond: {
678
+ if: { $eq: ['$prefix', prefix] },
679
+ then: null, // File at exact level
680
+ else: {
681
+ // Extract first segment after prefix
682
+ $arrayElemAt: [
683
+ { $split: [{ $substr: ['$prefix', prefixWithSlash.length, -1] }, '/'] },
684
+ 0,
685
+ ],
686
+ },
564
687
  },
565
688
  },
566
- ],
567
- metadata: [
568
- { $match: { immediateSubfolder: null } },
569
- { $count: 'all' },
570
- ],
689
+ },
571
690
  },
572
- },
573
- {
574
- $project: {
575
- all: { $arrayElemAt: ['$metadata.all', 0] },
576
- data: { $concatArrays: ['$folders', '$files'] },
577
- count: { $add: [{ $size: '$folders' }, { $size: '$files' }] },
691
+ {
692
+ $facet: {
693
+ files: [
694
+ { $match: { immediateSubfolder: null } }, // Files at exact level
695
+ { $sort: { _id: -1 } },
696
+ { $skip: limit?.skip || 0 },
697
+ { $limit: limit?.limit || 50 },
698
+ { $project: { path: 0, immediateSubfolder: 0 } },
699
+ { $addFields: { type: 'file' } },
700
+ ],
701
+ folders: [
702
+ { $match: { immediateSubfolder: { $ne: null } } },
703
+ { $group: { _id: '$immediateSubfolder' } }, // Unique folder names
704
+ { $sort: { _id: 1 } },
705
+ {
706
+ $project: {
707
+ _id: 0,
708
+ prefix: { $concat: [prefixWithSlash, '$_id'] },
709
+ name: '$_id',
710
+ type: { $literal: 'folder' },
711
+ },
712
+ },
713
+ ],
714
+ metadata: [
715
+ { $match: { immediateSubfolder: null } },
716
+ { $count: 'all' },
717
+ ],
718
+ },
578
719
  },
579
- },
580
- ])
581
- .toArray()
582
- .then((result) => result[0] || { all: 0, data: [], count: 0 });
720
+ {
721
+ $project: {
722
+ all: { $arrayElemAt: ['$metadata.all', 0] },
723
+ data: { $concatArrays: ['$folders', '$files'] },
724
+ count: { $add: [{ $size: '$folders' }, { $size: '$files' }] },
725
+ },
726
+ },
727
+ ])
728
+ .toArray()
729
+ .then((r) => r[0] || { all: 0, data: [], count: 0 });
730
+ }
583
731
  }
584
- // Non-root: show exact prefix match and extract subfolders
585
- const prefixWithSlash = query.prefix.endsWith('/') ? query.prefix : `${query.prefix}/`;
586
- const escapedPrefix = prefixWithSlash.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
587
- // Build non-root query by adding prefix conditions to realQuery
588
- const nonRootQuery = {
589
- ...realQuery,
590
- $or: [
591
- { prefix: query.prefix }, // Exact match files
592
- { prefix: { $regex: `^${escapedPrefix}` } }, // Deeper files for folder extraction
593
- ],
594
- };
732
+ return result || { all: 0, data: [], count: 0 };
733
+ }
734
+ /**
735
+ * Get all files for a company/project with optional prefix filtering.
736
+ * Returns a flat list of all matching files without creating virtual folders.
737
+ *
738
+ * @param company - Company identifier
739
+ * @param project - Optional project identifier
740
+ * @param app - Optional app identifier
741
+ * @param prefix - Optional prefix to filter files (e.g., "folder/subfolder")
742
+ * @param limit - Optional pagination with skip and limit
743
+ * @returns Promise with all matching files and count
744
+ */
745
+ getAllFiles(company, project, app, prefix, limit) {
746
+ const query = { company, project, app, parent: { $exists: false } };
747
+ // If prefix is provided, filter files with that prefix
748
+ if (prefix) {
749
+ const prefixWithSlash = prefix.endsWith('/') ? prefix : `${prefix}/`;
750
+ const escapedPrefix = prefixWithSlash.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
751
+ // Match files that start with the prefix (exact match or deeper)
752
+ query.$or = [
753
+ { prefix }, // Exact match
754
+ { prefix: { $regex: `^${escapedPrefix}` } }, // Starts with prefix/
755
+ ];
756
+ }
757
+ // Build data pipeline stages
758
+ const dataPipeline = [{ $sort: { _id: -1 } }];
759
+ // Only add pagination if limit is provided
760
+ if (limit) {
761
+ if (limit.skip)
762
+ dataPipeline.push({ $skip: limit.skip });
763
+ if (limit.limit)
764
+ dataPipeline.push({ $limit: limit.limit });
765
+ }
766
+ dataPipeline.push({ $project: { history: 0 } });
595
767
  return StorageModel.db.aggregate([
596
768
  {
597
- $match: nonRootQuery,
598
- },
599
- {
600
- $addFields: {
601
- // Extract immediate subfolder name after current prefix
602
- immediateSubfolder: {
603
- $cond: {
604
- if: { $eq: ['$prefix', query.prefix] },
605
- then: null, // File at exact level
606
- else: {
607
- // Extract first segment after prefix
608
- $arrayElemAt: [
609
- { $split: [{ $substr: ['$prefix', prefixWithSlash.length, -1] }, '/'] },
610
- 0,
611
- ],
612
- },
613
- },
614
- },
615
- },
769
+ $match: query,
616
770
  },
617
771
  {
618
772
  $facet: {
619
- files: [
620
- { $match: { immediateSubfolder: null } }, // Files at exact level
621
- { $sort: { _id: -1 } },
622
- { $skip: limit?.skip || 0 },
623
- { $limit: limit?.limit || 50 },
624
- { $project: { path: 0, bucket: 0, immediateSubfolder: 0 } },
625
- { $addFields: { type: 'file' } },
626
- ],
627
- folders: [
628
- { $match: { immediateSubfolder: { $ne: null } } },
629
- { $group: { _id: '$immediateSubfolder' } }, // Unique folder names
630
- { $sort: { _id: 1 } },
631
- {
632
- $project: {
633
- _id: 0,
634
- prefix: { $concat: [prefixWithSlash, '$_id'] },
635
- name: '$_id',
636
- type: { $literal: 'folder' },
637
- },
638
- },
639
- ],
640
- metadata: [
641
- { $match: { immediateSubfolder: null } },
642
- { $count: 'all' },
643
- ],
773
+ metadata: [{ $count: 'all' }],
774
+ data: dataPipeline,
644
775
  },
645
776
  },
646
777
  {
647
778
  $project: {
648
779
  all: { $arrayElemAt: ['$metadata.all', 0] },
649
- data: { $concatArrays: ['$folders', '$files'] },
650
- count: { $add: [{ $size: '$folders' }, { $size: '$files' }] },
780
+ data: 1,
781
+ count: { $size: '$data' },
651
782
  },
652
783
  },
653
784
  ])
654
785
  .toArray()
655
786
  .then((result) => result[0] || { all: 0, data: [], count: 0 });
656
787
  }
657
- getTags(company, project, search) {
788
+ async getTags(company, project, search) {
658
789
  let cleanSearch = search.toLowerCase().trim();
659
790
  let sortString = cleanSearch;
660
791
  let splitSearch = [];
@@ -666,9 +797,9 @@ export class StorageService extends Immutable {
666
797
  else {
667
798
  cleanSearch = new RegExp(`^(?!([a-z]+):).*(${cleanSearch}).*`);
668
799
  }
669
- return StorageModel.db.find({ company, project, tags: { $regex: cleanSearch, $options: 'i' } }, { projection: { tags: 1 } })
670
- .toArray()
671
- .then((matchedDocuments) => this.matchSortTags(matchedDocuments, cleanSearch, sortString));
800
+ const matchedDocuments = await StorageModel.db.find({ company, project, 'metadata.tags': { $regex: cleanSearch, $options: 'i' } }, { projection: { 'metadata.tags': 1 } })
801
+ .toArray();
802
+ return this.matchSortTags(matchedDocuments, cleanSearch, sortString);
672
803
  }
673
804
  single(company, project, filename) {
674
805
  return StorageModel.db.aggregate([
@@ -770,21 +901,39 @@ export class StorageService extends Immutable {
770
901
  /**
771
902
  * checks if the file was already successfully uploaded
772
903
  */
773
- exists(filename, prefix, app, options) {
774
- return StorageModel.db.countDocuments({
904
+ exists(filename, prefix, app, customer, list, options) {
905
+ const query = {
775
906
  company: options.company,
776
907
  project: options.project,
777
908
  filename,
778
909
  prefix,
779
910
  app,
780
- })
911
+ };
912
+ if (customer)
913
+ query.customer = customer;
914
+ if (list)
915
+ query.list = list;
916
+ return StorageModel.db.countDocuments(query)
781
917
  .then((exists) => exists >= 1);
782
918
  }
919
+ /**
920
+ * counts non-versioned storage documents bound to a specific assetId
921
+ */
922
+ async countByAssetId(assetId, app, customer, list, options) {
923
+ return StorageModel.db.countDocuments({
924
+ company: options.company,
925
+ project: options.project,
926
+ app,
927
+ assetId,
928
+ customer,
929
+ list,
930
+ });
931
+ }
783
932
  matchSortTags(documents, search, sort) {
784
933
  const mergedArray = [];
785
934
  const returnArray = [];
786
935
  documents.forEach((document) => {
787
- document.tags.forEach((tag) => {
936
+ document.metadata?.tags?.forEach((tag) => {
788
937
  if (mergedArray.find((tagArray) => tagArray.tag === tag))
789
938
  return;
790
939
  if (!tag.match(search))
@@ -801,14 +950,17 @@ export class StorageService extends Immutable {
801
950
  checkAllowedSize(filesize, allowedSize) {
802
951
  return filesize < allowedSize;
803
952
  }
804
- stats(company, project) {
953
+ stats(company, project, customer) {
954
+ const match = {
955
+ company,
956
+ project,
957
+ parent: { $exists: false },
958
+ };
959
+ if (customer)
960
+ match.customer = customer;
805
961
  const aggregation = [
806
962
  {
807
- $match: {
808
- company,
809
- project,
810
- parent: { $exists: false },
811
- },
963
+ $match: match,
812
964
  },
813
965
  {
814
966
  $group: {
@@ -833,28 +985,47 @@ export class StorageService extends Immutable {
833
985
  .then((value) => value[0] || { files: 0, size: 0 });
834
986
  }
835
987
  getByInternalId(internalId, options) {
836
- return StorageModel.db.findOne({ _id: internalId, company: options.company, project: options.project });
988
+ return StorageModel.db.findOne({ _id: internalId, company: options.company, project: options.project }, { projection: { path: 0 } });
837
989
  }
838
- getPathByFilename(filename, app, options) {
990
+ getPathByFilename(filename, app, customer, list, options) {
839
991
  const filenameObject = filenameHelper(filename);
840
- return StorageModel.db.findOne({
992
+ const query = {
841
993
  filename: filenameObject.filename,
842
994
  prefix: filenameObject.directory,
843
995
  company: options.company,
844
996
  project: options.project,
845
997
  app,
846
- }, {
998
+ };
999
+ if (customer)
1000
+ query.customer = customer;
1001
+ if (list)
1002
+ query.list = list;
1003
+ return StorageModel.db.findOne(query, {
847
1004
  projection: { path: 1, metadata: 1 },
848
1005
  });
849
1006
  }
850
- getByFilename(filename, prefix, bucket, options) {
851
- return StorageModel.db.findOne({
1007
+ getByFilename(filename, prefix, app, customer, list, options) {
1008
+ const query = {
852
1009
  filename,
853
1010
  prefix,
854
1011
  company: options.company,
855
1012
  project: options.project,
856
- bucket,
857
- });
1013
+ app,
1014
+ };
1015
+ if (customer)
1016
+ query.customer = customer;
1017
+ if (list)
1018
+ query.list = list;
1019
+ return StorageModel.db.findOne(query);
1020
+ }
1021
+ async removeBlobMembers(internalId, customer, options) {
1022
+ const storage = await this.model.db.findOne({ _id: internalId, company: options.company, project: options.project, customer });
1023
+ if (!storage)
1024
+ throw new Error('BLOB_NOT_FOUND');
1025
+ await fs.promises.unlink(`${this.options.dataFolder}/${storage.path}`);
1026
+ await this.removeBlobVersion(internalId, options);
1027
+ await this.delete(internalId, options.user, options.app);
1028
+ await jetstreamService.publish(`edge.${storage.app}.removed`, storage);
858
1029
  }
859
1030
  async removeBlob(internalId, options) {
860
1031
  const storage = await this.model.db.findOne({ _id: internalId, company: options.company, project: options.project });
@@ -862,14 +1033,16 @@ export class StorageService extends Immutable {
862
1033
  throw new Error('BLOB_NOT_FOUND');
863
1034
  await fs.promises.unlink(`${this.options.dataFolder}/${storage.path}`);
864
1035
  await this.removeBlobVersion(internalId, options);
865
- return this.delete(internalId, options.user, options.app);
1036
+ await this.delete(internalId, options.user, options.app);
1037
+ await jetstreamService.publish(`edge.${storage.app}.removed`, storage);
866
1038
  }
867
1039
  async removeBlobVersion(parentId, options) {
868
1040
  const versions = await this.model.db.find({ parent: parentId, company: options.company, project: options.project }).toArray();
869
1041
  if (versions.length) {
870
1042
  await Promise.allSettled(versions.map(async (single) => {
871
1043
  await fs.promises.unlink(`${this.options.dataFolder}/${single.path}`);
872
- return this.delete(single._id, options.user, options.app);
1044
+ await this.delete(single._id, options.user, options.app);
1045
+ await jetstreamService.publish(`edge.${single.app}.removed`, single);
873
1046
  }));
874
1047
  }
875
1048
  }