@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.
- package/eslint.config.js +146 -0
- package/lib/fastify-plugins.d.ts +6 -0
- package/lib/fastify-plugins.js +7 -0
- package/lib/fastify-plugins.js.map +1 -0
- package/lib/helpers.d.ts +4 -2
- package/lib/helpers.js +13 -2
- package/lib/helpers.js.map +1 -1
- package/lib/main.d.ts +1 -3
- package/lib/main.js +7 -3
- package/lib/main.js.map +1 -1
- package/lib/models.d.ts +4 -4
- package/lib/models.js +4 -4
- package/lib/models.js.map +1 -1
- package/lib/services.d.ts +5 -5
- package/lib/services.js +5 -13
- package/lib/services.js.map +1 -1
- package/lib/src/Immutable.class.d.ts +8 -1
- package/lib/src/Immutable.class.js +52 -8
- package/lib/src/Immutable.class.js.map +1 -1
- package/lib/src/ShareClientFactory.class.d.ts +1 -3
- package/lib/src/ShareClientFactory.class.js +1 -9
- package/lib/src/ShareClientFactory.class.js.map +1 -1
- package/lib/src/classes/models.class.js.map +1 -1
- package/lib/src/classes/modelv2.class.d.ts +2 -0
- package/lib/src/classes/modelv2.class.js +1 -1
- package/lib/src/classes/modelv2.class.js.map +1 -1
- package/lib/src/classes/mongo.class.js +4 -14
- package/lib/src/classes/mongo.class.js.map +1 -1
- package/lib/src/functions/handleError.d.ts +2 -3
- package/lib/src/functions/handleError.js +3 -16
- package/lib/src/functions/handleError.js.map +1 -1
- package/lib/src/functions/optionsHelper.d.ts +4 -4
- package/lib/src/functions/optionsHelper.js +5 -4
- package/lib/src/functions/optionsHelper.js.map +1 -1
- package/lib/src/functions/respondCode.d.ts +2 -1
- package/lib/src/functions/respondCode.js +1 -1
- package/lib/src/functions/respondCode.js.map +1 -1
- package/lib/src/helpers/ControllerHelper.d.ts +73 -0
- package/lib/src/helpers/ControllerHelper.js +242 -0
- package/lib/src/helpers/ControllerHelper.js.map +1 -0
- package/lib/src/helpers/EnvMapper.js +1 -0
- package/lib/src/helpers/EnvMapper.js.map +1 -0
- package/lib/src/helpers/auth0config.d.ts +6 -0
- package/lib/src/helpers/auth0config.js +23 -0
- package/lib/src/helpers/auth0config.js.map +1 -0
- package/lib/src/helpers/authPlugin.d.ts +29 -0
- package/lib/src/helpers/authPlugin.js +77 -0
- package/lib/src/helpers/authPlugin.js.map +1 -0
- package/lib/src/helpers/cacheHelper.d.ts +69 -0
- package/lib/src/helpers/cacheHelper.js +235 -0
- package/lib/src/helpers/cacheHelper.js.map +1 -0
- package/lib/src/helpers/createSasToken.d.ts +0 -2
- package/lib/src/helpers/createSasToken.js +35 -32
- package/lib/src/helpers/createSasToken.js.map +1 -1
- package/lib/src/helpers/getSecrets.d.ts +1 -1
- package/lib/src/helpers/getSecrets.js +10 -12
- package/lib/src/helpers/getSecrets.js.map +1 -1
- package/lib/src/helpers/limiterPlugin.d.ts +9 -0
- package/lib/src/helpers/limiterPlugin.js +72 -0
- package/lib/src/helpers/limiterPlugin.js.map +1 -0
- package/lib/src/helpers/loggingPlugin.d.ts +30 -0
- package/lib/src/helpers/loggingPlugin.js +87 -0
- package/lib/src/helpers/loggingPlugin.js.map +1 -0
- package/lib/src/helpers/queryAssertionPlugin.d.ts +3 -0
- package/lib/src/helpers/queryAssertionPlugin.js +20 -0
- package/lib/src/helpers/queryAssertionPlugin.js.map +1 -0
- package/lib/src/helpers/safeObjectId.d.ts +1 -1
- package/lib/src/helpers/safeObjectId.js +5 -1
- package/lib/src/helpers/safeObjectId.js.map +1 -1
- package/lib/src/helpers/storageSdkFactory.d.ts +2 -0
- package/lib/src/helpers/storageSdkFactory.js +11 -0
- package/lib/src/helpers/storageSdkFactory.js.map +1 -0
- package/lib/src/helpers/studioAppPlugin.d.ts +3 -0
- package/lib/src/helpers/studioAppPlugin.js +16 -0
- package/lib/src/helpers/studioAppPlugin.js.map +1 -0
- package/lib/src/logger.js +40 -2
- package/lib/src/logger.js.map +1 -1
- package/lib/src/models/access.model.d.ts +14 -3
- package/lib/src/models/access.model.js +7 -9
- package/lib/src/models/access.model.js.map +1 -1
- package/lib/src/models/customers.model.js +14 -4
- package/lib/src/models/customers.model.js.map +1 -1
- package/lib/src/models/design.model.d.ts +4 -0
- package/lib/src/models/design.model.js +58 -0
- package/lib/src/models/design.model.js.map +1 -0
- package/lib/src/models/domain.model.js +1 -1
- package/lib/src/models/domain.model.js.map +1 -1
- package/lib/src/models/editor.model.js +7 -0
- package/lib/src/models/editor.model.js.map +1 -1
- package/lib/src/models/emailLimit.model.d.ts +4 -0
- package/lib/src/models/emailLimit.model.js +31 -0
- package/lib/src/models/emailLimit.model.js.map +1 -0
- package/lib/src/models/hosting.model.js +1 -3
- package/lib/src/models/hosting.model.js.map +1 -1
- package/lib/src/models/hostingSettings.model.js +6 -4
- package/lib/src/models/hostingSettings.model.js.map +1 -1
- package/lib/src/models/invoice.model.d.ts +4 -0
- package/lib/src/models/invoice.model.js +235 -0
- package/lib/src/models/invoice.model.js.map +1 -0
- package/lib/src/models/mailFrom.model.js +51 -10
- package/lib/src/models/mailFrom.model.js.map +1 -1
- package/lib/src/models/project.model.js +2 -4
- package/lib/src/models/project.model.js.map +1 -1
- package/lib/src/models/publish-method.model.js +79 -430
- package/lib/src/models/publish-method.model.js.map +1 -1
- package/lib/src/models/publish.model.js +6 -0
- package/lib/src/models/publish.model.js.map +1 -1
- package/lib/src/models/storage.model.js +23 -5
- package/lib/src/models/storage.model.js.map +1 -1
- package/lib/src/models/structure.model.js +40 -0
- package/lib/src/models/structure.model.js.map +1 -1
- package/lib/src/models/upload.model.js +38 -2
- package/lib/src/models/upload.model.js.map +1 -1
- package/lib/src/prompts/textGeneration.js +88 -0
- package/lib/src/prompts/textGeneration.js.map +1 -1
- package/lib/src/prompts/textGenerationMulti.js +78 -44
- package/lib/src/prompts/textGenerationMulti.js.map +1 -1
- package/lib/src/services/access.service.d.ts +132 -33
- package/lib/src/services/access.service.js +270 -92
- package/lib/src/services/access.service.js.map +1 -1
- package/lib/src/services/ai.service.d.ts +4 -3
- package/lib/src/services/ai.service.js +22 -29
- package/lib/src/services/ai.service.js.map +1 -1
- package/lib/src/services/auth.service.d.ts +11 -0
- package/lib/src/services/auth.service.js +70 -0
- package/lib/src/services/auth.service.js.map +1 -0
- package/lib/src/services/conf.service.d.ts +3 -31
- package/lib/src/services/conf.service.js +58 -167
- package/lib/src/services/conf.service.js.map +1 -1
- package/lib/src/services/customers.service.d.ts +8 -4
- package/lib/src/services/customers.service.js +34 -7
- package/lib/src/services/customers.service.js.map +1 -1
- package/lib/src/services/designs.service.d.ts +7 -0
- package/lib/src/services/designs.service.js +10 -0
- package/lib/src/services/designs.service.js.map +1 -0
- package/lib/src/services/domains.service.d.ts +18 -84
- package/lib/src/services/domains.service.js +91 -583
- package/lib/src/services/domains.service.js.map +1 -1
- package/lib/src/services/editor.service.d.ts +4 -0
- package/lib/src/services/editor.service.js +28 -0
- package/lib/src/services/editor.service.js.map +1 -1
- package/lib/src/services/emailLimit.service.d.ts +21 -0
- package/lib/src/services/emailLimit.service.js +51 -0
- package/lib/src/services/emailLimit.service.js.map +1 -0
- package/lib/src/services/hosting.service.d.ts +12 -24
- package/lib/src/services/hosting.service.js +32 -122
- package/lib/src/services/hosting.service.js.map +1 -1
- package/lib/src/services/hostingAdmin.service.d.ts +1 -1
- package/lib/src/services/hostingAdmin.service.js +2 -2
- package/lib/src/services/hostingAdmin.service.js.map +1 -1
- package/lib/src/services/import.service.d.ts +6 -22
- package/lib/src/services/import.service.js +63 -65
- package/lib/src/services/import.service.js.map +1 -1
- package/lib/src/services/invoices.service.d.ts +30 -0
- package/lib/src/services/invoices.service.js +265 -0
- package/lib/src/services/invoices.service.js.map +1 -0
- package/lib/src/services/jetstream.service.d.ts +5 -3
- package/lib/src/services/jetstream.service.js +63 -7
- package/lib/src/services/jetstream.service.js.map +1 -1
- package/lib/src/services/listParticipants.service.d.ts +3 -5
- package/lib/src/services/listParticipants.service.js +76 -16
- package/lib/src/services/listParticipants.service.js.map +1 -1
- package/lib/src/services/mailFrom.service.d.ts +14 -1
- package/lib/src/services/mailFrom.service.js +59 -0
- package/lib/src/services/mailFrom.service.js.map +1 -1
- package/lib/src/services/me.service.d.ts +23 -12
- package/lib/src/services/me.service.js +65 -88
- package/lib/src/services/me.service.js.map +1 -1
- package/lib/src/services/publish.service.d.ts +6 -8
- package/lib/src/services/publish.service.js +34 -32
- package/lib/src/services/publish.service.js.map +1 -1
- package/lib/src/services/publishData.service.d.ts +10 -7
- package/lib/src/services/publishData.service.js +32 -75
- package/lib/src/services/publishData.service.js.map +1 -1
- package/lib/src/services/spamAnalasys.service.d.ts +4 -4
- package/lib/src/services/spamAnalasys.service.js +36 -44
- package/lib/src/services/spamAnalasys.service.js.map +1 -1
- package/lib/src/services/storage.service.d.ts +68 -39
- package/lib/src/services/storage.service.js +382 -209
- package/lib/src/services/storage.service.js.map +1 -1
- package/lib/src/services/structures.service.d.ts +8 -1
- package/lib/src/services/structures.service.js +26 -1
- package/lib/src/services/structures.service.js.map +1 -1
- package/lib/src/services/upload.service.d.ts +8 -1
- package/lib/src/services/upload.service.js +76 -3
- package/lib/src/services/upload.service.js.map +1 -1
- package/lib/tests/groupStructuresByModel.spec.d.ts +1 -0
- package/lib/tests/groupStructuresByModel.spec.js +33 -0
- package/lib/tests/groupStructuresByModel.spec.js.map +1 -0
- package/lib/tests/listParticipantsServiceJoin.spec.d.ts +1 -0
- package/lib/tests/listParticipantsServiceJoin.spec.js +151 -0
- package/lib/tests/listParticipantsServiceJoin.spec.js.map +1 -0
- package/lib/tests/storageServiceHandleFile.spec.d.ts +1 -0
- package/lib/tests/storageServiceHandleFile.spec.js +94 -0
- package/lib/tests/storageServiceHandleFile.spec.js.map +1 -0
- package/lib/tests/storageServiceToken.spec.d.ts +1 -0
- package/lib/tests/storageServiceToken.spec.js +104 -0
- package/lib/tests/storageServiceToken.spec.js.map +1 -0
- package/lib/tests/uploadServiceCreate.spec.d.ts +1 -0
- package/lib/tests/uploadServiceCreate.spec.js +81 -0
- package/lib/tests/uploadServiceCreate.spec.js.map +1 -0
- package/package.json +30 -26
- package/lib/src/AzureBlobStorage.share.d.ts +0 -19
- package/lib/src/AzureBlobStorage.share.js +0 -162
- package/lib/src/AzureBlobStorage.share.js.map +0 -1
- package/lib/src/AzureFileStorage.share.d.ts +0 -22
- package/lib/src/AzureFileStorage.share.js +0 -139
- package/lib/src/AzureFileStorage.share.js.map +0 -1
- package/lib/src/AzureVault.d.ts +0 -14
- package/lib/src/AzureVault.js +0 -28
- package/lib/src/AzureVault.js.map +0 -1
- package/lib/src/dns.challenge.class.d.ts +0 -17
- package/lib/src/dns.challenge.class.js +0 -41
- package/lib/src/dns.challenge.class.js.map +0 -1
- package/lib/src/http.challenge.class.d.ts +0 -33
- package/lib/src/http.challenge.class.js +0 -58
- package/lib/src/http.challenge.class.js.map +0 -1
- package/lib/src/models/certificate-action.model.d.ts +0 -5
- package/lib/src/models/certificate-action.model.js +0 -230
- package/lib/src/models/certificate-action.model.js.map +0 -1
- package/lib/src/models/certificate.model.d.ts +0 -4
- package/lib/src/models/certificate.model.js +0 -96
- package/lib/src/models/certificate.model.js.map +0 -1
- package/lib/src/models/editorBase.model.d.ts +0 -4
- package/lib/src/models/editorBase.model.js +0 -39
- package/lib/src/models/editorBase.model.js.map +0 -1
- package/lib/src/services/certificates.service.js +0 -199
- package/lib/src/services/certificates.service.js.map +0 -1
- package/lib/src/services/certificatesAction.service.d.ts +0 -0
- package/lib/src/services/certificatesAction.service.js +0 -237
- package/lib/src/services/certificatesAction.service.js.map +0 -1
- package/lib/src/services/editorBase.service.d.ts +0 -46
- package/lib/src/services/editorBase.service.js +0 -161
- package/lib/src/services/editorBase.service.js.map +0 -1
- package/lib/src/services/handleFile.service.d.ts +0 -9
- package/lib/src/services/handleFile.service.js +0 -45
- package/lib/src/services/handleFile.service.js.map +0 -1
- package/lib/src/services/media.service.d.ts +0 -35
- package/lib/src/services/media.service.js +0 -418
- package/lib/src/services/media.service.js.map +0 -1
- package/lib/src/services/share.service.d.ts +0 -6
- package/lib/src/services/share.service.js +0 -4
- package/lib/src/services/share.service.js.map +0 -1
- /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:
|
|
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(
|
|
39
|
+
* const { token } = getSecureToken(3600);
|
|
59
40
|
*
|
|
60
41
|
* @example
|
|
61
42
|
* // Generate token limited to specific company and project
|
|
62
|
-
* const { token } = getSecureToken(
|
|
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(
|
|
56
|
+
* const { token } = getSecureToken(3600, {
|
|
70
57
|
* url: '/company/project/files/'
|
|
71
58
|
* });
|
|
72
59
|
*/
|
|
73
|
-
getSecureToken(
|
|
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:
|
|
78
|
-
let payload = `${
|
|
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
|
-
|
|
140
|
+
}
|
|
141
|
+
// Parse payload structure: expires[:base64(limits)]
|
|
147
142
|
const parts = payload.split(':');
|
|
148
|
-
// parts[0] is
|
|
149
|
-
// parts[1] is
|
|
150
|
-
|
|
151
|
-
|
|
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[
|
|
155
|
-
if (
|
|
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 >
|
|
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[
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
446
|
-
|
|
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
|
|
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((
|
|
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
|
|
475
|
-
const isRootLevel = query
|
|
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
|
-
|
|
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
|
|
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((
|
|
582
|
+
.then((r) => r[0] || { all: 0, data: [], count: 0 });
|
|
514
583
|
}
|
|
515
584
|
// Treat undefined prefix same as empty string (root level)
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
//
|
|
538
|
-
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
|
|
582
|
-
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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:
|
|
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
|
-
|
|
620
|
-
|
|
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:
|
|
650
|
-
count: { $
|
|
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
|
-
|
|
670
|
-
.toArray()
|
|
671
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
851
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|