@lilaquadrat/studio 10.0.0-beta.9 → 10.0.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 +378 -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,17 @@ export class StorageService extends Immutable {
246
278
  await fs.promises.unlink(path.file);
247
279
  throw new Error('FILE_SIZE');
248
280
  }
281
+ if (uploadFile.size) {
282
+ const chunkStat = await fs.promises.stat(path.file);
283
+ const existingSize = uploadFile.paths?.length
284
+ ? (await Promise.all(uploadFile.paths.map((p) => fs.promises.stat(p.path).then((s) => s.size).catch(() => 0))))
285
+ .reduce((sum, s) => sum + s, 0)
286
+ : 0;
287
+ if (existingSize + chunkStat.size > uploadFile.size) {
288
+ await fs.promises.unlink(path.file);
289
+ throw new Error('FILE_SIZE');
290
+ }
291
+ }
249
292
  const updloadState = await UploadService.addPathToUpload(uploadInternalId, index, path.file);
250
293
  /**
251
294
  * if all chunks are there, complete the file and the upload
@@ -272,10 +315,13 @@ export class StorageService extends Immutable {
272
315
  await this.concatenateFilesWithCat(data, pathAndFile.file);
273
316
  }
274
317
  }
275
- else {
318
+ else if (type !== 'copy') {
276
319
  const writeStream = fs.createWriteStream(pathAndFile.file);
277
320
  await pipeline(data, writeStream);
278
321
  }
322
+ else {
323
+ await fs.promises.copyFile(data, pathAndFile.file);
324
+ }
279
325
  return pathAndFile;
280
326
  }
281
327
  async writeFileVersion(data, internalId, version) {
@@ -369,9 +415,7 @@ export class StorageService extends Immutable {
369
415
  catch (error) {
370
416
  if (error.code === 'ENOENT') {
371
417
  console.warn(`File not found: ${fullPath}`);
372
- return;
373
418
  }
374
- throw error;
375
419
  }
376
420
  }
377
421
  /**
@@ -399,12 +443,6 @@ export class StorageService extends Immutable {
399
443
  const file = [baseFolder, filename].join('/');
400
444
  return { dir: baseFolder, file, relativePath: file };
401
445
  }
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
446
  static sanitizeName(name) {
409
447
  return name.replace(/[^a-z0-9-_.]/gi, '').toLowerCase();
410
448
  }
@@ -426,10 +464,17 @@ export class StorageService extends Immutable {
426
464
  }
427
465
  return useMetadata;
428
466
  }
429
- get(company, project, app, query, limit) {
467
+ async get(select, limit) {
468
+ const { company, project, app, list, customer, assetId, query } = select;
430
469
  const realQuery = { company, project, app, parent: { $exists: false } };
470
+ if (list)
471
+ realQuery.list = list;
472
+ if (customer)
473
+ realQuery.customer = customer;
474
+ if (assetId?.length)
475
+ realQuery.assetId = { $in: assetId };
431
476
  let tags;
432
- if (query.tags) {
477
+ if (query?.tags) {
433
478
  if (!Array.isArray(query.tags)) {
434
479
  tags = [query.tags];
435
480
  }
@@ -438,15 +483,34 @@ export class StorageService extends Immutable {
438
483
  }
439
484
  realQuery['metadata.tags'] = { $in: tags };
440
485
  }
441
- if (query.search) {
486
+ if (query?.search) {
442
487
  realQuery.filename = { $regex: query.search, $options: 'i' };
443
488
  }
489
+ const structureLookupStages = (assetId?.length || query?.populateAssetId)
490
+ ? [
491
+ { $lookup: { from: 'structures', localField: 'assetId', foreignField: 'assetId', as: '_structure' } },
492
+ { $unwind: { path: '$_structure', preserveNullAndEmptyArrays: true } },
493
+ { $addFields: { assetId: { id: '$assetId', title: { $ifNull: ['$_structure.title', '$_structure.question', ''] } } } },
494
+ { $project: { _structure: 0 } },
495
+ ]
496
+ : [];
497
+ let result;
444
498
  // If ignorePrefix is given, return all matching files without prefix filtering
445
- if (query.ignorePrefix) {
446
- return StorageModel.db.aggregate([
499
+ if (query?.ignorePrefix) {
500
+ // If prefix is provided with ignorePrefix, match all files where prefix starts with the given value
501
+ if (query?.prefix) {
502
+ const prefixWithSlash = query.prefix.endsWith('/') ? query.prefix : `${query.prefix}/`;
503
+ const escapedPrefix = prefixWithSlash.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
504
+ realQuery.$or = [
505
+ { prefix: query.prefix }, // Exact match
506
+ { prefix: { $regex: `^${escapedPrefix}` } }, // Starts with prefix/
507
+ ];
508
+ }
509
+ result = await StorageModel.db.aggregate([
447
510
  {
448
511
  $match: realQuery,
449
512
  },
513
+ ...structureLookupStages,
450
514
  {
451
515
  $facet: {
452
516
  metadata: [{ $count: 'all' }],
@@ -454,7 +518,7 @@ export class StorageService extends Immutable {
454
518
  { $sort: { _id: -1 } },
455
519
  { $skip: limit?.skip || 0 },
456
520
  { $limit: limit?.limit || 50 },
457
- { $project: { path: 0, bucket: 0 } },
521
+ { $project: { path: 0 } },
458
522
  { $addFields: { type: 'file' } },
459
523
  ],
460
524
  },
@@ -468,11 +532,11 @@ export class StorageService extends Immutable {
468
532
  },
469
533
  ])
470
534
  .toArray()
471
- .then((result) => result[0] || { all: 0, data: [], count: 0 });
535
+ .then((r) => r[0] || { all: 0, data: [], count: 0 });
472
536
  }
473
537
  // 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 === '';
538
+ if (!result && (query?.search || query?.tags)) {
539
+ const isRootLevel = query?.prefix === undefined || query?.prefix === '';
476
540
  if (isRootLevel) {
477
541
  // Root level: only files without prefix
478
542
  realQuery.$or = [
@@ -485,10 +549,11 @@ export class StorageService extends Immutable {
485
549
  // Non-root: only files with exact prefix match
486
550
  realQuery.prefix = query.prefix;
487
551
  }
488
- return StorageModel.db.aggregate([
552
+ result = await StorageModel.db.aggregate([
489
553
  {
490
554
  $match: realQuery,
491
555
  },
556
+ ...structureLookupStages,
492
557
  {
493
558
  $facet: {
494
559
  metadata: [{ $count: 'all' }],
@@ -496,7 +561,7 @@ export class StorageService extends Immutable {
496
561
  { $sort: { _id: -1 } },
497
562
  { $skip: limit?.skip || 0 },
498
563
  { $limit: limit?.limit || 50 },
499
- { $project: { path: 0, bucket: 0 } },
564
+ { $project: { path: 0 } },
500
565
  { $addFields: { type: 'file' } },
501
566
  ],
502
567
  },
@@ -510,151 +575,213 @@ export class StorageService extends Immutable {
510
575
  },
511
576
  ])
512
577
  .toArray()
513
- .then((result) => result[0] || { all: 0, data: [], count: 0 });
578
+ .then((r) => r[0] || { all: 0, data: [], count: 0 });
514
579
  }
515
580
  // 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],
581
+ if (!result) {
582
+ const isRootLevel = query?.prefix === undefined || query?.prefix === '';
583
+ if (isRootLevel) {
584
+ // Root level: show files without prefix and extract virtual folders
585
+ result = await StorageModel.db.aggregate([
586
+ {
587
+ $match: realQuery,
588
+ },
589
+ ...structureLookupStages,
590
+ {
591
+ $addFields: {
592
+ // Extract first segment of prefix for root-level folders
593
+ immediateSubfolder: {
594
+ $cond: {
595
+ if: {
596
+ $or: [
597
+ { $eq: ['$prefix', null] },
598
+ { $eq: ['$prefix', ''] },
599
+ { $eq: [{ $type: '$prefix' }, 'missing'] },
600
+ ],
601
+ },
602
+ then: null, // File at root level (no prefix)
603
+ else: {
604
+ // Extract first segment of prefix
605
+ $arrayElemAt: [{ $split: ['$prefix', '/'] }, 0],
606
+ },
539
607
  },
540
608
  },
541
609
  },
542
610
  },
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' },
611
+ {
612
+ $facet: {
613
+ files: [
614
+ { $match: { immediateSubfolder: null } }, // Files at root level
615
+ { $sort: { _id: -1 } },
616
+ { $skip: limit?.skip || 0 },
617
+ { $limit: limit?.limit || 50 },
618
+ { $project: { path: 0, immediateSubfolder: 0 } },
619
+ { $addFields: { type: 'file' } },
620
+ ],
621
+ folders: [
622
+ { $match: { immediateSubfolder: { $ne: null } } },
623
+ { $group: { _id: '$immediateSubfolder' } }, // Unique folder names
624
+ { $sort: { _id: 1 } },
625
+ {
626
+ $project: {
627
+ _id: 0,
628
+ prefix: '$_id',
629
+ name: '$_id',
630
+ type: { $literal: 'folder' },
631
+ },
632
+ },
633
+ ],
634
+ metadata: [
635
+ { $match: { immediateSubfolder: null } },
636
+ { $count: 'all' },
637
+ ],
638
+ },
639
+ },
640
+ {
641
+ $project: {
642
+ all: { $arrayElemAt: ['$metadata.all', 0] },
643
+ data: { $concatArrays: ['$folders', '$files'] },
644
+ count: { $add: [{ $size: '$folders' }, { $size: '$files' }] },
645
+ },
646
+ },
647
+ ])
648
+ .toArray()
649
+ .then((r) => r[0] || { all: 0, data: [], count: 0 });
650
+ }
651
+ else {
652
+ // Non-root: show exact prefix match and extract subfolders
653
+ const prefix = query.prefix;
654
+ const prefixWithSlash = prefix.endsWith('/') ? prefix : `${prefix}/`;
655
+ const escapedPrefix = prefixWithSlash.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
656
+ // Build non-root query by adding prefix conditions to realQuery
657
+ const nonRootQuery = {
658
+ ...realQuery,
659
+ $or: [
660
+ { prefix }, // Exact match files
661
+ { prefix: { $regex: `^${escapedPrefix}` } }, // Deeper files for folder extraction
662
+ ],
663
+ };
664
+ result = await StorageModel.db.aggregate([
665
+ {
666
+ $match: nonRootQuery,
667
+ },
668
+ ...structureLookupStages,
669
+ {
670
+ $addFields: {
671
+ // Extract immediate subfolder name after current prefix
672
+ immediateSubfolder: {
673
+ $cond: {
674
+ if: { $eq: ['$prefix', prefix] },
675
+ then: null, // File at exact level
676
+ else: {
677
+ // Extract first segment after prefix
678
+ $arrayElemAt: [
679
+ { $split: [{ $substr: ['$prefix', prefixWithSlash.length, -1] }, '/'] },
680
+ 0,
681
+ ],
682
+ },
564
683
  },
565
684
  },
566
- ],
567
- metadata: [
568
- { $match: { immediateSubfolder: null } },
569
- { $count: 'all' },
570
- ],
685
+ },
571
686
  },
572
- },
573
- {
574
- $project: {
575
- all: { $arrayElemAt: ['$metadata.all', 0] },
576
- data: { $concatArrays: ['$folders', '$files'] },
577
- count: { $add: [{ $size: '$folders' }, { $size: '$files' }] },
687
+ {
688
+ $facet: {
689
+ files: [
690
+ { $match: { immediateSubfolder: null } }, // Files at exact level
691
+ { $sort: { _id: -1 } },
692
+ { $skip: limit?.skip || 0 },
693
+ { $limit: limit?.limit || 50 },
694
+ { $project: { path: 0, immediateSubfolder: 0 } },
695
+ { $addFields: { type: 'file' } },
696
+ ],
697
+ folders: [
698
+ { $match: { immediateSubfolder: { $ne: null } } },
699
+ { $group: { _id: '$immediateSubfolder' } }, // Unique folder names
700
+ { $sort: { _id: 1 } },
701
+ {
702
+ $project: {
703
+ _id: 0,
704
+ prefix: { $concat: [prefixWithSlash, '$_id'] },
705
+ name: '$_id',
706
+ type: { $literal: 'folder' },
707
+ },
708
+ },
709
+ ],
710
+ metadata: [
711
+ { $match: { immediateSubfolder: null } },
712
+ { $count: 'all' },
713
+ ],
714
+ },
578
715
  },
579
- },
580
- ])
581
- .toArray()
582
- .then((result) => result[0] || { all: 0, data: [], count: 0 });
716
+ {
717
+ $project: {
718
+ all: { $arrayElemAt: ['$metadata.all', 0] },
719
+ data: { $concatArrays: ['$folders', '$files'] },
720
+ count: { $add: [{ $size: '$folders' }, { $size: '$files' }] },
721
+ },
722
+ },
723
+ ])
724
+ .toArray()
725
+ .then((r) => r[0] || { all: 0, data: [], count: 0 });
726
+ }
583
727
  }
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
- };
728
+ return result || { all: 0, data: [], count: 0 };
729
+ }
730
+ /**
731
+ * Get all files for a company/project with optional prefix filtering.
732
+ * Returns a flat list of all matching files without creating virtual folders.
733
+ *
734
+ * @param company - Company identifier
735
+ * @param project - Optional project identifier
736
+ * @param app - Optional app identifier
737
+ * @param prefix - Optional prefix to filter files (e.g., "folder/subfolder")
738
+ * @param limit - Optional pagination with skip and limit
739
+ * @returns Promise with all matching files and count
740
+ */
741
+ getAllFiles(company, project, app, prefix, limit) {
742
+ const query = { company, project, app, parent: { $exists: false } };
743
+ // If prefix is provided, filter files with that prefix
744
+ if (prefix) {
745
+ const prefixWithSlash = prefix.endsWith('/') ? prefix : `${prefix}/`;
746
+ const escapedPrefix = prefixWithSlash.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
747
+ // Match files that start with the prefix (exact match or deeper)
748
+ query.$or = [
749
+ { prefix }, // Exact match
750
+ { prefix: { $regex: `^${escapedPrefix}` } }, // Starts with prefix/
751
+ ];
752
+ }
753
+ // Build data pipeline stages
754
+ const dataPipeline = [{ $sort: { _id: -1 } }];
755
+ // Only add pagination if limit is provided
756
+ if (limit) {
757
+ if (limit.skip)
758
+ dataPipeline.push({ $skip: limit.skip });
759
+ if (limit.limit)
760
+ dataPipeline.push({ $limit: limit.limit });
761
+ }
762
+ dataPipeline.push({ $project: { history: 0 } });
595
763
  return StorageModel.db.aggregate([
596
764
  {
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
- },
765
+ $match: query,
616
766
  },
617
767
  {
618
768
  $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
- ],
769
+ metadata: [{ $count: 'all' }],
770
+ data: dataPipeline,
644
771
  },
645
772
  },
646
773
  {
647
774
  $project: {
648
775
  all: { $arrayElemAt: ['$metadata.all', 0] },
649
- data: { $concatArrays: ['$folders', '$files'] },
650
- count: { $add: [{ $size: '$folders' }, { $size: '$files' }] },
776
+ data: 1,
777
+ count: { $size: '$data' },
651
778
  },
652
779
  },
653
780
  ])
654
781
  .toArray()
655
782
  .then((result) => result[0] || { all: 0, data: [], count: 0 });
656
783
  }
657
- getTags(company, project, search) {
784
+ async getTags(company, project, search) {
658
785
  let cleanSearch = search.toLowerCase().trim();
659
786
  let sortString = cleanSearch;
660
787
  let splitSearch = [];
@@ -666,9 +793,9 @@ export class StorageService extends Immutable {
666
793
  else {
667
794
  cleanSearch = new RegExp(`^(?!([a-z]+):).*(${cleanSearch}).*`);
668
795
  }
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));
796
+ const matchedDocuments = await StorageModel.db.find({ company, project, 'metadata.tags': { $regex: cleanSearch, $options: 'i' } }, { projection: { 'metadata.tags': 1 } })
797
+ .toArray();
798
+ return this.matchSortTags(matchedDocuments, cleanSearch, sortString);
672
799
  }
673
800
  single(company, project, filename) {
674
801
  return StorageModel.db.aggregate([
@@ -770,21 +897,39 @@ export class StorageService extends Immutable {
770
897
  /**
771
898
  * checks if the file was already successfully uploaded
772
899
  */
773
- exists(filename, prefix, app, options) {
774
- return StorageModel.db.countDocuments({
900
+ exists(filename, prefix, app, customer, list, options) {
901
+ const query = {
775
902
  company: options.company,
776
903
  project: options.project,
777
904
  filename,
778
905
  prefix,
779
906
  app,
780
- })
907
+ };
908
+ if (customer)
909
+ query.customer = customer;
910
+ if (list)
911
+ query.list = list;
912
+ return StorageModel.db.countDocuments(query)
781
913
  .then((exists) => exists >= 1);
782
914
  }
915
+ /**
916
+ * counts non-versioned storage documents bound to a specific assetId
917
+ */
918
+ async countByAssetId(assetId, app, customer, list, options) {
919
+ return StorageModel.db.countDocuments({
920
+ company: options.company,
921
+ project: options.project,
922
+ app,
923
+ assetId,
924
+ customer,
925
+ list,
926
+ });
927
+ }
783
928
  matchSortTags(documents, search, sort) {
784
929
  const mergedArray = [];
785
930
  const returnArray = [];
786
931
  documents.forEach((document) => {
787
- document.tags.forEach((tag) => {
932
+ document.metadata?.tags?.forEach((tag) => {
788
933
  if (mergedArray.find((tagArray) => tagArray.tag === tag))
789
934
  return;
790
935
  if (!tag.match(search))
@@ -801,14 +946,17 @@ export class StorageService extends Immutable {
801
946
  checkAllowedSize(filesize, allowedSize) {
802
947
  return filesize < allowedSize;
803
948
  }
804
- stats(company, project) {
949
+ stats(company, project, customer) {
950
+ const match = {
951
+ company,
952
+ project,
953
+ parent: { $exists: false },
954
+ };
955
+ if (customer)
956
+ match.customer = customer;
805
957
  const aggregation = [
806
958
  {
807
- $match: {
808
- company,
809
- project,
810
- parent: { $exists: false },
811
- },
959
+ $match: match,
812
960
  },
813
961
  {
814
962
  $group: {
@@ -833,28 +981,47 @@ export class StorageService extends Immutable {
833
981
  .then((value) => value[0] || { files: 0, size: 0 });
834
982
  }
835
983
  getByInternalId(internalId, options) {
836
- return StorageModel.db.findOne({ _id: internalId, company: options.company, project: options.project });
984
+ return StorageModel.db.findOne({ _id: internalId, company: options.company, project: options.project }, { projection: { path: 0 } });
837
985
  }
838
- getPathByFilename(filename, app, options) {
986
+ getPathByFilename(filename, app, customer, list, options) {
839
987
  const filenameObject = filenameHelper(filename);
840
- return StorageModel.db.findOne({
988
+ const query = {
841
989
  filename: filenameObject.filename,
842
990
  prefix: filenameObject.directory,
843
991
  company: options.company,
844
992
  project: options.project,
845
993
  app,
846
- }, {
994
+ };
995
+ if (customer)
996
+ query.customer = customer;
997
+ if (list)
998
+ query.list = list;
999
+ return StorageModel.db.findOne(query, {
847
1000
  projection: { path: 1, metadata: 1 },
848
1001
  });
849
1002
  }
850
- getByFilename(filename, prefix, bucket, options) {
851
- return StorageModel.db.findOne({
1003
+ getByFilename(filename, prefix, app, customer, list, options) {
1004
+ const query = {
852
1005
  filename,
853
1006
  prefix,
854
1007
  company: options.company,
855
1008
  project: options.project,
856
- bucket,
857
- });
1009
+ app,
1010
+ };
1011
+ if (customer)
1012
+ query.customer = customer;
1013
+ if (list)
1014
+ query.list = list;
1015
+ return StorageModel.db.findOne(query);
1016
+ }
1017
+ async removeBlobMembers(internalId, customer, options) {
1018
+ const storage = await this.model.db.findOne({ _id: internalId, company: options.company, project: options.project, customer });
1019
+ if (!storage)
1020
+ throw new Error('BLOB_NOT_FOUND');
1021
+ await fs.promises.unlink(`${this.options.dataFolder}/${storage.path}`);
1022
+ await this.removeBlobVersion(internalId, options);
1023
+ await this.delete(internalId, options.user, options.app);
1024
+ await jetstreamService.publish(`edge.${storage.app}.removed`, storage);
858
1025
  }
859
1026
  async removeBlob(internalId, options) {
860
1027
  const storage = await this.model.db.findOne({ _id: internalId, company: options.company, project: options.project });
@@ -862,14 +1029,16 @@ export class StorageService extends Immutable {
862
1029
  throw new Error('BLOB_NOT_FOUND');
863
1030
  await fs.promises.unlink(`${this.options.dataFolder}/${storage.path}`);
864
1031
  await this.removeBlobVersion(internalId, options);
865
- return this.delete(internalId, options.user, options.app);
1032
+ await this.delete(internalId, options.user, options.app);
1033
+ await jetstreamService.publish(`edge.${storage.app}.removed`, storage);
866
1034
  }
867
1035
  async removeBlobVersion(parentId, options) {
868
1036
  const versions = await this.model.db.find({ parent: parentId, company: options.company, project: options.project }).toArray();
869
1037
  if (versions.length) {
870
1038
  await Promise.allSettled(versions.map(async (single) => {
871
1039
  await fs.promises.unlink(`${this.options.dataFolder}/${single.path}`);
872
- return this.delete(single._id, options.user, options.app);
1040
+ await this.delete(single._id, options.user, options.app);
1041
+ await jetstreamService.publish(`edge.${single.app}.removed`, single);
873
1042
  }));
874
1043
  }
875
1044
  }