Snippets pessoais — API
Textos pré-definidos ("atalhos") privados ao próprio usuário, cada um identificado por um
shortcut curto e único entre os snippets ativos do dono. Usados para inserir trechos de texto
recorrentes rapidamente (o conteúdo em si é de uso do frontend — este levantamento cobriu apenas o
contrato da API, sem confirmar onde o snippet é consumido na interface).
Funcionamento
- Cada snippet pertence a exatamente um
ownerUserId— sempre osubjectdo access token; não há rota para operar sobre snippet de outro usuário. - Criar: exige
Idempotency-Key; reenviar a mesma chave com o mesmo payload devolve o snippet já criado.shortcutdeve ser único entre os snippets ativos do mesmo dono (case-insensitive) — criar um segundo com o mesmo atalho é conflito. - Conteúdo é sempre sanitizado antes de salvar: em
PLAIN_TEXT, qualquer marcação HTML é rejeitada; emRICH_TEXT, tags e atributos perigosos (script,iframe, manipuladoreson*, protocolosjavascript:/data:etc.) são rejeitados, e só uma lista fixa de tags simples (p,br,strong,em,u,s,ul,ol,li,blockquote) sobrevive à sanitização — atributos são sempre removidos. - Substituir (
PUT) e retirar (DELETE) usam controle de versão otimista: o snippet só é modificado se a versão informada bate com a versão atual; a cada substituição a versão incrementa. - Retirar é soft-delete (
deletedAt); um snippet retirado não aparece mais emlist/find. - Um token OAuth delegado por serviço (
binding !== null) nunca acessa este recurso — só sessão de usuário final.
Endpoints
| Método | Rota | Descrição |
|---|---|---|
| POST | /v1/me/snippets | Cria um snippet pessoal |
| GET | /v1/me/snippets | Lista os snippets ativos do dono, paginado |
| GET | /v1/me/snippets/:snippetId | Lê um snippet específico |
| PUT | /v1/me/snippets/:snippetId | Substitui um snippet (versão obrigatória) |
| DELETE | /v1/me/snippets/:snippetId | Retira (soft-delete) um snippet |
Versão: v1
Swagger: Identity — Personal snippets · Rota (Dev): http://localhost:3000/v1/me/snippets
Permissões
Rotas privadas ao próprio usuário: exigem apenas um access token de usuário final (não
delegado). Não há permissão nomeada — o escopo é sempre o subject do token.
| Rota | Guards | Acesso |
|---|---|---|
| Todas deste grupo | IdentityOAuthAccessTokenGuard, IdentitySnippetUserTokenGuard | usuário autenticado, apenas sobre os próprios snippets; token delegado por serviço é recusado |
Headers
| Header | Obrigatório | Descrição |
|---|---|---|
Authorization | Sim | Bearer <access_token> de usuário final |
Idempotency-Key | Sim (POST) | única por dono e payload |
If-Match | Sim (DELETE) | versão atual do snippet |
Path parameters
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
snippetId | uuid | Sim (exceto POST/GET lista) | Identificador do snippet |
Query parameters (GET lista)
| Nome | Tipo | Obrigatório | Default | Descrição |
|---|---|---|---|---|
page | int ≥ 1 | Não | 1 | página |
limit | int (1–200) | Não | 100 | tamanho da página |
Body
POST / PUT — IdentitySnippetMaintainRequest:
json{"shortcut": "assinatura-padrao","title": "Assinatura padrão","contentFormat": "PLAIN_TEXT","content": "Atenciosamente,\nDra. Ana Silva","version": 2}
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
shortcut | string | Sim | 1–80 caracteres; apenas letras/números/./_/- |
title | string | Sim | 1–200 caracteres |
contentFormat | PLAIN_TEXT | RICH_TEXT | Sim | enum |
content | string | Sim | 1–50.000 caracteres; sanitizado conforme contentFormat |
version | int ≥ 1 | Sim só no PUT | deve bater com a versão atual do snippet |
Response
201/200 — IdentitySnippetResponse:
json{"data": {"id": "...", "shortcut": "assinatura-padrao", "title": "Assinatura padrão","contentFormat": "PLAIN_TEXT", "content": "Atenciosamente,\nDra. Ana Silva","version": 1, "createdAt": "2026-08-26T12:00:00.000Z", "updatedAt": "2026-08-26T12:00:00.000Z"}}
200 — lista: envelope paginado (data[] + meta.page/limit/total/totalPages).
204 — DELETE: sem corpo.
Erros
| Classe de erro | errorCode | Status | Quando ocorre |
|---|---|---|---|
| — (guard) | DELEGATED_USER_REQUIRED | 401 | token delegado por serviço, sem sessão de usuário final |
| ValidationError | IDEMPOTENCY_KEY_REQUIRED | 400 | header ausente no POST |
| ValidationError | IDENTITY_SNIPPET_INVALID_TITLE | 400 | título vazio ou maior que 200 caracteres |
IdentitySnippetInvalidShortcutError | IDENTITY_SNIPPET_INVALID_SHORTCUT | 422 | shortcut fora do padrão permitido |
IdentitySnippetUnsafeContentError | IDENTITY_SNIPPET_UNSAFE_CONTENT | 422 | conteúdo com marcação não permitida, protocolo perigoso, ou tamanho/controle inválido |
IdentitySnippetIdempotencyConflictError | IDENTITY_SNIPPET_IDEMPOTENCY_CONFLICT | 409 | mesma Idempotency-Key com payload diferente |
IdentitySnippetDuplicateShortcutError | IDENTITY_SNIPPET_DUPLICATE_SHORTCUT | 409 | shortcut já usado por outro snippet ativo do mesmo dono |
IdentitySnippetNotFoundError | IDENTITY_SNIPPET_NOT_FOUND | 404 | snippet inexistente, de outro dono, ou já retirado |
IdentitySnippetVersionConflictError | IDENTITY_SNIPPET_VERSION_CONFLICT | 409 | version/If-Match não bate com a versão atual |
| ValidationError | VERSION_REQUIRED | 400 | PUT sem version, ou DELETE sem If-Match válido |
| — | IDENTITY_INVALID_OAUTH_ACCESS_TOKEN | 401 | token ausente, inválido, expirado ou revogado |
Regras de negócio
| ID | Regra | Comportamento esperado |
|---|---|---|
| RN-01 | Snippet é sempre privado ao dono | não existe rota para ler/editar snippet de outro usuário, nem para administrador |
| RN-02 | shortcut é único apenas entre ativos do mesmo dono | um shortcut pode ser reaproveitado depois que o snippet anterior é retirado |
| RN-03 | Sanitização é obrigatória e não configurável | PLAIN_TEXT nunca aceita marcação; formato rico só mantém uma lista fixa de tags, sem atributos |
| RN-04 | Retirar é soft-delete versionado | retirar duas vezes com a mesma versão esperada (já incrementada) é tratado como já concluído; com versão desatualizada é conflito |
| RN-05 | Token delegado por serviço nunca acessa snippets | mesmo com identityAccessToken válido, binding !== null é recusado |
Compliance
| Órgão / norma | Exigência | Como a rota atende |
|---|---|---|
| LGPD | isolamento por dono | toda leitura/escrita é filtrada por ownerUserId = subject do token |
| HIPAA (condicional) | não expor conteúdo sensível em auditoria | eventos de auditoria gravam metadados (tamanho, formato, versão), não o conteúdo do snippet |
Variáveis de ambiente
Nenhuma específica a esta rota.
Requisitos não funcionais
| Requisito | Definição |
|---|---|
| Idempotência | Sim, na criação, por Idempotency-Key |
| Concorrência | Otimista, por version/If-Match |
| Paginação | Sim — page/limit, máximo 200 por página |
| Auditoria | Sim — identity.snippet.created.v1, .updated.v1, .deleted.v1 (sem o conteúdo em claro) |
Relacionado
- 🖥️ Tela:
A confirmar — responsável: time de frontend; data: 24/09/2026. - 📂 Módulo: Usuário