@hed-hog/core 0.0.400 → 0.0.402
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/dist/access-log/access-log.interceptor.d.ts +10 -0
- package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
- package/dist/access-log/access-log.interceptor.js +15 -1
- package/dist/access-log/access-log.interceptor.js.map +1 -1
- package/dist/ai/ai.controller.d.ts +2 -0
- package/dist/ai/ai.controller.d.ts.map +1 -1
- package/dist/ai/ai.service.d.ts +86 -2
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +304 -23
- package/dist/ai/ai.service.js.map +1 -1
- package/dist/ai-instruction/ai-prompt-resolver.service.d.ts +12 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.d.ts.map +1 -1
- package/dist/ai-instruction/ai-prompt-resolver.service.js +26 -0
- package/dist/ai-instruction/ai-prompt-resolver.service.js.map +1 -1
- package/dist/core.module.d.ts.map +1 -1
- package/dist/core.module.js +3 -0
- package/dist/core.module.js.map +1 -1
- package/dist/document-kit/document-kit.module.d.ts +3 -0
- package/dist/document-kit/document-kit.module.d.ts.map +1 -0
- package/dist/document-kit/document-kit.module.js +24 -0
- package/dist/document-kit/document-kit.module.js.map +1 -0
- package/dist/document-kit/document-kit.registry.d.ts +55 -0
- package/dist/document-kit/document-kit.registry.d.ts.map +1 -0
- package/dist/document-kit/document-kit.registry.js +91 -0
- package/dist/document-kit/document-kit.registry.js.map +1 -0
- package/dist/document-kit/document-kit.service.d.ts +49 -0
- package/dist/document-kit/document-kit.service.d.ts.map +1 -0
- package/dist/document-kit/document-kit.service.js +135 -0
- package/dist/document-kit/document-kit.service.js.map +1 -0
- package/dist/file/file-upload-ticket.service.d.ts +1 -1
- package/dist/file/file.controller.d.ts +3 -3
- package/dist/file/file.service.d.ts +136 -13
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +297 -27
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/mcp-file-source.schema.d.ts.map +1 -1
- package/dist/file/mcp-file-source.schema.js +8 -5
- package/dist/file/mcp-file-source.schema.js.map +1 -1
- package/dist/file/provider/abstract.provider.d.ts +33 -0
- package/dist/file/provider/abstract.provider.d.ts.map +1 -1
- package/dist/file/provider/abstract.provider.js +9 -0
- package/dist/file/provider/abstract.provider.js.map +1 -1
- package/dist/file/provider/s3-multipart.util.d.ts +17 -0
- package/dist/file/provider/s3-multipart.util.d.ts.map +1 -0
- package/dist/file/provider/s3-multipart.util.js +88 -0
- package/dist/file/provider/s3-multipart.util.js.map +1 -0
- package/dist/file/provider/s3.provider.d.ts +9 -1
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +16 -0
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/file/provider/spaces.provider.d.ts +10 -1
- package/dist/file/provider/spaces.provider.d.ts.map +1 -1
- package/dist/file/provider/spaces.provider.js +17 -0
- package/dist/file/provider/spaces.provider.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/integration-profile/integration-profile.controller.d.ts +1 -0
- package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.secrets.js +26 -0
- package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
- package/dist/integration-profile/integration-profile.service.d.ts +1 -0
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +3 -3
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/integration-profile/testers/ai.testers.d.ts.map +1 -1
- package/dist/integration-profile/testers/ai.testers.js +46 -8
- package/dist/integration-profile/testers/ai.testers.js.map +1 -1
- package/dist/integration-profile/testers/http-probe.d.ts.map +1 -1
- package/dist/integration-profile/testers/http-probe.js +5 -3
- package/dist/integration-profile/testers/http-probe.js.map +1 -1
- package/dist/integration-profile/testers/integration-tester.types.d.ts +5 -0
- package/dist/integration-profile/testers/integration-tester.types.d.ts.map +1 -1
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +16 -1
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mcp/mcp-server.service.d.ts.map +1 -1
- package/dist/mcp/mcp-server.service.js +10 -1
- package/dist/mcp/mcp-server.service.js.map +1 -1
- package/dist/mcp/mcp.module.d.ts.map +1 -1
- package/dist/mcp/mcp.module.js +2 -0
- package/dist/mcp/mcp.module.js.map +1 -1
- package/dist/mcp/tools/core-access-log.mcp-tools.d.ts +17 -0
- package/dist/mcp/tools/core-access-log.mcp-tools.d.ts.map +1 -0
- package/dist/mcp/tools/core-access-log.mcp-tools.js +74 -0
- package/dist/mcp/tools/core-access-log.mcp-tools.js.map +1 -0
- package/dist/mcp/tools/core-files.mcp-tools.js +4 -4
- package/dist/mcp/tools/core-files.mcp-tools.js.map +1 -1
- package/dist/meeting/meeting-provider.registry.d.ts +72 -0
- package/dist/meeting/meeting-provider.registry.d.ts.map +1 -1
- package/dist/meeting/meeting-provider.registry.js.map +1 -1
- package/dist/oauth/oauth-token-broker.service.d.ts +50 -1
- package/dist/oauth/oauth-token-broker.service.d.ts.map +1 -1
- package/dist/oauth/oauth-token-broker.service.js +105 -3
- package/dist/oauth/oauth-token-broker.service.js.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +6 -0
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.js +30 -4
- package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
- package/dist/pdf/document/charts/chart-bar.d.ts +24 -0
- package/dist/pdf/document/charts/chart-bar.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-bar.js +117 -0
- package/dist/pdf/document/charts/chart-bar.js.map +1 -0
- package/dist/pdf/document/charts/chart-donut.d.ts +16 -0
- package/dist/pdf/document/charts/chart-donut.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-donut.js +89 -0
- package/dist/pdf/document/charts/chart-donut.js.map +1 -0
- package/dist/pdf/document/charts/chart-gauge.d.ts +15 -0
- package/dist/pdf/document/charts/chart-gauge.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-gauge.js +60 -0
- package/dist/pdf/document/charts/chart-gauge.js.map +1 -0
- package/dist/pdf/document/charts/chart-line.d.ts +29 -0
- package/dist/pdf/document/charts/chart-line.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-line.js +89 -0
- package/dist/pdf/document/charts/chart-line.js.map +1 -0
- package/dist/pdf/document/charts/chart-primitives.d.ts +64 -0
- package/dist/pdf/document/charts/chart-primitives.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-primitives.js +110 -0
- package/dist/pdf/document/charts/chart-primitives.js.map +1 -0
- package/dist/pdf/document/charts/chart-radar.d.ts +13 -0
- package/dist/pdf/document/charts/chart-radar.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-radar.js +83 -0
- package/dist/pdf/document/charts/chart-radar.js.map +1 -0
- package/dist/pdf/document/charts/chart-scale.d.ts +39 -0
- package/dist/pdf/document/charts/chart-scale.d.ts.map +1 -0
- package/dist/pdf/document/charts/chart-scale.js +118 -0
- package/dist/pdf/document/charts/chart-scale.js.map +1 -0
- package/dist/pdf/document/charts/index.d.ts +8 -0
- package/dist/pdf/document/charts/index.d.ts.map +1 -0
- package/dist/pdf/document/charts/index.js +24 -0
- package/dist/pdf/document/charts/index.js.map +1 -0
- package/dist/pdf/document/index.d.ts +8 -0
- package/dist/pdf/document/index.d.ts.map +1 -0
- package/dist/pdf/document/index.js +24 -0
- package/dist/pdf/document/index.js.map +1 -0
- package/dist/pdf/document/pdf-document.chrome.d.ts +20 -0
- package/dist/pdf/document/pdf-document.chrome.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.chrome.js +36 -0
- package/dist/pdf/document/pdf-document.chrome.js.map +1 -0
- package/dist/pdf/document/pdf-document.css.d.ts +20 -0
- package/dist/pdf/document/pdf-document.css.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.css.js +327 -0
- package/dist/pdf/document/pdf-document.css.js.map +1 -0
- package/dist/pdf/document/pdf-document.renderer.d.ts +10 -0
- package/dist/pdf/document/pdf-document.renderer.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.renderer.js +226 -0
- package/dist/pdf/document/pdf-document.renderer.js.map +1 -0
- package/dist/pdf/document/pdf-document.service.d.ts +27 -0
- package/dist/pdf/document/pdf-document.service.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.service.js +81 -0
- package/dist/pdf/document/pdf-document.service.js.map +1 -0
- package/dist/pdf/document/pdf-document.theme.d.ts +41 -0
- package/dist/pdf/document/pdf-document.theme.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.theme.js +105 -0
- package/dist/pdf/document/pdf-document.theme.js.map +1 -0
- package/dist/pdf/document/pdf-document.types.d.ts +164 -0
- package/dist/pdf/document/pdf-document.types.d.ts.map +1 -0
- package/dist/pdf/document/pdf-document.types.js +18 -0
- package/dist/pdf/document/pdf-document.types.js.map +1 -0
- package/dist/pdf/pdf.module.d.ts.map +1 -1
- package/dist/pdf/pdf.module.js +3 -2
- package/dist/pdf/pdf.module.js.map +1 -1
- package/dist/pdf/pdf.service.d.ts +75 -1
- package/dist/pdf/pdf.service.d.ts.map +1 -1
- package/dist/pdf/pdf.service.js +167 -63
- package/dist/pdf/pdf.service.js.map +1 -1
- package/dist/pdf/templates/commercial-proposal.template.d.ts +96 -0
- package/dist/pdf/templates/commercial-proposal.template.d.ts.map +1 -0
- package/dist/pdf/templates/commercial-proposal.template.js +276 -0
- package/dist/pdf/templates/commercial-proposal.template.js.map +1 -0
- package/dist/setting/favicon.controller.d.ts +26 -0
- package/dist/setting/favicon.controller.d.ts.map +1 -0
- package/dist/setting/favicon.controller.js +130 -0
- package/dist/setting/favicon.controller.js.map +1 -0
- package/dist/setting/setting.module.d.ts.map +1 -1
- package/dist/setting/setting.module.js +2 -1
- package/dist/setting/setting.module.js.map +1 -1
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +3 -0
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/task/task.service.d.ts +6 -0
- package/dist/task/task.service.d.ts.map +1 -1
- package/dist/task/task.service.js +44 -13
- package/dist/task/task.service.js.map +1 -1
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +35 -2
- package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -1
- package/dist/whatsapp-sender/whatsapp-sender.service.js +107 -22
- package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -1
- package/hedhog/data/integration_type.yaml +6 -0
- package/hedhog/data/route.yaml +8 -0
- package/hedhog/data/setting_group.yaml +52 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.test.tsx.ejs +85 -2
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +576 -491
- package/hedhog/frontend/app/users/simulate-access-dialog.test.tsx.ejs +29 -50
- package/hedhog/frontend/app/users/simulate-access-dialog.tsx.ejs +146 -50
- package/hedhog/frontend/messages/en.json +81 -2
- package/hedhog/frontend/messages/pt.json +81 -2
- package/hedhog/table/file.yaml +3 -1
- package/package.json +5 -5
- package/src/access-log/access-log.interceptor.spec.ts +50 -0
- package/src/access-log/access-log.interceptor.ts +18 -0
- package/src/ai/ai-attachment.fragments.spec.ts +56 -3
- package/src/ai/ai.service.responses.spec.ts +429 -0
- package/src/ai/ai.service.ts +409 -27
- package/src/ai-instruction/ai-prompt-resolver.service.spec.ts +44 -0
- package/src/ai-instruction/ai-prompt-resolver.service.ts +29 -0
- package/src/core.module.ts +3 -0
- package/src/document-kit/document-kit.module.ts +12 -0
- package/src/document-kit/document-kit.registry.ts +125 -0
- package/src/document-kit/document-kit.service.spec.ts +156 -0
- package/src/document-kit/document-kit.service.ts +173 -0
- package/src/file/file-direct-upload.spec.ts +344 -0
- package/src/file/file.service.spec.ts +168 -1
- package/src/file/file.service.ts +435 -36
- package/src/file/mcp-file-source.schema.ts +8 -5
- package/src/file/provider/abstract.provider.ts +57 -0
- package/src/file/provider/s3-multipart.spec.ts +123 -0
- package/src/file/provider/s3-multipart.util.ts +126 -0
- package/src/file/provider/s3.provider.ts +65 -1
- package/src/file/provider/spaces.provider.ts +67 -1
- package/src/index.ts +5 -0
- package/src/integration-profile/integration-profile.secrets.ts +26 -0
- package/src/integration-profile/integration-profile.service.ts +1 -0
- package/src/integration-profile/testers/ai.testers.ts +70 -5
- package/src/integration-profile/testers/http-probe.ts +5 -3
- package/src/integration-profile/testers/integration-tester.types.ts +5 -0
- package/src/integration-profile/testers/oauth.testers.spec.ts +13 -0
- package/src/integration-profile/testers/remaining.testers.spec.ts +65 -1
- package/src/integration-profile/testers/tester-coverage.spec.ts +12 -0
- package/src/mail/mail.service.spec.ts +25 -0
- package/src/mail/mail.service.ts +18 -1
- package/src/mcp/mcp-server.service.spec.ts +16 -0
- package/src/mcp/mcp-server.service.ts +10 -1
- package/src/mcp/mcp.module.ts +2 -0
- package/src/mcp/tools/core-access-log.mcp-tools.ts +66 -0
- package/src/mcp/tools/core-files.mcp-tools.ts +4 -4
- package/src/meeting/meeting-provider.registry.ts +80 -0
- package/src/oauth/oauth-token-broker.service.spec.ts +108 -0
- package/src/oauth/oauth-token-broker.service.ts +129 -2
- package/src/oauth/providers/microsoft-entra-id.provider.spec.ts +133 -0
- package/src/oauth/providers/microsoft-entra-id.provider.ts +29 -2
- package/src/pdf/document/charts/chart-bar.ts +217 -0
- package/src/pdf/document/charts/chart-donut.ts +163 -0
- package/src/pdf/document/charts/chart-gauge.ts +94 -0
- package/src/pdf/document/charts/chart-line.ts +159 -0
- package/src/pdf/document/charts/chart-primitives.ts +188 -0
- package/src/pdf/document/charts/chart-radar.ts +136 -0
- package/src/pdf/document/charts/chart-scale.spec.ts +93 -0
- package/src/pdf/document/charts/chart-scale.ts +141 -0
- package/src/pdf/document/charts/charts.spec.ts +157 -0
- package/src/pdf/document/charts/index.ts +7 -0
- package/src/pdf/document/index.ts +7 -0
- package/src/pdf/document/pdf-document.chrome.ts +38 -0
- package/src/pdf/document/pdf-document.css.ts +326 -0
- package/src/pdf/document/pdf-document.renderer.spec.ts +172 -0
- package/src/pdf/document/pdf-document.renderer.ts +317 -0
- package/src/pdf/document/pdf-document.service.ts +77 -0
- package/src/pdf/document/pdf-document.theme.ts +146 -0
- package/src/pdf/document/pdf-document.types.ts +149 -0
- package/src/pdf/pdf.module.ts +3 -2
- package/src/pdf/pdf.service.spec.ts +131 -0
- package/src/pdf/pdf.service.ts +256 -82
- package/src/pdf/templates/commercial-proposal.template.spec.ts +177 -0
- package/src/pdf/templates/commercial-proposal.template.ts +400 -0
- package/src/setting/favicon.controller.spec.ts +92 -0
- package/src/setting/favicon.controller.ts +135 -0
- package/src/setting/setting.module.ts +2 -1
- package/src/setting/setting.service.ts +3 -0
- package/src/task/task.service.spec.ts +77 -1
- package/src/task/task.service.ts +58 -14
- package/src/whatsapp-sender/whatsapp-sender.service.spec.ts +263 -0
- package/src/whatsapp-sender/whatsapp-sender.service.ts +154 -16
|
@@ -34,7 +34,7 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
|
|
|
34
34
|
var _a;
|
|
35
35
|
var FileService_1;
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.FileService = void 0;
|
|
37
|
+
exports.FileService = exports.DIRECT_UPLOAD_DEFAULT_TTL_SECONDS = exports.DIRECT_UPLOAD_MAX_PARTS = exports.DIRECT_UPLOAD_MIN_PART_BYTES = void 0;
|
|
38
38
|
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
39
39
|
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
40
40
|
const client_lambda_1 = require("@aws-sdk/client-lambda");
|
|
@@ -57,6 +57,8 @@ const mimetype_sniff_util_1 = require("./mimetype-sniff.util");
|
|
|
57
57
|
const image_optimization_service_1 = require("./image-optimization.service");
|
|
58
58
|
const abstract_provider_1 = require("./provider/abstract.provider");
|
|
59
59
|
const provider_factory_1 = require("./provider/provider.factory");
|
|
60
|
+
/** Códigos de erro do S3/Spaces que indicam indisponibilidade passageira, não arquivo ruim. */
|
|
61
|
+
const STORAGE_UNAVAILABLE_CODES = ['SlowDown', 'ServiceUnavailable', 'InternalError', 'RequestTimeout'];
|
|
60
62
|
/**
|
|
61
63
|
* Resolver o provider custa 3 queries (setting + integration_profile, que ainda decifra o
|
|
62
64
|
* config, + file_provider) e reconstrói o client de storage a cada arquivo. Em upload em
|
|
@@ -65,6 +67,17 @@ const provider_factory_1 = require("./provider/provider.factory");
|
|
|
65
67
|
* A janela curta mantém a troca de perfil de storage praticamente imediata.
|
|
66
68
|
*/
|
|
67
69
|
const PROVIDER_CACHE_TTL_MS = Math.max(0, Number((_a = process.env.FILE_PROVIDER_CACHE_TTL_MS) !== null && _a !== void 0 ? _a : 30000));
|
|
70
|
+
/** Upload direto em partes: mínimo de 16 MiB por parte e até 9000 partes (regra do Studio). */
|
|
71
|
+
exports.DIRECT_UPLOAD_MIN_PART_BYTES = 16 * 1024 * 1024;
|
|
72
|
+
exports.DIRECT_UPLOAD_MAX_PARTS = 9000;
|
|
73
|
+
/** Validade do token e das URLs de parte: um vídeo grande em conexão lenta leva horas. */
|
|
74
|
+
exports.DIRECT_UPLOAD_DEFAULT_TTL_SECONDS = 86400;
|
|
75
|
+
const DIRECT_UPLOAD_TOKEN_TYPE = 'file-direct-upload';
|
|
76
|
+
/**
|
|
77
|
+
* `file.size` é BIGINT no banco, e o PrismaService o devolve como number: o teto real é o
|
|
78
|
+
* maior inteiro exato de um number (~9 PB).
|
|
79
|
+
*/
|
|
80
|
+
const FILE_SIZE_COLUMN_MAX = Number.MAX_SAFE_INTEGER;
|
|
68
81
|
let FileService = FileService_1 = class FileService {
|
|
69
82
|
resolveProviderPath(file) {
|
|
70
83
|
// Supports legacy basename-only values while prioritizing full provider keys.
|
|
@@ -226,14 +239,30 @@ let FileService = FileService_1 = class FileService {
|
|
|
226
239
|
});
|
|
227
240
|
return file;
|
|
228
241
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Traduz a falha do provider de storage para a resposta HTTP certa.
|
|
244
|
+
*
|
|
245
|
+
* 5xx e throttle do S3/Spaces são indisponibilidade passageira, não defeito:
|
|
246
|
+
* viram 503 com `code: STORAGE_UNAVAILABLE`, que o HttpExceptionFilter da API
|
|
247
|
+
* responde com `Retry-After` e reporta deduplicado. Sem isso, um 503 do Spaces
|
|
248
|
+
* com corpo que não é XML (ex.: `upstream connect error` do balanceador)
|
|
249
|
+
* estourava no parse do SDK como `char 'u' is not expected` e virava 500.
|
|
250
|
+
*
|
|
251
|
+
* O status vem de `$metadata.httpStatusCode`, que o SDK carimba mesmo quando a
|
|
252
|
+
* desserialização do erro falha; `$response.statusCode` cobre o resto.
|
|
253
|
+
*/
|
|
254
|
+
mapStorageError(error, fileId) {
|
|
255
|
+
var _a, _b, _c;
|
|
256
|
+
const status = (_b = (_a = error === null || error === void 0 ? void 0 : error.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode) !== null && _b !== void 0 ? _b : (_c = error === null || error === void 0 ? void 0 : error.$response) === null || _c === void 0 ? void 0 : _c.statusCode;
|
|
257
|
+
const codes = [error === null || error === void 0 ? void 0 : error.name, error === null || error === void 0 ? void 0 : error.Code];
|
|
258
|
+
if (codes.includes('NoSuchKey') || status === 404) {
|
|
235
259
|
throw new common_1.NotFoundException(`File not found in storage: ${fileId}`);
|
|
236
260
|
}
|
|
261
|
+
const unavailable = (typeof status === 'number' && status >= 500) ||
|
|
262
|
+
codes.some((c) => STORAGE_UNAVAILABLE_CODES.includes(c));
|
|
263
|
+
if (unavailable) {
|
|
264
|
+
throw new common_1.ServiceUnavailableException({ message: 'Storage temporarily unavailable', code: 'STORAGE_UNAVAILABLE' }, { cause: error });
|
|
265
|
+
}
|
|
237
266
|
throw error;
|
|
238
267
|
}
|
|
239
268
|
async openReadStreamById(fileId) {
|
|
@@ -254,12 +283,16 @@ let FileService = FileService_1 = class FileService {
|
|
|
254
283
|
}
|
|
255
284
|
const provider = await this.getProvider();
|
|
256
285
|
const providerPath = this.resolveProviderPath(file);
|
|
286
|
+
// O HEAD só serve para descobrir o tamanho quando a tabela não o tem. Feito
|
|
287
|
+
// sempre, dobrava as requisições ao storage em cada segmento HLS.
|
|
257
288
|
let metadata;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
289
|
+
if (file.size == null) {
|
|
290
|
+
try {
|
|
291
|
+
metadata = await provider.metaData(providerPath);
|
|
292
|
+
}
|
|
293
|
+
catch (_b) {
|
|
294
|
+
metadata = undefined;
|
|
295
|
+
}
|
|
263
296
|
}
|
|
264
297
|
try {
|
|
265
298
|
const stream = await provider.readStream(providerPath);
|
|
@@ -270,7 +303,7 @@ let FileService = FileService_1 = class FileService {
|
|
|
270
303
|
};
|
|
271
304
|
}
|
|
272
305
|
catch (error) {
|
|
273
|
-
this.
|
|
306
|
+
this.mapStorageError(error, fileId);
|
|
274
307
|
}
|
|
275
308
|
}
|
|
276
309
|
async openReadStreamWithRange(fileId, start, end) {
|
|
@@ -285,11 +318,17 @@ let FileService = FileService_1 = class FileService {
|
|
|
285
318
|
throw new common_1.NotFoundException(`File not found: ${fileId}`);
|
|
286
319
|
const provider = await this.getProvider();
|
|
287
320
|
const providerPath = this.resolveProviderPath(file);
|
|
288
|
-
const totalSize = (_a = file.size) !== null && _a !== void 0 ? _a : 0;
|
|
321
|
+
const totalSize = Number((_a = file.size) !== null && _a !== void 0 ? _a : 0);
|
|
289
322
|
const resolvedStart = start !== null && start !== void 0 ? start : 0;
|
|
290
323
|
const resolvedEnd = end !== undefined ? end : totalSize > 0 ? totalSize - 1 : undefined;
|
|
291
324
|
const hasRange = start !== undefined;
|
|
292
|
-
|
|
325
|
+
let stream;
|
|
326
|
+
try {
|
|
327
|
+
stream = await provider.readStream(providerPath, hasRange ? { start: resolvedStart, end: resolvedEnd } : undefined);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
this.mapStorageError(error, fileId);
|
|
331
|
+
}
|
|
293
332
|
const contentLength = resolvedEnd !== undefined ? resolvedEnd - resolvedStart + 1 : totalSize;
|
|
294
333
|
return {
|
|
295
334
|
file,
|
|
@@ -329,7 +368,6 @@ let FileService = FileService_1 = class FileService {
|
|
|
329
368
|
}
|
|
330
369
|
}
|
|
331
370
|
async getBuffer(fileId) {
|
|
332
|
-
var _a;
|
|
333
371
|
if (!fileId) {
|
|
334
372
|
throw new common_1.BadRequestException(`No file ID provided`);
|
|
335
373
|
}
|
|
@@ -351,13 +389,7 @@ let FileService = FileService_1 = class FileService {
|
|
|
351
389
|
providerBuffer = await provider.buffer(providerPath);
|
|
352
390
|
}
|
|
353
391
|
catch (error) {
|
|
354
|
-
|
|
355
|
-
(error === null || error === void 0 ? void 0 : error.Code) === 'NoSuchKey' ||
|
|
356
|
-
((_a = error === null || error === void 0 ? void 0 : error.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode) === 404;
|
|
357
|
-
if (missingInStorage) {
|
|
358
|
-
throw new common_1.NotFoundException(`File not found in storage: ${fileId}`);
|
|
359
|
-
}
|
|
360
|
-
throw error;
|
|
392
|
+
this.mapStorageError(error, fileId);
|
|
361
393
|
}
|
|
362
394
|
const buffer = Buffer.from(providerBuffer);
|
|
363
395
|
return {
|
|
@@ -428,7 +460,13 @@ let FileService = FileService_1 = class FileService {
|
|
|
428
460
|
}
|
|
429
461
|
const normalizedFilename = this.normalizeUploadedFilename(fileBuffer.originalname);
|
|
430
462
|
const effectiveMimetype = await this.resolveEffectiveMimetype(fileBuffer.mimetype, normalizedFilename, fileBuffer.buffer);
|
|
431
|
-
const optimized =
|
|
463
|
+
const optimized = (options === null || options === void 0 ? void 0 : options.skipImageOptimization)
|
|
464
|
+
? {
|
|
465
|
+
buffer: fileBuffer.buffer,
|
|
466
|
+
mimetype: effectiveMimetype,
|
|
467
|
+
originalname: normalizedFilename,
|
|
468
|
+
}
|
|
469
|
+
: await this.imageOptimizationService.optimizeBuffer(fileBuffer.buffer, effectiveMimetype, normalizedFilename, { maxWidth: resize === null || resize === void 0 ? void 0 : resize.maxWidth, maxHeight: resize === null || resize === void 0 ? void 0 : resize.maxHeight });
|
|
432
470
|
const normalizedFile = Object.assign(Object.assign({}, fileBuffer), { buffer: optimized.buffer, mimetype: optimized.mimetype, originalname: optimized.originalname, size: optimized.buffer.length });
|
|
433
471
|
const url = await provider.upload(destination, normalizedFile);
|
|
434
472
|
const file = await this.prismaService.file.create({
|
|
@@ -480,8 +518,9 @@ let FileService = FileService_1 = class FileService {
|
|
|
480
518
|
// streaming direto do disco via provider.uploadFromPath. Isso evita carregar
|
|
481
519
|
// o arquivo inteiro na RAM (fs.readFile estoura o limite de Buffer / causa OOM)
|
|
482
520
|
// e habilita upload multipart no S3. A otimização de imagem só se aplica a
|
|
483
|
-
// image/*, então não há perda nesse caminho.
|
|
484
|
-
|
|
521
|
+
// image/*, então não há perda nesse caminho. `skipImageOptimization` manda a
|
|
522
|
+
// imagem pelo mesmo caminho: o chamador quer os bytes exatamente como estão.
|
|
523
|
+
if (!mimetype.startsWith('image/') || (metadata === null || metadata === void 0 ? void 0 : metadata.skipImageOptimization)) {
|
|
485
524
|
const url = await provider.uploadFromPath(destination, filePath, {
|
|
486
525
|
filename: originalname,
|
|
487
526
|
mimetype,
|
|
@@ -530,6 +569,222 @@ let FileService = FileService_1 = class FileService {
|
|
|
530
569
|
});
|
|
531
570
|
return file;
|
|
532
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* O storage ativo aceita upload direto em partes assinadas (S3 e Spaces)? Sem provider
|
|
574
|
+
* configurado, ou com um que não implementa, é `false` — quem oferece o caminho cai no
|
|
575
|
+
* upload pela API.
|
|
576
|
+
*/
|
|
577
|
+
async supportsDirectUpload() {
|
|
578
|
+
try {
|
|
579
|
+
return (0, abstract_provider_1.supportsDirectUpload)(await this.getProvider());
|
|
580
|
+
}
|
|
581
|
+
catch (_a) {
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async getDirectUploadProvider() {
|
|
586
|
+
const provider = await this.getProvider();
|
|
587
|
+
if (!(0, abstract_provider_1.supportsDirectUpload)(provider)) {
|
|
588
|
+
throw new common_1.ConflictException('The file storage does not support direct uploads');
|
|
589
|
+
}
|
|
590
|
+
return provider;
|
|
591
|
+
}
|
|
592
|
+
/** `key` gravada pelo start: `${destination}/${uuid.ext}`, sem subpasta extra. */
|
|
593
|
+
assertDirectUploadKey(key, destination) {
|
|
594
|
+
const prefix = `${destination}/`;
|
|
595
|
+
const storedFilename = typeof key === 'string' && key.startsWith(prefix)
|
|
596
|
+
? key.slice(prefix.length)
|
|
597
|
+
: '';
|
|
598
|
+
if (!storedFilename || storedFilename.includes('/')) {
|
|
599
|
+
throw new common_1.BadRequestException('Invalid direct upload key');
|
|
600
|
+
}
|
|
601
|
+
return storedFilename;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Abre um upload multipart direto no storage. A chave segue o mesmo formato que o
|
|
605
|
+
* `uploadFromPath` grava (`file.location` = destination, `file.path` = uuid.ext do
|
|
606
|
+
* `getFilename` do provider), então o arquivo final é servido como qualquer outro.
|
|
607
|
+
*
|
|
608
|
+
* Nada vai para o banco aqui: o estado entre as chamadas fica com quem chamou (token
|
|
609
|
+
* assinado, ver `signDirectUploadToken`) e a linha `file` só nasce no complete.
|
|
610
|
+
* Tamanho da parte com a regra do Studio: no mínimo 16 MiB e no máximo 9000 partes.
|
|
611
|
+
*/
|
|
612
|
+
async startDirectUpload(destination, info) {
|
|
613
|
+
var _a, _b;
|
|
614
|
+
if (!destination) {
|
|
615
|
+
throw new common_1.BadRequestException(`No destination provided`);
|
|
616
|
+
}
|
|
617
|
+
const size = Number(info === null || info === void 0 ? void 0 : info.size);
|
|
618
|
+
if (!Number.isInteger(size) || size <= 0) {
|
|
619
|
+
throw new common_1.BadRequestException(`No size provided`);
|
|
620
|
+
}
|
|
621
|
+
// Acima disso o tamanho não cabe exato num number, e a linha não teria como ser lida.
|
|
622
|
+
if (size > FILE_SIZE_COLUMN_MAX) {
|
|
623
|
+
throw new common_1.BadRequestException(`File too large: ${size} bytes`);
|
|
624
|
+
}
|
|
625
|
+
const mimetype = String((_a = info === null || info === void 0 ? void 0 : info.mimetype) !== null && _a !== void 0 ? _a : '').trim();
|
|
626
|
+
if (!(info === null || info === void 0 ? void 0 : info.skipMimeValidation) && !(await this.acceptMimetypes(mimetype))) {
|
|
627
|
+
throw new common_1.BadRequestException(`Invalid file type: ${mimetype}`);
|
|
628
|
+
}
|
|
629
|
+
if (!(info === null || info === void 0 ? void 0 : info.skipSizeValidation) && !(await this.maxFileSize(size))) {
|
|
630
|
+
throw new common_1.BadRequestException(`File too large: ${size} bytes`);
|
|
631
|
+
}
|
|
632
|
+
const originalname = this.normalizeUploadedFilename(String((_b = info === null || info === void 0 ? void 0 : info.originalname) !== null && _b !== void 0 ? _b : '').trim() || 'file');
|
|
633
|
+
const provider = await this.getDirectUploadProvider();
|
|
634
|
+
const key = `${destination}/${provider.getFilename(originalname)}`;
|
|
635
|
+
const uploadId = await provider.createMultipartUpload(key, mimetype || 'application/octet-stream');
|
|
636
|
+
const partSize = Math.max(exports.DIRECT_UPLOAD_MIN_PART_BYTES, Math.ceil(size / exports.DIRECT_UPLOAD_MAX_PARTS));
|
|
637
|
+
return {
|
|
638
|
+
key,
|
|
639
|
+
uploadId,
|
|
640
|
+
partSize,
|
|
641
|
+
partCount: Math.max(1, Math.ceil(size / partSize)),
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
/** URLs assinadas de PUT para as partes pedidas (1-based, sem repetir). */
|
|
645
|
+
async signDirectUploadParts(key, uploadId, partNumbers, ttlSeconds = exports.DIRECT_UPLOAD_DEFAULT_TTL_SECONDS) {
|
|
646
|
+
if (!key || !uploadId) {
|
|
647
|
+
throw new common_1.BadRequestException('Invalid direct upload');
|
|
648
|
+
}
|
|
649
|
+
const numbers = Array.isArray(partNumbers) ? partNumbers.map(Number) : [];
|
|
650
|
+
if (numbers.length === 0 ||
|
|
651
|
+
numbers.some((n) => !Number.isInteger(n) || n < 1 || n > exports.DIRECT_UPLOAD_MAX_PARTS)) {
|
|
652
|
+
throw new common_1.BadRequestException('Invalid part numbers');
|
|
653
|
+
}
|
|
654
|
+
const provider = await this.getDirectUploadProvider();
|
|
655
|
+
return provider.signUploadParts(key, uploadId, [...new Set(numbers)], Math.max(60, Math.floor(ttlSeconds)));
|
|
656
|
+
}
|
|
657
|
+
/** Aborta o multipart (as partes já enviadas somem do bucket). Upload já fechado não é erro. */
|
|
658
|
+
async abortDirectUpload(key, uploadId) {
|
|
659
|
+
var _a;
|
|
660
|
+
if (!key || !uploadId) {
|
|
661
|
+
throw new common_1.BadRequestException('Invalid direct upload');
|
|
662
|
+
}
|
|
663
|
+
const provider = await this.getDirectUploadProvider();
|
|
664
|
+
try {
|
|
665
|
+
await provider.abortMultipartUpload(key, uploadId);
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
const status = (_a = error === null || error === void 0 ? void 0 : error.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode;
|
|
669
|
+
if ((error === null || error === void 0 ? void 0 : error.name) === 'NoSuchUpload' || (error === null || error === void 0 ? void 0 : error.Code) === 'NoSuchUpload' || status === 404) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
throw error;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Fecha o multipart e registra o arquivo. Confere o que o storage gravou antes de criar
|
|
677
|
+
* a linha — tamanho pelo HeadObject (tem que bater com o declarado no start), teto
|
|
678
|
+
* `storage-max-size`, allowlist `storage-accept-mimetype` e, quando pedido, os primeiros
|
|
679
|
+
* bytes (`verifyHead`) — e apaga o objeto quando algo não confere: um upload recusado
|
|
680
|
+
* não deixa blob órfão no bucket.
|
|
681
|
+
*/
|
|
682
|
+
async completeDirectUpload(input) {
|
|
683
|
+
var _a, _b, _c, _d, _e;
|
|
684
|
+
const { key, uploadId, destination } = input !== null && input !== void 0 ? input : {};
|
|
685
|
+
if (!destination) {
|
|
686
|
+
throw new common_1.BadRequestException(`No destination provided`);
|
|
687
|
+
}
|
|
688
|
+
if (!uploadId) {
|
|
689
|
+
throw new common_1.BadRequestException('Invalid direct upload');
|
|
690
|
+
}
|
|
691
|
+
const storedFilename = this.assertDirectUploadKey(key, destination);
|
|
692
|
+
const parts = Array.isArray(input.parts) ? input.parts : [];
|
|
693
|
+
if (parts.length === 0 ||
|
|
694
|
+
parts.some((part) => !Number.isInteger(Number(part === null || part === void 0 ? void 0 : part.partNumber)) ||
|
|
695
|
+
Number(part.partNumber) < 1 ||
|
|
696
|
+
typeof (part === null || part === void 0 ? void 0 : part.etag) !== 'string' ||
|
|
697
|
+
!part.etag.trim())) {
|
|
698
|
+
throw new common_1.BadRequestException('Invalid upload parts');
|
|
699
|
+
}
|
|
700
|
+
const provider = await this.getDirectUploadProvider();
|
|
701
|
+
try {
|
|
702
|
+
await provider.completeMultipartUpload(key, uploadId, parts.map((part) => ({ partNumber: Number(part.partNumber), etag: part.etag.trim() })));
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
const status = (_a = error === null || error === void 0 ? void 0 : error.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode;
|
|
706
|
+
if (typeof status === 'number' && status >= 400 && status < 500) {
|
|
707
|
+
// Parte faltando, ETag errado, parte pequena demais: o multipart não tem conserto.
|
|
708
|
+
await this.abortDirectUpload(key, uploadId).catch(() => undefined);
|
|
709
|
+
throw new common_1.BadRequestException('The uploaded parts could not be assembled');
|
|
710
|
+
}
|
|
711
|
+
throw error;
|
|
712
|
+
}
|
|
713
|
+
const discard = async () => {
|
|
714
|
+
await provider.delete(key).catch((error) => this.logger.warn(`Failed to delete rejected direct upload ${key}: ${error === null || error === void 0 ? void 0 : error.message}`));
|
|
715
|
+
};
|
|
716
|
+
const declaredSize = Number(input.size);
|
|
717
|
+
const storedSize = this.getFileSizeFromMetadata(await provider.metaData(key));
|
|
718
|
+
if (storedSize === undefined || storedSize !== declaredSize) {
|
|
719
|
+
await discard();
|
|
720
|
+
throw new common_1.BadRequestException(`Stored size (${storedSize !== null && storedSize !== void 0 ? storedSize : 'unknown'} bytes) differs from the declared size (${declaredSize} bytes)`);
|
|
721
|
+
}
|
|
722
|
+
if (storedSize > FILE_SIZE_COLUMN_MAX ||
|
|
723
|
+
(!input.skipSizeValidation && !(await this.maxFileSize(storedSize)))) {
|
|
724
|
+
await discard();
|
|
725
|
+
throw new common_1.BadRequestException(`File too large: ${storedSize} bytes`);
|
|
726
|
+
}
|
|
727
|
+
let mimetype = String((_b = input.mimetype) !== null && _b !== void 0 ? _b : '').trim();
|
|
728
|
+
if (input.verifyHead) {
|
|
729
|
+
try {
|
|
730
|
+
const verified = await input.verifyHead.check(await provider.readHead(key, input.verifyHead.bytes));
|
|
731
|
+
if (typeof verified === 'string' && verified.trim()) {
|
|
732
|
+
mimetype = verified.trim();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
await discard();
|
|
737
|
+
throw error;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (!input.skipMimeValidation && !(await this.acceptMimetypes(mimetype))) {
|
|
741
|
+
await discard();
|
|
742
|
+
throw new common_1.BadRequestException(`Invalid file type: ${mimetype}`);
|
|
743
|
+
}
|
|
744
|
+
try {
|
|
745
|
+
return await this.prismaService.file.create({
|
|
746
|
+
data: {
|
|
747
|
+
filename: this.normalizeUploadedFilename(String((_c = input.originalname) !== null && _c !== void 0 ? _c : '').trim() || storedFilename),
|
|
748
|
+
path: storedFilename,
|
|
749
|
+
provider_id: this.providerId,
|
|
750
|
+
location: destination,
|
|
751
|
+
mimetype_id: await this.getMimeType(mimetype),
|
|
752
|
+
size: storedSize,
|
|
753
|
+
is_private: (_d = input.isPrivate) !== null && _d !== void 0 ? _d : false,
|
|
754
|
+
created_by_user_id: (_e = input.createdByUserId) !== null && _e !== void 0 ? _e : null,
|
|
755
|
+
},
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
await discard();
|
|
760
|
+
throw error;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Token opaco que carrega o estado de um upload direto entre as chamadas (sem tabela):
|
|
765
|
+
* JWT HS256 com o JWT_SECRET da API e o tipo `file-direct-upload`, como o token do
|
|
766
|
+
* `file-upload-ticket`. Quem chama põe nas claims o que precisa amarrar (marca, usuário).
|
|
767
|
+
*/
|
|
768
|
+
signDirectUploadToken(claims, ttlSeconds = exports.DIRECT_UPLOAD_DEFAULT_TTL_SECONDS) {
|
|
769
|
+
return this.jwtService.sign(Object.assign(Object.assign({}, claims), { t: DIRECT_UPLOAD_TOKEN_TYPE }), {
|
|
770
|
+
secret: process.env.JWT_SECRET,
|
|
771
|
+
expiresIn: `${Math.max(1, Math.floor(ttlSeconds))}s`,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
/** Claims do token de upload direto; assinatura inválida, expirado ou de outro tipo = 400. */
|
|
775
|
+
readDirectUploadToken(token) {
|
|
776
|
+
let payload;
|
|
777
|
+
try {
|
|
778
|
+
payload = this.jwtService.verify(String(token !== null && token !== void 0 ? token : ''), { secret: process.env.JWT_SECRET });
|
|
779
|
+
}
|
|
780
|
+
catch (_a) {
|
|
781
|
+
throw new common_1.BadRequestException('Invalid or expired upload token');
|
|
782
|
+
}
|
|
783
|
+
if ((payload === null || payload === void 0 ? void 0 : payload.t) !== DIRECT_UPLOAD_TOKEN_TYPE) {
|
|
784
|
+
throw new common_1.BadRequestException('Token is not a direct upload token');
|
|
785
|
+
}
|
|
786
|
+
return payload;
|
|
787
|
+
}
|
|
533
788
|
/**
|
|
534
789
|
* Lista as imagens armazenadas que valem uma passada por `reoptimizeFile`, para a
|
|
535
790
|
* operação em massa de reprocessamento do acervo.
|
|
@@ -591,7 +846,7 @@ let FileService = FileService_1 = class FileService {
|
|
|
591
846
|
var _a;
|
|
592
847
|
const { file, buffer } = await this.getBuffer(fileId);
|
|
593
848
|
const currentMimetype = file.file_mimetype.name;
|
|
594
|
-
const oldSize = (_a = file.size) !== null && _a !== void 0 ? _a : buffer.length;
|
|
849
|
+
const oldSize = Number((_a = file.size) !== null && _a !== void 0 ? _a : buffer.length);
|
|
595
850
|
if (!currentMimetype.startsWith('image/')) {
|
|
596
851
|
return { changed: false, oldSize, newSize: oldSize, mimetypeChanged: false };
|
|
597
852
|
}
|
|
@@ -1454,6 +1709,21 @@ let FileService = FileService_1 = class FileService {
|
|
|
1454
1709
|
expiresIn: (_a = params.expiresInSeconds) !== null && _a !== void 0 ? _a : 900,
|
|
1455
1710
|
});
|
|
1456
1711
|
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Apaga um objeto de bucket avulso, com as credenciais de quem o configurou.
|
|
1714
|
+
*
|
|
1715
|
+
* Mesmo motivo do `headS3Object` e do `signS3Object`: o bucket e de outra
|
|
1716
|
+
* library (o Studio guarda os brutos no perfil de armazenamento dele), e nao o
|
|
1717
|
+
* destino ativo do core. O S3 responde 204 tambem para key inexistente, entao
|
|
1718
|
+
* apagar duas vezes nao e erro.
|
|
1719
|
+
*/
|
|
1720
|
+
async deleteS3Object(params) {
|
|
1721
|
+
const s3 = new client_s3_1.S3Client({
|
|
1722
|
+
region: params.region,
|
|
1723
|
+
credentials: Object.assign({ accessKeyId: params.accessKeyId, secretAccessKey: params.secretAccessKey }, (params.sessionToken ? { sessionToken: params.sessionToken } : {})),
|
|
1724
|
+
});
|
|
1725
|
+
await s3.send(new client_s3_1.DeleteObjectCommand({ Bucket: params.bucket, Key: params.key }));
|
|
1726
|
+
}
|
|
1457
1727
|
async headS3Bucket(params) {
|
|
1458
1728
|
const s3 = new client_s3_1.S3Client({
|
|
1459
1729
|
region: params.region,
|