Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ee434d9
feat(api): persistência de refresh tokens — migration V40, entity, po…
Jul 10, 2026
a455730
feat(api): sign-in/sign-up persistem refresh token; TokenPort.extract…
Jul 10, 2026
014e96d
fix(api): RefreshTokenUseCase valida type=refresh, rotaciona e detect…
Jul 10, 2026
18a4017
feat(api): logout com revogação e cookie httpOnly do refresh token
Jul 10, 2026
2d70094
feat(api): rate limit estrito p/ POST /api/auth/* e purge diário de r…
Jul 10, 2026
785af97
feat(web): interceptor com refresh silencioso + fila; tokens fora do …
Jul 10, 2026
1bc6026
feat(web): logout real, AuthProvider com loading gate e guards via co…
Jul 10, 2026
fc9e110
docs: refresh token rotation — pattern em architecture.md e notas ope…
Jul 10, 2026
1665749
chore(repo): add project guidance and run configurations
Jul 12, 2026
1dd037d
feat: add trending aggregation and metric dashboards
Jul 12, 2026
9cff5bc
feat: add store administration and title associations
Jul 12, 2026
a3bf60f
feat: add editorial news workflow and public queries
Jul 12, 2026
282a029
feat: publish activity events and harden concurrent writes
Jul 12, 2026
6db6211
feat(web): integrate news, stores and admin workflows
Jul 12, 2026
8b4ba5b
feat(api): expose title store assignments in admin
Jul 12, 2026
3d1300b
refactor(web): align FSD UI, navigation and shared primitives
Jul 12, 2026
dcc8198
chore(api): harden profiles, seeds and service deployment
Jul 12, 2026
3cfaafc
docs: document trending and editorial news capabilities
Jul 12, 2026
deb6fc6
feat(api): harden auth analytics and editorial workflows
Jul 19, 2026
7861f55
feat(web): integrate reader admin and accessibility flows
Jul 19, 2026
ca7939e
feat(mobile): establish authenticated reader foundation
Jul 19, 2026
b1c7695
docs: consolidate guidance audits and technical debt
Jul 19, 2026
63246fb
chore: remove pnpm store index file
Jul 20, 2026
ea869ff
feat(api): add catalog search and release feeds
Aug 1, 2026
70753a6
feat(web): add catalog discovery and release experience
Aug 1, 2026
ccc4332
docs: record audit findings and remediation
Aug 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
117 changes: 117 additions & 0 deletions .agents/skills/database-design/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
name: database-design
description: "Use esta skill SEMPRE que for implementar um recurso que persista ou altere dados — antes de escrever entidade, migration, repository ou endpoint. Acione ao criar nova feature/entity/tabela/coluna, adicionar relacionamento ou FK, mudar tipo de coluna, criar índice, modelar enum/status, lidar com dinheiro/preço, contadores, jsonb, ou ao decidir entre PostgreSQL e MongoDB. Também use quando o usuário pedir para modelar banco, planejar schema, criar migration Flyway, normalizar tabela, resolver dívida de modelagem, ou revisar integridade/performance de dados. O objetivo é produzir o PLANO de schema (em BCNF, com FKs, índices, constraints e migration) ANTES de codar. Codifica as Database Modeling Guidelines do AGENTS.md para o monorepo Manga-Reader (dual-DB Postgres+Mongo)."
---

# Database Design — Arquiteto de Dados do Manga-Reader

Você é o **Arquiteto de Dados Sênior** do projeto. Seu papel: **antes de qualquer código de
persistência**, produzir um plano de schema correto — normalizado a **BCNF**, com integridade
referencial, tipos certos, constraints, índices e migration Flyway — e só então guiar a implementação
coordenada (entity → migration → repository → mapper → seed → testes).

> Esta skill é o **protocolo de aplicação** das *Database Modeling Guidelines* do `AGENTS.md`.
> Leia `references/db-modeling-protocol.md` para o passo-a-passo detalhado, a árvore de decisão
> jsonb-vs-tabela, a matriz de `ON DELETE`, e os snippets de SQL/migration.

---

## Quando esta skill é obrigatória

Acione **antes de escrever a primeira linha** sempre que o recurso:

- cria/edita uma **entidade** ou **tabela**;
- adiciona/altera **coluna**, **relacionamento**, **FK** ou **índice**;
- modela **status/tipo/enum**, **dinheiro/preço**, **contador**, ou usa **jsonb**;
- precisa decidir **PostgreSQL vs MongoDB**.

Se a tarefa "só" adiciona um campo, ainda assim rode o **mini-protocolo** (tipo + nullability +
constraint + índice). Modelagem ruim é mais cara de reverter depois do deploy.

---

## Filosofia

1. **Integridade antes de performance.** FK, CHECK e BCNF primeiro; otimização (índice, cache) depois,
e só com evidência.
2. **Desnormalizar é uma decisão, não um acidente.** Todo desvio de BCNF precisa de justificativa
escrita (snapshot cross-DB, contador reconciliável, jsonb de exibição).
3. **Explique o porquê.** O critério da decisão importa tanto quanto a decisão — é o que mantém o
schema saudável no próximo recurso.

---

## Protocolo (siga em ordem)

### 1. Escolher o banco
PostgreSQL (relacional, transacional) **ou** MongoDB (catálogo/UGC de volume). Referência cruzada
Postgres→Mongo é `title_id varchar`, **sem FK**. Ver tabela de ownership no `AGENTS.md`.

### 2. Levantar dependências funcionais e chaves
Para cada atributo, perguntar: *de que ele depende?* Todo determinante `X` de `X → Y` deve ser
**chave candidata**. Se não for, **extrair `X` para tabela própria + FK** (BCNF).

### 3. Decidir forma de cada campo
- **Escalar relacional** → coluna tipada.
- **Dinheiro** → `bigint` centavos + `currency`. Nunca varchar/float.
- **Enum/status** → `@Enumerated(STRING)` + `CHECK (col IN (...))` espelhando o enum.
- **Lista/objeto** → **árvore de decisão jsonb-vs-tabela** (`references/`): jsonb se opaco/exibição;
tabela se filtrado/agregado/com FK no Postgres.

### 4. Integridade referencial
Toda referência = **FK com `ON DELETE` explícito** (CASCADE filha/junção · RESTRICT conteúdo
autorado/financeiro · SET NULL referência opcional). Ver matriz no `references/`.

### 5. Índices
FK sempre indexada; colunas de `WHERE`/`JOIN`/`ORDER BY`; parcial para flags. **Sem redundância**
(prefixo de UNIQUE composto) nem duplicatas.

### 6. Contadores / derivados
Cache só com tabela-fonte → manter por incremento **e** reconciliar via job (`SET=COUNT`). Sem
tabela-fonte → documentar que fica só por incremento.

### 7. Migration + implementação coordenada
Flyway `V<n>__descricao.sql`, forward-only, comentada. Backfill antes de drop. Toda mudança de coluna
mapeada → ajustar **entity, DTO, mapper, seed, testes** juntos (Hibernate `validate`). Validar
`V1..Vn` em Postgres real.

---

## Output Format

Sempre entregue o plano **antes** do código, neste formato:

### 🗄️ Banco escolhido
Postgres ou Mongo + por quê.

### 📐 Modelo (BCNF)
Tabelas/coleções, colunas com tipo, PK, chaves candidatas, FKs. Aponte cada dependência funcional e
confirme que todo determinante é chave candidata. Justifique qualquer desnormalização.

### 🔗 Integridade
FKs com `ON DELETE`, CHECK constraints (com os valores do enum), UNIQUE, NOT NULL.

### ⚡ Índices
Lista com a query que cada um serve. Aponte redundâncias evitadas.

### 🧮 Derivados
Contadores/caches e como serão mantidos/reconciliados.

### 🛠️ Migration + código
Migration Flyway sugerida + lista dos arquivos Java/TS a tocar em conjunto (entity, DTO, mapper, seed,
testes) e o plano de verificação.

---

## Checklist final (antes de implementar)

- [ ] Banco correto (Postgres relacional × Mongo catálogo/UGC)?
- [ ] Em **BCNF** — todo determinante é chave candidata? Desnormalização justificada por escrito?
- [ ] Dinheiro em centavos + currency? Enum com CHECK? Nenhum booleano-string?
- [ ] Toda FK presente e com `ON DELETE` explícito e correto?
- [ ] FKs e predicados de query indexados; **sem** índice redundante/duplicado?
- [ ] jsonb só para dado opaco/exibição (não filtrado no Postgres)?
- [ ] Contadores com tabela-fonte têm plano de reconciliação?
- [ ] Migration Flyway forward-only, com backfill antes de drop, validada em Postgres real?
- [ ] Entity/DTO/mapper/seed/testes ajustados em conjunto?
- [ ] Item de dívida atualizado em `docs/technical-debt/sql-technical-debt.md` se aplicável?
164 changes: 164 additions & 0 deletions .agents/skills/database-design/references/db-modeling-protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# DB Modeling Protocol — Referência

Detalhe das *Database Modeling Guidelines* (`docs/database-modeling.md`). Exemplos vêm das migrations reais V23–V30.

---

## 1. Dual-DB — ownership

| PostgreSQL (JPA + Flyway) | MongoDB (Spring Data + Mongock) |
|---------------------------|---------------------------------|
| users, groups, group_users, group_works | titles |
| events, event_tickets, event_participants, event_organizers | chapters |
| — (forum migrou p/ Mongo em V016; tabelas dropam na fase 2/V33) | comments (unificado: obra/resenha/fórum), comments_votes |
| | forum_topics, forum_topics_votes, reviews_votes |
| user_libraries, user_recommendations, user_social_links | reviews, reviews_aggregate, title_trend_daily |
| stores, store_titles | news |
| subscriptions, subscription_plans, gift_codes, payments, subscription_audit_logs | |
| tags, domain_labels | |

**Cross-DB**: Postgres guarda `title_id varchar` (ObjectId do Mongo). **Sem FK possível.** Campos da
obra copiados no Postgres (`name`, `cover`, `genres`) são **snapshots de leitura deliberados** —
reconciliar por evento/job, **nunca** criar tabela `titles` no Postgres.

---

## 2. BCNF — teste prático

Para cada tabela, liste as dependências funcionais `X → Y`. Pergunte: **`X` é chave candidata?**

- Sim → ok.
- Não → **viola BCNF**. Extraia `{X, Y…}` para tabela própria e referencie por FK.

**Exemplo real (BCNF-01, V28):** `events` tinha `organizer_id → organizer_name, organizer_avatar…`.
`organizer_id` não era chave candidata de `events` e organizadores se repetiam → extraído para
`event_organizers(id, external_id UNIQUE, name, avatar, …)` + `events.organizer_ref` FK. No write-path,
`findOrCreate(externalId)` deduplica e propaga edição (consistência desejada).

**Quando NÃO normalizar:** se a "repetição" é um snapshot independente por linha (item de biblioteca,
recomendação do usuário), manter denormalizado — não há ganho de consistência, só acoplamento.

---

## 3. Árvore de decisão — jsonb vs tabela

```
O campo é lista/objeto?
├── É filtrado / agregado / precisa de FK no Postgres (WHERE/JOIN/GROUP BY)?
│ ├── SIM → TABELA (junção ou filha) + índice
│ └── NÃO → continua
└── É opaco / só exibição / i18n / preferência?
└── SIM → jsonb aceitável
```

- **jsonb ok**: `users.settings`, `LocalizedString` (`name`/`description`), `users.content_locales`,
`events.gallery/schedule/special_guests/social_links`, `groups.focus_tags`, `forum_topics.tags`,
`stores.features`.
- **Vira tabela** quando surgir filtro relacional. *Nota:* filtro de gênero hoje é no **Mongo** (`$all`
em `titles`), por isso as arrays de gênero do Postgres seguem jsonb. Reabrir se aparecer filtro no
Postgres.

---

## 4. Tipos

| Conceito | Tipo certo | Errado |
|----------|-----------|--------|
| Dinheiro | `bigint` centavos + `currency varchar(3)` | `varchar`, `double`, `numeric` solto |
| Enum/status | `varchar` + `@Enumerated(STRING)` + CHECK | `EnumType.ORDINAL`, varchar livre |
| Booleano | `boolean` | `varchar('true'/'S')` |
| Timestamp | `timestamp` | string |
| i18n texto | `jsonb` (`LocalizedString`) | colunas por idioma |

**Dinheiro (V24):**
```sql
ALTER TABLE event_tickets
ADD COLUMN price_in_cents bigint,
ADD COLUMN currency varchar(3) NOT NULL DEFAULT 'BRL';
UPDATE event_tickets SET price_in_cents = ROUND(
NULLIF(regexp_replace(replace(price, ',', '.'), '[^0-9.]', '', 'g'), '')::numeric * 100);
ALTER TABLE event_tickets ALTER COLUMN price_in_cents SET NOT NULL,
ADD CONSTRAINT chk_event_tickets_price CHECK (price_in_cents >= 0);
ALTER TABLE event_tickets DROP COLUMN price;
```
Entity: `long priceInCents` + `String currency`. DTO/mapper/seed/frontend `formatCurrency` juntos.

**Enum + CHECK (V26/V30):** valores espelham `Enum.name()`. CHECK passa NULL (ok p/ coluna nullable).
```sql
ALTER TABLE users ADD CONSTRAINT chk_users_role CHECK (role IN ('ADMIN','MODERATOR','MEMBER'));
ALTER TABLE stores ADD CONSTRAINT chk_stores_category CHECK (category IN ('OFICIAL','NOVA','USADO'));
```

---

## 5. Matriz de `ON DELETE` (IR-5, V27)

| Política | Usar quando | Exemplos |
|----------|-------------|----------|
| **CASCADE** | filha/junção perde sentido sem o pai | `event_tickets→events`, `forum_replies→forum_topics`, `group_users→groups/users`, `event_participants→events/users`, `user_social_links/libraries/recommendations→users`, `store_titles→stores` |
| **RESTRICT** | conteúdo autorado ou registro financeiro/plano-em-uso | `forum_topics/forum_replies.author→users`, `payments→users`, `subscriptions→users`, `subscriptions/gift_codes.plan→subscription_plans`, `subscription_audit_logs→subscriptions` |
| **SET NULL** | referência opcional | `gift_codes.redeemed_by_user_id`, `subscription_audit_logs.performed_by`, `events.organizer_ref` |

```sql
ALTER TABLE forum_replies DROP CONSTRAINT fk_forum_replies_topic,
ADD CONSTRAINT fk_forum_replies_topic
FOREIGN KEY (topic_id) REFERENCES forum_topics(id) ON DELETE CASCADE;
```
FK criada sem nome (inline `REFERENCES`) → Postgres nomeia `<tabela>_<col>_fkey`. Indexe toda FK.

---

## 6. Índices

- **Criar**: toda coluna de FK; `WHERE`/`JOIN`/`ORDER BY` frequentes; parcial p/ flag de baixa
cardinalidade (`CREATE INDEX … WHERE status='ACTIVE'`).
- **Não criar** (redundante): coluna que é **prefixo mais à esquerda** de um índice composto/UNIQUE já
existente. Ex.: `idx_user_libraries_user(user_id)` é coberto por `uk(user_id,title_id)` → drop.
- **Não duplicar**: dois índices na mesma coluna com nomes diferentes (ex.: `idx_group_users_user` +
`idx_group_users_user_id`).

---

## 7. Contadores desnormalizados (PERF-6)

Cache permitido **se há tabela-fonte**. Manter por incremento no use case **e** reconciliar:

```java
@Modifying
@Query("UPDATE ForumTopic t SET t.replyCount = (SELECT COUNT(r) FROM ForumReply r WHERE r.topic = t)")
int reconcileReplyCounts();
```
`CounterReconciliationJob` (`@Scheduled`, `@Profile("!test")`) chama os reconciles. `SET=COUNT` é
idempotente → sem double-count. **Não use trigger.** Contadores sem fonte (likes/views/popularity/
rating/interested) ficam só por incremento — documentar.

Reconciliáveis hoje: `groups.total_titles`←`group_works`,
`events.participants`←`event_participants` (Postgres) e
`forum_topics.replyCount`←`comments {targetType=FORUM_TOPIC}` (Mongo, mesmo job).

---

## 8. Migration Flyway — regras

1. `V<n>__descricao_em_snake.sql`, próximo número na sequência, **forward-only**.
2. Comentário no topo explicando intenção + a query/regra que justifica.
3. Destrutivo (drop coluna, troca de tipo): **backfill na mesma migration antes do drop**.
4. Coluna mapeada mudou → ajustar **entity + DTO + mapper + seed + testes** no mesmo PR (senão
Hibernate `validate` quebra no boot).
5. **Validar a cadeia `V1..Vn`** aplicando em Postgres real antes de concluir:
```bash
docker run -d --name fwcheck -e POSTGRES_USER=manga -e POSTGRES_PASSWORD=manga -e POSTGRES_DB=manga postgres:17-alpine
for f in $(ls .../db/migration/V*.sql | sort -V); do
docker exec -i fwcheck psql -v ON_ERROR_STOP=1 -U manga -d manga < "$f" || break
done
```
6. Testes que persistem entidades com `jsonb` precisam de **Testcontainers Postgres** (H2 não suporta
jsonb) — `@DataJpaTest @Tag("testcontainers") @Import(PostgresTestContainerConfig.class)` + Flyway.

---

## 9. Documentação

Toda decisão de modelagem nova ou dívida identificada → registrar em
`docs/technical-debt/sql-technical-debt.md` (fonte oficial da dívida de banco). Item resolvido =
marcar **RESOLVIDO** com o número da migration, não apagar.
Loading