Skip to main content

Salvar, listar, editar e excluir favoritos de busca — API

Permite ao usuário salvar um conjunto de filtros da worklist com um nome ("favorito de busca"), listar os próprios favoritos, consultar um específico, renomear ou substituir seus filtros, e excluí-lo. É um recurso inteiramente privado ao usuário autenticado — não existe, hoje, compartilhamento entre usuários (ver nota na visão geral).

Funcionamento​

  1. Valida o token e a permissão EXAM_READ, sempre com escopo current-user (listar, criar) ou resource/rls (encontrar, editar, excluir um favorito específico por id).
  2. Valida o corpo (ValidationPipe de classe: whitelist + forbidNonWhitelisted + transform, além do ValidationPipe global).
  3. Para criar: verifica se já existe, para o mesmo usuário, um favorito com o mesmo nome (comparação sem diferenciar maiúsculas/minúsculas, ignorando favoritos já excluídos); se houver, recusa com 409. Os filtros são validados e normalizados por DiagnosisSavedWorklistFilter (mesmo modelo usado pela busda padrão).
  4. Para editar: busca o favorito pelo id e pelo dono (findActiveByIdAndUser); se não achar (não existe, foi excluído, ou é de outro usuário), responde 404 — nunca 403, para não revelar a um usuário que o id pertence a outra pessoa. Um corpo vazio (nem name nem filters) é rejeitado; enviar filters: null explicitamente também é rejeitado (não existe hoje a operação de "limpar filtros" — para isso, edite os filtros para um objeto vazio {}, não null). Renomear verifica conflito de nome de novo, ignorando o próprio registro.
  5. Para excluir: confirma posse do mesmo jeito, depois faz soft delete (deleted_at) — a validação de conflito de grupo mencionada no Swagger (409 LISTING_PREFERENCE_ASSOCIATION_CONFLICT) ainda não está implementada (ver visão geral): hoje a exclusão é sempre incondicional.
  6. Toda criação, edição e exclusão grava um evento de auditoria com o estado anterior/novo dos filtros.

Endpoints​

MétodoRotaDescrição
GET/v1/worklist-preferences/listingsLista os favoritos de busca do usuário autenticado
GET/v1/worklist-preferences/listings/:idBusca um favorito específico do usuário
POST/v1/worklist-preferences/listingsCria um favorito de busca
PATCH/v1/worklist-preferences/listings/:idRenomeia e/ou substitui os filtros de um favorito
DELETE/v1/worklist-preferences/listings/:idExclui (soft delete) um favorito

Versão: v1

Swagger: GET /worklist-preferences/listings · Rota (Dev): http://localhost:3000/v1/worklist-preferences/listings

Lógica de decisão de POST/PATCH (conflito de nome e validação → desfechos):

Permissões​

RotaGuardsPermissãoEscopo
GET /listings, POST /listingsJwtAuthenticationGuard, AuthorizationGuardEXAM_READ (any)current-user
GET/PATCH/DELETE /listings/:ididemEXAM_READ (any)resource (worklist-listing-preference), enforcement rls, identificador = :id

O escopo resource/rls garante que o registro consultado/alterado pertence ao ator autenticado; um id válido de outro usuário responde 404, não 403.

Headers​

HeaderObrigatórioDescrição
AuthorizationSimBearer <access_token>

Path parameters​

NomeTipoObrigatórioDescrição
iduuidSim (exceto GET/POST da coleção)Id do favorito de busca

Query parameters​

Nenhum.

Body​

POST /listings — CreateDiagnosisListingPreferenceRequest:

json
{
"name": "Urgência e emergência",
"filters": { "priorities": ["URGENT", "EMERGENCY"] }
}
CampoTipoObrigatórioValidação
namestringSim@IsNotEmpty @MaxLength(255)
filtersobjetoSimver mapa de campos abaixo (DiagnosisSavedWorklistFilterRequest)

PATCH /listings/:id — UpdateDiagnosisListingPreferenceRequest:

CampoTipoObrigatórioValidação
namestringNão@IsNotEmpty @MaxLength(120) — atenção: o limite de tamanho aqui é 120, mais baixo que o limite de 255 aceito na criação e no modelo persistido
filtersobjeto ou nullNãose presente, mesmo formato da criação; null explícito é rejeitado (400); omitir mantém os filtros atuais

Corpo precisa ter pelo menos um dos dois campos; um corpo {} é rejeitado com 400 INVALID_WORKLIST_PREFERENCE.

Mapa de campos de filters (DiagnosisSavedWorklistFilterRequest, usado por favoritos e por busca padrão):

CampoTipoValores possíveisObservação
unitIds, radiologistIds, teamUserIds, specialtyIdsstring[]uuidsdeduplicados e ordenados ao salvar
term, patientName, patientCode, patientIdentity, requestingPhysicianName, accessionNumberstring≤120 caracteres
studyDescriptionstring≤255 caracteres
insurancestring≤50 caracteres
interestingReasonstring≤30 caracteressem fonte de dados projetada — ver busca avançada
statusesenum[] (ExamStatus)—
prioritiesenum[] (ExamPriority)—
modalitiesenum[] (ExamModality)—
sourcesenum[] (ExamSource)—
aiLevelsenum[] (WorklistAiLevel)ABSENT, LOW, MEDIUM, HIGHsem fonte de dados projetada
includeUnassignedRadiologistboolean—
isCriticalFinding, isTechnicalSuspicion, isDuplicate, isOriginal, isReleased, isComplement, isPriorboolean—
priorModeenum (WorklistPriorMode)ONLY
hasAttachments, hasComments, hasKeyImages, hasAttachmentKeyImages, hasPatientAttachments, hasExamAttachments, hasAudio, slaExpiredboolean—
performedAtFrom/To, createdAtFrom/To, transferredAtFrom/Todate (ISO)—início não pode ser depois do fim
examIduuid—
patientSexenum (ExamPatientSex)—
patientBirthDatestring YYYY-MM-DD—
intervalenum (WorklistInterval)inclui ALLALL é normalizado para THIRTY_DAYS ao salvar — ver RN-02
dateModeenum (WorklistDateMode)TRANSFERRED, PREPARED

Um campo fora dessa lista (PERSISTABLE_FILTER_KEYS) é rejeitado com 400 INVALID_WORKLIST_PREFERENCE — diferente da busca avançada, que aceita campos extras e apenas os ignora (ver busca avançada).

Response​

200 — GET /listings ({ data: DiagnosisListingPreferenceResponse[] }):

json
{
"data": [
{
"id": "c4e2....",
"userId": "8f2a....",
"name": "Urgência e emergência",
"filters": { "priorities": ["EMERGENCY", "URGENT"] },
"shared": false,
"readOnly": false,
"createdAt": "2026-09-01T12:00:00.000Z",
"updatedAt": "2026-09-01T12:00:00.000Z"
}
]
}

shared e readOnly são hoje sempre false — reservados para o compartilhamento por grupo ainda não implementado (ver visão geral).

201 — POST /listings: mesmo formato de um item de data acima.

200 — PATCH /listings/:id: idem, refletindo o novo name/filters.

204 — DELETE /listings/:id: sem corpo.

Erros​

Classe de erroerrorCodeStatusQuando ocorre
(guard)UNAUTHENTICATED401token ausente, inválido ou expirado
(guard)FORBIDDEN_ACTION403falta EXAM_READ
(validação)BAD_REQUEST400corpo malformado, campo fora do whitelist
DiagnosisWorklistPreferenceInvalidErrorINVALID_WORKLIST_PREFERENCE400nome fora de 1–255 chars, filters com campo não suportado, intervalo de data invertido, corpo de PATCH vazio, ou filters: null explícito
DiagnosisListingPreferenceNotFoundErrorLISTING_PREFERENCE_NOT_FOUND404id não existe, foi excluído, ou pertence a outro usuário
DiagnosisListingPreferenceNameConflictErrorLISTING_PREFERENCE_NAME_CONFLICT409já existe favorito do mesmo usuário com esse nome (case-insensitive)

O Swagger também documenta um 409 LISTING_PREFERENCE_ASSOCIATION_CONFLICT para DELETE (vínculo de grupo impeditivo), mas o código atual sempre exclui incondicionalmente — ver visão geral. Não confirmado que esse erro seja hoje alcançável.

Regras de negócio​

IDRegraComportamento esperado
RN-01Nome é único por usuário, sem diferenciar maiúsculas/minúsculas"Urgente" e "urgente" conflitam entre si para o mesmo usuário; favoritos de outro usuário não contam
RN-02interval: ALL nunca é persistido como estásalvo como THIRTY_DAYS — o favorito nunca guarda "sem limite" como intervalo relativo
RN-03PATCH distingue omitir de limparomitir filters mantém o snapshot atual; enviar filters: null é erro; renomear sem enviar filters não toca nos filtros salvos
RN-04Exclusão é sempre soft delete incondicional hojenão há checagem de vínculo de grupo ativa (BLOQUEADO USR-09 no código)
RN-05Favorito nunca é compartilhadoshared/readOnly sempre false; não existe hoje um jeito de outro usuário enxergar este favorito
RN-06Filtros salvos aceitam mais critérios do que a busca avançada aplica hojeaiLevels, interestingReason, priorMode, dateMode, specialtyIds, includeUnassignedRadiologist e os contadores derivados podem ser salvos num favorito, mas — se o favorito for aplicado chamando GET /exams com esses mesmos parâmetros — a busca ignora vários deles (ver busca avançada)

Compliance​

Não se aplica diretamente — o próprio favorito não expõe dado clínico ou de paciente além dos critérios de busca que o usuário mesmo escolheu salvar (ex.: um nome de paciente digitado em patientName). A leitura de exames que o favorito filtra segue as regras de busca avançada.

Variáveis de ambiente​

Nenhuma encontrada para esta rota.

Tempo médio de resposta​

A confirmar — responsável: time de Diagnosis; data: 24/09/2026. Não há medição publicada.

Requisitos não funcionais​

RequisitoDefinição
IdempotênciaPUT não existe aqui; PATCH não é idempotente por natureza mas repetir a mesma chamada produz o mesmo estado final; DELETE é idempotente na prática (segunda chamada responde 404, não erro de estado)
PaginaçãoNão — GET /listings devolve a lista completa do usuário
Rate limitLimite global padrão da API (ver visão geral)
CacheNão
AuditoriaSim — diagnosis.worklist.listing-preference-created/updated/deleted, com snapshot antes/depois dos filtros

Relacionado​

  • 🖥️ Tela: A confirmar — responsável: time de frontend; data: 24/09/2026. (destino de front: search-favorites, search-favorite-details, search-favorite-edit)
  • 📂 Módulo: Busca
  • 🔎 Relacionado: Busca avançada de exames, Busca padrão