@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.
Files changed (274) hide show
  1. package/dist/access-log/access-log.interceptor.d.ts +10 -0
  2. package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
  3. package/dist/access-log/access-log.interceptor.js +15 -1
  4. package/dist/access-log/access-log.interceptor.js.map +1 -1
  5. package/dist/ai/ai.controller.d.ts +2 -0
  6. package/dist/ai/ai.controller.d.ts.map +1 -1
  7. package/dist/ai/ai.service.d.ts +86 -2
  8. package/dist/ai/ai.service.d.ts.map +1 -1
  9. package/dist/ai/ai.service.js +304 -23
  10. package/dist/ai/ai.service.js.map +1 -1
  11. package/dist/ai-instruction/ai-prompt-resolver.service.d.ts +12 -0
  12. package/dist/ai-instruction/ai-prompt-resolver.service.d.ts.map +1 -1
  13. package/dist/ai-instruction/ai-prompt-resolver.service.js +26 -0
  14. package/dist/ai-instruction/ai-prompt-resolver.service.js.map +1 -1
  15. package/dist/core.module.d.ts.map +1 -1
  16. package/dist/core.module.js +3 -0
  17. package/dist/core.module.js.map +1 -1
  18. package/dist/document-kit/document-kit.module.d.ts +3 -0
  19. package/dist/document-kit/document-kit.module.d.ts.map +1 -0
  20. package/dist/document-kit/document-kit.module.js +24 -0
  21. package/dist/document-kit/document-kit.module.js.map +1 -0
  22. package/dist/document-kit/document-kit.registry.d.ts +55 -0
  23. package/dist/document-kit/document-kit.registry.d.ts.map +1 -0
  24. package/dist/document-kit/document-kit.registry.js +91 -0
  25. package/dist/document-kit/document-kit.registry.js.map +1 -0
  26. package/dist/document-kit/document-kit.service.d.ts +49 -0
  27. package/dist/document-kit/document-kit.service.d.ts.map +1 -0
  28. package/dist/document-kit/document-kit.service.js +135 -0
  29. package/dist/document-kit/document-kit.service.js.map +1 -0
  30. package/dist/file/file-upload-ticket.service.d.ts +1 -1
  31. package/dist/file/file.controller.d.ts +3 -3
  32. package/dist/file/file.service.d.ts +136 -13
  33. package/dist/file/file.service.d.ts.map +1 -1
  34. package/dist/file/file.service.js +297 -27
  35. package/dist/file/file.service.js.map +1 -1
  36. package/dist/file/mcp-file-source.schema.d.ts.map +1 -1
  37. package/dist/file/mcp-file-source.schema.js +8 -5
  38. package/dist/file/mcp-file-source.schema.js.map +1 -1
  39. package/dist/file/provider/abstract.provider.d.ts +33 -0
  40. package/dist/file/provider/abstract.provider.d.ts.map +1 -1
  41. package/dist/file/provider/abstract.provider.js +9 -0
  42. package/dist/file/provider/abstract.provider.js.map +1 -1
  43. package/dist/file/provider/s3-multipart.util.d.ts +17 -0
  44. package/dist/file/provider/s3-multipart.util.d.ts.map +1 -0
  45. package/dist/file/provider/s3-multipart.util.js +88 -0
  46. package/dist/file/provider/s3-multipart.util.js.map +1 -0
  47. package/dist/file/provider/s3.provider.d.ts +9 -1
  48. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  49. package/dist/file/provider/s3.provider.js +16 -0
  50. package/dist/file/provider/s3.provider.js.map +1 -1
  51. package/dist/file/provider/spaces.provider.d.ts +10 -1
  52. package/dist/file/provider/spaces.provider.d.ts.map +1 -1
  53. package/dist/file/provider/spaces.provider.js +17 -0
  54. package/dist/file/provider/spaces.provider.js.map +1 -1
  55. package/dist/index.d.ts +5 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +5 -0
  58. package/dist/index.js.map +1 -1
  59. package/dist/integration-profile/integration-profile.controller.d.ts +1 -0
  60. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
  61. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
  62. package/dist/integration-profile/integration-profile.secrets.js +26 -0
  63. package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
  64. package/dist/integration-profile/integration-profile.service.d.ts +1 -0
  65. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
  66. package/dist/integration-profile/integration-profile.service.js +3 -3
  67. package/dist/integration-profile/integration-profile.service.js.map +1 -1
  68. package/dist/integration-profile/testers/ai.testers.d.ts.map +1 -1
  69. package/dist/integration-profile/testers/ai.testers.js +46 -8
  70. package/dist/integration-profile/testers/ai.testers.js.map +1 -1
  71. package/dist/integration-profile/testers/http-probe.d.ts.map +1 -1
  72. package/dist/integration-profile/testers/http-probe.js +5 -3
  73. package/dist/integration-profile/testers/http-probe.js.map +1 -1
  74. package/dist/integration-profile/testers/integration-tester.types.d.ts +5 -0
  75. package/dist/integration-profile/testers/integration-tester.types.d.ts.map +1 -1
  76. package/dist/mail/mail.service.d.ts.map +1 -1
  77. package/dist/mail/mail.service.js +16 -1
  78. package/dist/mail/mail.service.js.map +1 -1
  79. package/dist/mcp/mcp-server.service.d.ts.map +1 -1
  80. package/dist/mcp/mcp-server.service.js +10 -1
  81. package/dist/mcp/mcp-server.service.js.map +1 -1
  82. package/dist/mcp/mcp.module.d.ts.map +1 -1
  83. package/dist/mcp/mcp.module.js +2 -0
  84. package/dist/mcp/mcp.module.js.map +1 -1
  85. package/dist/mcp/tools/core-access-log.mcp-tools.d.ts +17 -0
  86. package/dist/mcp/tools/core-access-log.mcp-tools.d.ts.map +1 -0
  87. package/dist/mcp/tools/core-access-log.mcp-tools.js +74 -0
  88. package/dist/mcp/tools/core-access-log.mcp-tools.js.map +1 -0
  89. package/dist/mcp/tools/core-files.mcp-tools.js +4 -4
  90. package/dist/mcp/tools/core-files.mcp-tools.js.map +1 -1
  91. package/dist/meeting/meeting-provider.registry.d.ts +72 -0
  92. package/dist/meeting/meeting-provider.registry.d.ts.map +1 -1
  93. package/dist/meeting/meeting-provider.registry.js.map +1 -1
  94. package/dist/oauth/oauth-token-broker.service.d.ts +50 -1
  95. package/dist/oauth/oauth-token-broker.service.d.ts.map +1 -1
  96. package/dist/oauth/oauth-token-broker.service.js +105 -3
  97. package/dist/oauth/oauth-token-broker.service.js.map +1 -1
  98. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +6 -0
  99. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
  100. package/dist/oauth/providers/microsoft-entra-id.provider.js +30 -4
  101. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  102. package/dist/pdf/document/charts/chart-bar.d.ts +24 -0
  103. package/dist/pdf/document/charts/chart-bar.d.ts.map +1 -0
  104. package/dist/pdf/document/charts/chart-bar.js +117 -0
  105. package/dist/pdf/document/charts/chart-bar.js.map +1 -0
  106. package/dist/pdf/document/charts/chart-donut.d.ts +16 -0
  107. package/dist/pdf/document/charts/chart-donut.d.ts.map +1 -0
  108. package/dist/pdf/document/charts/chart-donut.js +89 -0
  109. package/dist/pdf/document/charts/chart-donut.js.map +1 -0
  110. package/dist/pdf/document/charts/chart-gauge.d.ts +15 -0
  111. package/dist/pdf/document/charts/chart-gauge.d.ts.map +1 -0
  112. package/dist/pdf/document/charts/chart-gauge.js +60 -0
  113. package/dist/pdf/document/charts/chart-gauge.js.map +1 -0
  114. package/dist/pdf/document/charts/chart-line.d.ts +29 -0
  115. package/dist/pdf/document/charts/chart-line.d.ts.map +1 -0
  116. package/dist/pdf/document/charts/chart-line.js +89 -0
  117. package/dist/pdf/document/charts/chart-line.js.map +1 -0
  118. package/dist/pdf/document/charts/chart-primitives.d.ts +64 -0
  119. package/dist/pdf/document/charts/chart-primitives.d.ts.map +1 -0
  120. package/dist/pdf/document/charts/chart-primitives.js +110 -0
  121. package/dist/pdf/document/charts/chart-primitives.js.map +1 -0
  122. package/dist/pdf/document/charts/chart-radar.d.ts +13 -0
  123. package/dist/pdf/document/charts/chart-radar.d.ts.map +1 -0
  124. package/dist/pdf/document/charts/chart-radar.js +83 -0
  125. package/dist/pdf/document/charts/chart-radar.js.map +1 -0
  126. package/dist/pdf/document/charts/chart-scale.d.ts +39 -0
  127. package/dist/pdf/document/charts/chart-scale.d.ts.map +1 -0
  128. package/dist/pdf/document/charts/chart-scale.js +118 -0
  129. package/dist/pdf/document/charts/chart-scale.js.map +1 -0
  130. package/dist/pdf/document/charts/index.d.ts +8 -0
  131. package/dist/pdf/document/charts/index.d.ts.map +1 -0
  132. package/dist/pdf/document/charts/index.js +24 -0
  133. package/dist/pdf/document/charts/index.js.map +1 -0
  134. package/dist/pdf/document/index.d.ts +8 -0
  135. package/dist/pdf/document/index.d.ts.map +1 -0
  136. package/dist/pdf/document/index.js +24 -0
  137. package/dist/pdf/document/index.js.map +1 -0
  138. package/dist/pdf/document/pdf-document.chrome.d.ts +20 -0
  139. package/dist/pdf/document/pdf-document.chrome.d.ts.map +1 -0
  140. package/dist/pdf/document/pdf-document.chrome.js +36 -0
  141. package/dist/pdf/document/pdf-document.chrome.js.map +1 -0
  142. package/dist/pdf/document/pdf-document.css.d.ts +20 -0
  143. package/dist/pdf/document/pdf-document.css.d.ts.map +1 -0
  144. package/dist/pdf/document/pdf-document.css.js +327 -0
  145. package/dist/pdf/document/pdf-document.css.js.map +1 -0
  146. package/dist/pdf/document/pdf-document.renderer.d.ts +10 -0
  147. package/dist/pdf/document/pdf-document.renderer.d.ts.map +1 -0
  148. package/dist/pdf/document/pdf-document.renderer.js +226 -0
  149. package/dist/pdf/document/pdf-document.renderer.js.map +1 -0
  150. package/dist/pdf/document/pdf-document.service.d.ts +27 -0
  151. package/dist/pdf/document/pdf-document.service.d.ts.map +1 -0
  152. package/dist/pdf/document/pdf-document.service.js +81 -0
  153. package/dist/pdf/document/pdf-document.service.js.map +1 -0
  154. package/dist/pdf/document/pdf-document.theme.d.ts +41 -0
  155. package/dist/pdf/document/pdf-document.theme.d.ts.map +1 -0
  156. package/dist/pdf/document/pdf-document.theme.js +105 -0
  157. package/dist/pdf/document/pdf-document.theme.js.map +1 -0
  158. package/dist/pdf/document/pdf-document.types.d.ts +164 -0
  159. package/dist/pdf/document/pdf-document.types.d.ts.map +1 -0
  160. package/dist/pdf/document/pdf-document.types.js +18 -0
  161. package/dist/pdf/document/pdf-document.types.js.map +1 -0
  162. package/dist/pdf/pdf.module.d.ts.map +1 -1
  163. package/dist/pdf/pdf.module.js +3 -2
  164. package/dist/pdf/pdf.module.js.map +1 -1
  165. package/dist/pdf/pdf.service.d.ts +75 -1
  166. package/dist/pdf/pdf.service.d.ts.map +1 -1
  167. package/dist/pdf/pdf.service.js +167 -63
  168. package/dist/pdf/pdf.service.js.map +1 -1
  169. package/dist/pdf/templates/commercial-proposal.template.d.ts +96 -0
  170. package/dist/pdf/templates/commercial-proposal.template.d.ts.map +1 -0
  171. package/dist/pdf/templates/commercial-proposal.template.js +276 -0
  172. package/dist/pdf/templates/commercial-proposal.template.js.map +1 -0
  173. package/dist/setting/favicon.controller.d.ts +26 -0
  174. package/dist/setting/favicon.controller.d.ts.map +1 -0
  175. package/dist/setting/favicon.controller.js +130 -0
  176. package/dist/setting/favicon.controller.js.map +1 -0
  177. package/dist/setting/setting.module.d.ts.map +1 -1
  178. package/dist/setting/setting.module.js +2 -1
  179. package/dist/setting/setting.module.js.map +1 -1
  180. package/dist/setting/setting.service.d.ts.map +1 -1
  181. package/dist/setting/setting.service.js +3 -0
  182. package/dist/setting/setting.service.js.map +1 -1
  183. package/dist/task/task.service.d.ts +6 -0
  184. package/dist/task/task.service.d.ts.map +1 -1
  185. package/dist/task/task.service.js +44 -13
  186. package/dist/task/task.service.js.map +1 -1
  187. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +35 -2
  188. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -1
  189. package/dist/whatsapp-sender/whatsapp-sender.service.js +107 -22
  190. package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -1
  191. package/hedhog/data/integration_type.yaml +6 -0
  192. package/hedhog/data/route.yaml +8 -0
  193. package/hedhog/data/setting_group.yaml +52 -0
  194. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.test.tsx.ejs +85 -2
  195. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +576 -491
  196. package/hedhog/frontend/app/users/simulate-access-dialog.test.tsx.ejs +29 -50
  197. package/hedhog/frontend/app/users/simulate-access-dialog.tsx.ejs +146 -50
  198. package/hedhog/frontend/messages/en.json +81 -2
  199. package/hedhog/frontend/messages/pt.json +81 -2
  200. package/hedhog/table/file.yaml +3 -1
  201. package/package.json +5 -5
  202. package/src/access-log/access-log.interceptor.spec.ts +50 -0
  203. package/src/access-log/access-log.interceptor.ts +18 -0
  204. package/src/ai/ai-attachment.fragments.spec.ts +56 -3
  205. package/src/ai/ai.service.responses.spec.ts +429 -0
  206. package/src/ai/ai.service.ts +409 -27
  207. package/src/ai-instruction/ai-prompt-resolver.service.spec.ts +44 -0
  208. package/src/ai-instruction/ai-prompt-resolver.service.ts +29 -0
  209. package/src/core.module.ts +3 -0
  210. package/src/document-kit/document-kit.module.ts +12 -0
  211. package/src/document-kit/document-kit.registry.ts +125 -0
  212. package/src/document-kit/document-kit.service.spec.ts +156 -0
  213. package/src/document-kit/document-kit.service.ts +173 -0
  214. package/src/file/file-direct-upload.spec.ts +344 -0
  215. package/src/file/file.service.spec.ts +168 -1
  216. package/src/file/file.service.ts +435 -36
  217. package/src/file/mcp-file-source.schema.ts +8 -5
  218. package/src/file/provider/abstract.provider.ts +57 -0
  219. package/src/file/provider/s3-multipart.spec.ts +123 -0
  220. package/src/file/provider/s3-multipart.util.ts +126 -0
  221. package/src/file/provider/s3.provider.ts +65 -1
  222. package/src/file/provider/spaces.provider.ts +67 -1
  223. package/src/index.ts +5 -0
  224. package/src/integration-profile/integration-profile.secrets.ts +26 -0
  225. package/src/integration-profile/integration-profile.service.ts +1 -0
  226. package/src/integration-profile/testers/ai.testers.ts +70 -5
  227. package/src/integration-profile/testers/http-probe.ts +5 -3
  228. package/src/integration-profile/testers/integration-tester.types.ts +5 -0
  229. package/src/integration-profile/testers/oauth.testers.spec.ts +13 -0
  230. package/src/integration-profile/testers/remaining.testers.spec.ts +65 -1
  231. package/src/integration-profile/testers/tester-coverage.spec.ts +12 -0
  232. package/src/mail/mail.service.spec.ts +25 -0
  233. package/src/mail/mail.service.ts +18 -1
  234. package/src/mcp/mcp-server.service.spec.ts +16 -0
  235. package/src/mcp/mcp-server.service.ts +10 -1
  236. package/src/mcp/mcp.module.ts +2 -0
  237. package/src/mcp/tools/core-access-log.mcp-tools.ts +66 -0
  238. package/src/mcp/tools/core-files.mcp-tools.ts +4 -4
  239. package/src/meeting/meeting-provider.registry.ts +80 -0
  240. package/src/oauth/oauth-token-broker.service.spec.ts +108 -0
  241. package/src/oauth/oauth-token-broker.service.ts +129 -2
  242. package/src/oauth/providers/microsoft-entra-id.provider.spec.ts +133 -0
  243. package/src/oauth/providers/microsoft-entra-id.provider.ts +29 -2
  244. package/src/pdf/document/charts/chart-bar.ts +217 -0
  245. package/src/pdf/document/charts/chart-donut.ts +163 -0
  246. package/src/pdf/document/charts/chart-gauge.ts +94 -0
  247. package/src/pdf/document/charts/chart-line.ts +159 -0
  248. package/src/pdf/document/charts/chart-primitives.ts +188 -0
  249. package/src/pdf/document/charts/chart-radar.ts +136 -0
  250. package/src/pdf/document/charts/chart-scale.spec.ts +93 -0
  251. package/src/pdf/document/charts/chart-scale.ts +141 -0
  252. package/src/pdf/document/charts/charts.spec.ts +157 -0
  253. package/src/pdf/document/charts/index.ts +7 -0
  254. package/src/pdf/document/index.ts +7 -0
  255. package/src/pdf/document/pdf-document.chrome.ts +38 -0
  256. package/src/pdf/document/pdf-document.css.ts +326 -0
  257. package/src/pdf/document/pdf-document.renderer.spec.ts +172 -0
  258. package/src/pdf/document/pdf-document.renderer.ts +317 -0
  259. package/src/pdf/document/pdf-document.service.ts +77 -0
  260. package/src/pdf/document/pdf-document.theme.ts +146 -0
  261. package/src/pdf/document/pdf-document.types.ts +149 -0
  262. package/src/pdf/pdf.module.ts +3 -2
  263. package/src/pdf/pdf.service.spec.ts +131 -0
  264. package/src/pdf/pdf.service.ts +256 -82
  265. package/src/pdf/templates/commercial-proposal.template.spec.ts +177 -0
  266. package/src/pdf/templates/commercial-proposal.template.ts +400 -0
  267. package/src/setting/favicon.controller.spec.ts +92 -0
  268. package/src/setting/favicon.controller.ts +135 -0
  269. package/src/setting/setting.module.ts +2 -1
  270. package/src/setting/setting.service.ts +3 -0
  271. package/src/task/task.service.spec.ts +77 -1
  272. package/src/task/task.service.ts +58 -14
  273. package/src/whatsapp-sender/whatsapp-sender.service.spec.ts +263 -0
  274. 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
- mapStorageNotFoundError(error, fileId) {
230
- var _a;
231
- const missingInStorage = (error === null || error === void 0 ? void 0 : error.name) === 'NoSuchKey' ||
232
- (error === null || error === void 0 ? void 0 : error.Code) === 'NoSuchKey' ||
233
- ((_a = error === null || error === void 0 ? void 0 : error.$metadata) === null || _a === void 0 ? void 0 : _a.httpStatusCode) === 404;
234
- if (missingInStorage) {
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
- try {
259
- metadata = await provider.metaData(providerPath);
260
- }
261
- catch (_b) {
262
- metadata = undefined;
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.mapStorageNotFoundError(error, fileId);
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
- const stream = await provider.readStream(providerPath, hasRange ? { start: resolvedStart, end: resolvedEnd } : undefined);
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
- const missingInStorage = (error === null || error === void 0 ? void 0 : error.name) === 'NoSuchKey' ||
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 = 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 });
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
- if (!mimetype.startsWith('image/')) {
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,