From 4c632d43bfb3125bf959cebe8e4e8f0112ddfce8 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Sat, 26 Sep 2026 13:54:16 -0300 Subject: [PATCH] =?UTF-8?q?feat(C199-large):=20grava=C3=A7=C3=B5es=20do=20?= =?UTF-8?q?acervo=20sem=20o=20teto=20de=204=20GB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O diálogo, a rota e o stream deixam de recusar arquivos acima de 4 GiB; o nome do arquivo e o `formatRecordingFileSize` continuam iguais. Acima de 2 GiB a mídia não passa mais pelo `fs.readFile` do Payload local API: a row nasce de um placeholder de 0 byte e a mesma key é sobrescrita por upload multipart direto no S3 (padrão C225-large, extraído para `utilities/privateMedia/privateMediaUpload.ts` e reusado pelo espelho de falas web, que mantém o teto de 5 GiB via `maxBytes` do chamador). O objeto é removido se a transação falhar, sem row fantasma, e o upload multipart de gravações não tem teto (`Infinity`). --- .../2026-09-26-gravacoes-sem-teto-4gb.md | 1 + .../acervo/gravacoes/enviar/route.ts | 10 -- .../recording/RecordingUploadDialog.tsx | 11 +- src/lib/recording.ts | 15 +- src/utilities/media/largeS3Upload.ts | 10 +- .../privateMedia/privateMediaUpload.ts | 95 ++++++++++++ src/utilities/recordings/recordingUpload.ts | 110 ++++++++------ src/utilities/speech/webSpeechIngest.ts | 88 ++++------- tests/int/recording.int.spec.ts | 80 +++++++++- tests/unit/largeS3Upload.unit.spec.ts | 27 ++++ tests/unit/privateMediaUpload.unit.spec.ts | 140 ++++++++++++++++++ tests/unit/recording.unit.spec.ts | 14 +- 12 files changed, 442 insertions(+), 159 deletions(-) create mode 100644 docs/changelog/2026-09-26-gravacoes-sem-teto-4gb.md create mode 100644 src/utilities/privateMedia/privateMediaUpload.ts create mode 100644 tests/unit/privateMediaUpload.unit.spec.ts diff --git a/docs/changelog/2026-09-26-gravacoes-sem-teto-4gb.md b/docs/changelog/2026-09-26-gravacoes-sem-teto-4gb.md new file mode 100644 index 00000000..5adf8481 --- /dev/null +++ b/docs/changelog/2026-09-26-gravacoes-sem-teto-4gb.md @@ -0,0 +1 @@ +**Gravações enviadas ao acervo sem o teto de 4 GB (2026-09-26):** o diálogo "Enviar gravação" recusava qualquer arquivo acima de 4 GiB (`RECORDING_MAX_BYTES`) — limite que plenárias longas encostam. O teto saiu do cliente, da rota e do stream: o corpo continua indo para o temp com backpressure, agora sem teto de produto (disco e multipart do S3 são o limite). Acima de 2 GiB a mídia deixa de passar pelo `fs.readFile` do Payload: o `recordingMedia` nasce de um placeholder de 0 byte e a mesma key é sobrescrita por upload multipart direto no S3 — padrão do C225-large, extraído para o dono `utilities/privateMedia/privateMediaUpload.ts` e agora compartilhado com o espelho de falas web (que mantém o próprio teto de 5 GiB via `maxBytes` do chamador; gravações passam `Infinity`). O nome original do arquivo sobrevive ao placeholder e o objeto é removido se a transação falhar, sem row fantasma. Sem migration e sem schema; unit (novo helper + limites do uploader) e int de gravações/falas web verdes. Operação: o `/tmp` do container precisa de headroom para o tamanho da gravação (upload + download do job de transcrição). diff --git a/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/enviar/route.ts b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/enviar/route.ts index 4eb882f0..b9b7cd26 100644 --- a/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/enviar/route.ts +++ b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/gravacoes/enviar/route.ts @@ -7,7 +7,6 @@ import { RECORDING_FILE_TYPE_MESSAGE, RECORDING_TITLE_REQUIRED_MESSAGE, recordingFileTypeAllowed, - recordingUploadTooLargeMessage, toRecordingViewModel, } from '@/lib/recording' import { @@ -49,7 +48,6 @@ const SAFE_MESSAGES = [ RECORDING_TITLE_REQUIRED_MESSAGE, RECORDING_FILE_NAME_MESSAGE, RECORDING_FILE_TYPE_MESSAGE, - recordingUploadTooLargeMessage, RECORDING_BODY_MISSING_MESSAGE, ] @@ -78,13 +76,6 @@ export const POST = async (request: Request): Promise= 0 - ? parsedLength - : null - try { const payload = await getPayload({ config }) const { id } = await receiveRecordingUpload({ @@ -92,7 +83,6 @@ export const POST = async (request: Request): Promise RECORDING_MAX_BYTES) { - setFileError(recordingTooLargeMessage(next.size)) } } @@ -285,7 +278,7 @@ export const RecordingUploadDialog = ({