Ouvir e listar áudios do exame — API
Cobre a consulta dos áudios de ditado já anexados a um exame: a listagem paginada (para exibir a lista de gravações, mais recente primeiro) e a busca de um áudio específico por id (para tocar um item isolado). As duas rotas exigem a mesma permissão e devolvem, para cada áudio, um link temporário de download.
Funcionamento
Listar (GET /exams/{examId}/audios): autentica o ator e verifica a permissão
exam:list-audio no escopo do exame; valida page/limit; busca no banco apenas os áudios ativos
(não excluídos) daquele exame, ordenados do mais recente para o mais antigo; para cada um, gera um
link de download pré-assinado e devolve a lista.
Buscar por id (GET /exams/{examId}/audios/{audioId}): mesma permissão da listagem; busca o
áudio pelo audioId e confirma que ele pertence ao examId da rota — um audioId que existe mas
pertence a outro exame é tratado como não encontrado, sem revelar que o registro existe em outro
lugar; gera o link de download e devolve o item.
Endpoints
| Método | Rota | Descrição |
|---|---|---|
| GET | /v1/exams/{examId}/audios | Lista os áudios ativos do exame, paginado |
| GET | /v1/exams/{examId}/audios/{audioId} | Busca um áudio específico do exame |
Versão: v1
Swagger:
Rota (Dev): http://localhost:3000/v1/exams/{examId}/audios
Lógica de decisão das duas rotas (validações e permissão → desfechos):
Permissões
| Rota | Guards | Permissão exigida |
|---|---|---|
GET /exams/{examId}/audios | JwtAuthenticationGuard, AuthorizationGuard | exam:list-audio (PermissionName.EXAM_LIST_AUDIO), escopo resource sobre o exame (enforcement: rls) |
GET /exams/{examId}/audios/{audioId} | JwtAuthenticationGuard, AuthorizationGuard | exam:list-audio (mesma permissão da listagem — não existe uma permissão separada para "ver um áudio") |
Sem token válido → 401 UNAUTHENTICATED. Sem a permissão no escopo do exame → 403 FORBIDDEN_ACTION. Um exame fora do escopo do ator (oculto por RLS) resulta em lista vazia (na
listagem) ou 404 (na busca por id) — não em 403.
Headers
| Header | Obrigatório | Descrição |
|---|---|---|
Authorization | Sim | Bearer <access_token> |
Path parameters
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
examId | uuid | Sim | Identificador do exame (nas duas rotas) |
audioId | uuid | Sim (só na busca por id) | Identificador do áudio |
Query parameters
| Nome | Tipo | Obrigatório | Default | Descrição |
|---|---|---|---|---|
page | inteiro | Não | 1 | Página da listagem (mínimo 1) |
limit | inteiro | Não | 20 | Itens por página (mínimo 1, máximo 100) |
page/limit só se aplicam à rota de listagem.
Body
Nenhuma das duas rotas recebe corpo.
Response
200 — listagem:
json{"data": [{"id": "0199....-uuid","examId": "0198....-uuid","unitId": "0197....-uuid","audioName": "ditado-torax.webm","path": "tenants/.../units/.../diagnosis/exam-audios/0199....audio","link": "https://storage.exemplo.com/.../0199....audio?X-Amz-Expires=300&...","createdAt": "2026-09-24T12:00:00.000Z"}]}
200 — busca por id:
json{"data": {"id": "0199....-uuid","examId": "0198....-uuid","unitId": "0197....-uuid","audioName": "ditado-torax.webm","path": "tenants/.../units/.../diagnosis/exam-audios/0199....audio","link": "https://storage.exemplo.com/.../0199....audio?X-Amz-Expires=300&...","createdAt": "2026-09-24T12:00:00.000Z"}}
Mapa de campos
| Campo | Tipo | Valores possíveis | Default |
|---|---|---|---|
id | uuid | identificador do áudio | — |
examId | uuid | exame ao qual o áudio pertence | — |
unitId | uuid | unidade proprietária do exame no momento da gravação | — |
audioName | string | null | nome informado no upload ou nome original do arquivo; null se nenhum dos dois estava disponível | null |
path | string | null | caminho interno no storage (tenants/{tenantId}/units/{unitId}/diagnosis/exam-audios/{uuid}.audio) | null |
link | string (uri) | null | URL de download pré-assinada, válida por 300 segundos a partir da resposta; recalculada a cada consulta, nunca é armazenada | null |
createdAt | data ISO-8601 | data/hora da gravação | — |
Erros
| Classe de erro | errorCode | Status | Quando ocorre |
|---|---|---|---|
UnauthenticatedError | UNAUTHENTICATED | 401 | token ausente, inválido ou expirado |
ForbiddenAction | FORBIDDEN_ACTION | 403 | ator sem exam:list-audio no escopo do exame |
| (validação de payload) | BAD_REQUEST | 400 | examId/audioId não são uuid válidos, ou page/limit fora da faixa aceita |
DiagnosisExamAudioNotFoundError | EXAM_AUDIO_NOT_FOUND | 404 | (só na busca por id) áudio inexistente, excluído, sem path persistido, ou pertencente a outro examId |
Regras de negócio
| ID | Regra | Comportamento esperado |
|---|---|---|
| RN-01 | Listagem só devolve áudios ativos | registros com exclusão lógica (deletedAt preenchido) nunca aparecem — o filtro é automático no find/findOne do TypeORM sobre uma entidade com @DeleteDateColumn |
| RN-02 | Ordenação é sempre do mais recente para o mais antigo | order: { createdAt: 'DESC' }, sem opção de inverter |
| RN-03 | Paginação tem limites fixos | page mínimo 1 (default 1); limit entre 1 e 100 (default 20) |
| RN-04 | Busca por id valida o vínculo com o exame da rota | um audioId de outro exame devolve 404 EXAM_AUDIO_NOT_FOUND, igual a um id inexistente — não distingue os dois casos na resposta |
| RN-05 | O link de download é gerado na hora, não persistido | cada chamada gera uma nova URL pré-assinada (300 segundos de validade); duas consultas seguidas ao mesmo áudio devolvem links diferentes |
Compliance
| Órgão / norma | Exigência | Como a rota atende |
|---|---|---|
| LGPD | acesso ao dado de voz restrito e com expiração | a rota exige permissão específica no exame; o link devolvido expira em 300s, não é um link público permanente |
| HIPAA | controle de acesso baseado em papel | permissão exam:list-audio avaliada por exame, mais RLS no banco |
| ANVISA (indireto) | integridade do conteúdo consultado | a listagem não permite alterar o áudio; a exclusão lógica preserva o registro em vez de apagá-lo silenciosamente |
A confirmar — responsável: time de Diagnosis; data: 24/09/2026. Não foi confirmado neste
levantamento se a reprodução do áudio (o acesso efetivo à URL pré-assinada) gera um evento de
auditoria próprio — o código do módulo audita a gravação e a exclusão (diagnosis.exam.audio-added
/ -removed), mas não foi localizado um evento equivalente para a leitura/reprodução.
Variáveis de ambiente
Nenhuma variável de ambiente é específica destas rotas. A validade do link (PRESIGNED_AUDIO_LINK_EXPIRY_SECONDS,
300 segundos) é uma constante compartilhada com a rota de gravação — ver
Gravar áudio.
Tempo médio de resposta
A confirmar — responsável: time de Diagnosis; data: 24/09/2026. Não há medição publicada; não foi
executado neste levantamento.
Requisitos não funcionais
| Requisito | Definição |
|---|---|
| Idempotência | Sim — as duas rotas são leituras, sem efeito colateral no banco (a geração do link pré-assinado não grava nada) |
| Paginação | Sim, só na listagem (page/limit, ver acima) |
| Rate limit | Não há @Throttle nestas rotas |
| Cache | Não |
| Auditoria | Não confirmada para a leitura (ver ressalva em Compliance) |
Relacionado
- 🖥️ Tela:
A confirmar — responsável: time de frontend; data: 24/09/2026.(a pastadocs/portal2/interfaceainda não tem página de áudio nesta branch; este levantamento cobriu apenas o backend) - 📂 Módulo: Áudio
- ⏮️ Etapa anterior: Gravar áudio
- ▶️ Próximo passo: Excluir áudio