| name | crud-api |
| description | Implementação de CRUD completo para a API do ReservAqui. Especializada na arquitetura do projeto (Entity → Service → Controller → Routes) com multi-tenant PostgreSQL. Use esta skill sempre que precisar criar ou modificar endpoints REST, entidades, rotas, serviços ou controllers no backend — especialmente ao adicionar novos recursos a tabelas do master DB (usuario, anfitriao) ou ao schema tenant (hotel). Aciona automaticamente quando o usuário menciona: "criar endpoint", "novo recurso", "CRUD", "rota", "controller", "service", "adicionar ao backend", "preciso de uma API para", ou quando descreve uma necessidade de negócio que claramente requer novos endpoints ou entidades. |
CRUD API — ReservAqui Backend
Especialista em implementar operações CRUD no backend do ReservAqui, aplicando a arquitetura em camadas do projeto, o padrão de funções privadas/públicas dos services, e as regras de segurança e escalabilidade já estabelecidas.
Context Load (MANDATORY — run this first)
Antes de qualquer análise, pesquisa ou implementação, verifique se existe um arquivo de contexto salvo:
- Procure
.context/crud-api_context.md na raiz do projeto.
- Se o arquivo existir: leia-o na íntegra. Use as seções de Arquitetura, Arquivos Afetados, Referências de Código e Decisões de Design para restaurar o contexto de trabalho. Pule qualquer exploração de codebase que já esteja documentada lá. Informe ao usuário:
"Contexto restaurado de .context/crud-api_context.md (v<N>, atualizado em <data>). Continuando da sessão anterior."
- Se o arquivo não existir: prossiga normalmente — explore o codebase, colete contexto e documente ao final via Context Storage.
Regra: Nunca ignore um arquivo de contexto existente. Ele existe exatamente para evitar reanalise. Confie nele e atualize se a implementação mudar.
Fase 0 — Discovery de Negócio (OBRIGATÓRIA antes de qualquer código)
Antes de escrever uma linha de código, conduza estas duas trilhas em paralelo. Elas devem ser respondidas antes de avançar.
Track A — Regras de Negócio
Pergunte ao usuário (ou extraia de regras de negócio.txt se existir):
- Qual entidade estamos criando/modificando? (nome, propósito, dono — usuário, hotel ou sistema)
- Onde vive no banco? Master DB (dados globais) ou schema tenant (dados por hotel)?
- Quem pode acessar? Usuário autenticado (
authGuard), hotel autenticado (hotelGuard), público, ou combinação?
- Quais operações são necessárias? Listar, criar, ler por ID, atualizar (parcial/total), deletar (hard/soft)?
- Existem restrições de unicidade, ownership ou relacionamento? (ex: "um usuário só pode ter uma reserva ativa por hotel")
- Há regras de validação específicas? Formatos, limites, campos obrigatórios vs. opcionais.
- Existem operações que precisam de rate limiting? (login, upload, pagamento — ações com risco de abuso)
Track B — Análise de Schema
Leia os arquivos de schema relevantes:
Backend/database/scripts/init_master.sql — para entidades globais
Backend/database/scripts/init_tenant.sql — para schema por hotel
Identifique e documente:
- Tabelas envolvidas e seus campos (tipos, constraints, NOT NULL, UNIQUE, FK)
- Índices existentes — especialmente
WHERE deleted_at IS NULL para soft deletes
- Relacionamentos — FKs e comportamento CASCADE vs. RESTRICT
- Checks e enums já definidos no banco
- Campos de auditoria —
criado_em, atualizado_em, deleted_at
Apresente um resumo do schema antes de propor a implementação.
Fase 1 — Proposta de Arquitetura
Após o Discovery, apresente ao usuário um resumo das decisões cobrindo:
Entidade: [nome]
Banco: [master | tenant: {schema_name}]
Acesso: [público | authGuard | hotelGuard | combinado]
Operações: [GET /... | POST /... | PATCH /... | DELETE /...]
Validações: [campos e regras]
Rate limiting: [sim/não — qual endpoint]
Soft delete: [sim/não]
Aguarde aprovação explícita antes de implementar.
Fase 2 — Implementação por Camadas
Implemente sempre na ordem: Entity → Service → Controller → Routes → app.ts. Cada camada tem responsabilidade estrita.
Camada 1 — Entity (Validação Pura)
Arquivo: Backend/src/entities/[NomeEntidade].ts
- A Entity é responsável apenas por validação de input. Nunca acessa banco de dados.
- Implemente métodos estáticos:
validate() para criação, validatePartial() para updates (todos os campos opcionais), e métodos específicos se necessário (ex: validateNovaSenha()).
- Retorne objetos tipados ou lance
Error com mensagem descritiva em português.
- Defina e exporte as interfaces de input (
RegisterXInput, UpdateXInput) e output seguro (XSafe — sem campos sensíveis como senhas ou hashes).
export interface RegisterXInput { }
export interface UpdateXInput { }
export interface XSafe { }
export class NomeEntidade {
static validate(input: unknown): RegisterXInput { }
static validatePartial(input: unknown): UpdateXInput { }
}
Camada 2 — Service (Lógica de Negócio)
Arquivo: Backend/src/services/[nomeEntidade].service.ts
Regra Fundamental — Wrapper Pattern:
- Toda função pública exportada deve ser um wrapper fino que chama a função privada correspondente.
- A lógica real fica na função privada com prefixo
_ (underscore).
- Nunca exporte diretamente a implementação completa.
async function _registerX(input: RegisterXInput): Promise<XSafe> {
}
export async function registerX(input: RegisterXInput): Promise<XSafe> {
return _registerX(input);
}
export async function registerX(input: RegisterXInput): Promise<XSafe> {
}
Por que esse padrão? Permite adicionar logging, caching, métricas ou decorators no wrapper público sem tocar na lógica de negócio, e facilita mocking em testes.
Regras do Service:
- Lance
Error com mensagens em português claras (ex: "Email já cadastrado", "Recurso não encontrado", "Credenciais inválidas")
- Para master DB: use
pool diretamente do database/connection.ts
- Para tenant DB: use
withTenant(schemaName, async (client) => { ... }) para garantir isolamento de schema
- Nunca retorne campos sensíveis (senhas, hashes, tokens) — use o tipo
XSafe
- Para soft deletes: use
UPDATE ... SET deleted_at = NOW() ao invés de DELETE
- Para buscas: sempre filtre
WHERE deleted_at IS NULL quando a tabela tiver esse campo
Camada 3 — Controller (HTTP Mapping)
Arquivo: Backend/src/controllers/[nomeEntidade].controller.ts
- O controller não contém lógica de negócio — apenas traduz HTTP ↔ Service.
- Implemente
mapError() para traduzir mensagens de erro do service em status codes adequados.
- Nunca exponha stack traces ou mensagens internas ao cliente.
function mapError(message: string): number {
if (message.includes('já cadastrado') || message.includes('já existe')) return 409;
if (message.includes('inválid') || message.includes('obrigatório')) return 400;
if (message.includes('não encontrado')) return 404;
if (message.includes('Credenciais') || message.includes('não autorizado')) return 401;
if (message.includes('sem permissão') || message.includes('proibido')) return 403;
return 500;
}
export async function createXController(req: Request, res: Response): Promise<void> {
try {
const input = NomeEntidade.validate(req.body);
const result = await registerX(input);
res.status(201).json({ data: result });
} catch (err) {
const message = err instanceof Error ? err.message : 'Erro interno';
res.status(mapError(message)).json({ error: message });
}
}
Padrão de resposta:
- Sucesso criação:
201 { data: {...} }
- Sucesso leitura/update:
200 { data: {...} }
- Sucesso sem corpo:
204 (delete)
- Erro:
{ error: "mensagem humana" } — nunca stack trace
Camada 4 — Routes (Middleware Chain)
Arquivo: Backend/src/routes/[nomeEntidade].routes.ts
- Monte os middlewares na ordem correta: validação → rate limiting → autenticação → controller
- Reutilize middlewares existentes sempre que possível.
import { Router } from 'express';
import { authGuard } from '../middlewares/authGuard';
import { hotelGuard } from '../middlewares/hotelGuard';
import { requireFields } from '../middlewares/validateBody';
const router = Router();
router.post('/', requireFields(['campo1', 'campo2']), authGuard, createXController);
router.get('/me', authGuard, getXController);
router.patch('/me', authGuard, updateXController);
router.delete('/:id', authGuard, deleteXController);
export default router;
Decisão de guard:
authGuard → endpoint para usuários hóspedes (valida user_id no token)
hotelGuard → endpoint para hotéis anfitriões (valida hotel_id no token, rejeita tokens de usuário)
- Sem guard → endpoint público (ex: busca de disponibilidade)
Camada 5 — Registro em app.ts
Verifique Backend/src/app.ts e adicione a rota se ainda não estiver lá:
import xRoutes from './routes/x.routes';
app.use(`${API_PREFIX}/recurso`, xRoutes);
Checklist de Segurança (revise antes de concluir)
Antes de entregar a implementação, verifique:
Checklist de Escalabilidade
Sugestões Proativas
Ao analisar o schema ou as regras de negócio, aponte ativamente se:
- Falta índice em campo frequentemente filtrado
- Uma operação pode causar N+1 queries (consultas em loop)
- Um endpoint público expõe dados sensíveis sem necessidade
- Uma listagem sem paginação pode retornar volumes grandes
- Um relacionamento pode se beneficiar de
CASCADE vs. RESTRICT
- Há oportunidade de soft delete onde hoje é hard delete (ou vice-versa)
Referência Rápida — Arquitetura do Projeto
| Camada | Arquivo | Responsabilidade |
|---|
| Entity | src/entities/X.ts | Validação de input, interfaces de tipo |
| Service | src/services/x.service.ts | Lógica de negócio, queries DB |
| Controller | src/controllers/x.controller.ts | HTTP ↔ Service, mapError() |
| Routes | src/routes/x.routes.ts | Middleware chain, registro de endpoints |
| app.ts | src/app.ts | Montagem das rotas no Express |
| Middleware | Arquivo | Uso |
|---|
authGuard | middlewares/authGuard.ts | Endpoints de usuário hóspede |
hotelGuard | middlewares/hotelGuard.ts | Endpoints de hotel anfitrião |
requireFields | middlewares/validateBody.ts | Validar campos obrigatórios no body |
loginRateLimiter | middlewares/rateLimiter.ts | Proteção anti-brute-force |
uploadMiddleware | middlewares/imageUpload.ts | Upload de imagens |
| DB Context | Quando usar |
|---|
pool direto | Tabelas master (usuario, anfitriao, refresh_tokens...) |
withTenant(schema, fn) | Tabelas tenant (reserva, quarto, categoria_quarto...) |
Context Storage (MANDATORY — run this last)
Após concluir a implementação, crie ou atualize .context/crud-api_context.md
na raiz do projeto (não dentro da pasta da skill). Se o arquivo já existir, atualize-o
para refletir o estado atual — nunca apague a seção Changelog.
Arquivo a escrever: .context/crud-api_context.md
Use este template (preencha todas as seções):
# Context: CRUD API
> Last updated: <ISO 8601 datetime>
> Version: <N>
## Purpose
Rastreamento das implementações de CRUD realizadas no backend ReservAqui via skill crud-api.
## Architecture / How It Works
- Camadas: Entity (validação) → Service (lógica + DB) → Controller (HTTP) → Routes (middleware chain)
- DB: master (pool direto) | tenant (withTenant())
- Auth: authGuard (usuário) | hotelGuard (hotel)
- Pattern: exported public wrappers → private _functions
## Affected Project Files
| File | Uses this system? | Relationship |
|------|:-----------------:|--------------|
| `Backend/src/app.ts` | Yes | Monta as rotas implementadas |
## Code Reference
### `Backend/src/services/x.service.ts` — `functionName(args)`
\`\`\`typescript
// cole o trecho relevante aqui
\`\`\`
**How it works:** explicação em linguagem natural.
**Coupling / side-effects:** o que mais depende disso.
## Key Design Decisions
- Decisão tomada e por quê (trade-offs, alternativas consideradas)
## Changelog
### v<N> — <date>
- O que foi implementado ou alterado nesta sessão
### v<N-1> — <date>
- (preserve entradas anteriores — nunca as apague)
Após escrever o arquivo, informe ao usuário:
"Contexto salvo em .context/crud-api_context.md — sessões futuras podem carregar este arquivo para restaurar o contexto completo instantaneamente, sem reler o codebase."