Skip to content

feat(activity): S5 — query object ReactionSummary (#544) - #563

Open
davicbtoliveira wants to merge 3 commits into
feat/timeline-reactionfrom
feat/reaction-summary
Open

davicbtoliveira wants to merge 3 commits into
feat/timeline-reactionfrom
feat/reaction-summary

Conversation

@davicbtoliveira

@davicbtoliveira davicbtoliveira commented Sep 16, 2026

Copy link
Copy Markdown
Member

Contexto

A timeline web precisa exibir o breakdown de reações de cada post (👍 3 · ❤️ 5) e destacar a reação do usuário atual, sem que o número de consultas cresça com o tamanho da página. Hoje o único caminho é withCount('reactions'), que lê o agregado do Discord e não sabe "quem reagiu".

Este PR entrega o S5 do PRD #539: o query object ReactionSummary, que resume as reações de um conjunto de posts em número fixo de consultas lendo só activity_user_reactions:

  • 1 query de agregação: select timeline_id, reaction, count(*) ... where timeline_id in (...) group by timeline_id, reaction;
  • 1 query para a reação do usuário nos mesmos posts, pulada quando userId é null.

Cada post volta como um DTO TimelineReactionSummary com counts (só reações com contagem > 0, indexadas pelo value do enum) e mine (?TimelineReaction). Toda timeline_id pedida tem um item na coleção, mesmo sem reações. A substituição do withCount('reactions') no feed e no thread fica para o S8 (#547).

Nota de design: a issue descreve mine como ?string. Optei por ?TimelineReaction para manter o padrão do módulo (a ReactWith já devolve o enum) e a preferência do projeto por DTOs/enums no lugar de arrays e strings soltas. Quem consumir no S8 faz ->value só na borda Blade/Livewire. Se preferirem o ?string literal, é uma troca de uma linha.

Alterações

  • app-modules/activity/src/Reaction/Queries/ReactionSummary.php: forTimelines(iterable $timelineIds, ?string $userId): Collection<string, TimelineReactionSummary>, indexada por timeline_id. Deduplica ids e retorna coleção vazia sem tocar o banco quando a lista é vazia. A agregação usa toBase() para não hidratar UserReaction parciais; o enum volta via TimelineReaction::from() com narrowing explícito das colunas.
  • app-modules/activity/src/Reaction/DTOs/TimelineReactionSummary.php: DTO final readonly com timelineId, counts, mine e os helpers total() e countOf(TimelineReaction).
  • app-modules/activity/tests/Feature/Reaction/ReactionSummaryTest.php: 7 testes cobrindo o shape do breakdown, mine nulo sem usuário, exclusão de posts não pedidos, ids repetidos, lista vazia e o custo constante de consultas (1 query sem usuário, ≤ 2 com usuário, mesmo número para 3 e 30 posts). A contagem usa o query log da conexão com reset em finally; os helpers globais são prefixados para não colidir com outros arquivos Pest.

Issues Relacionadas

Closes #544
Related to #539

davicbtoliveira and others added 2 commits September 15, 2026 22:53
Resume as reações de um conjunto de posts em número fixo de consultas:
uma agregação `group by (timeline_id, reaction)` e, se houver usuário,
uma leitura das linhas dele nos mesmos posts. Sem N+1 conforme a
página cresce; a 2ª consulta não roda quando `userId` é null.

Cada item vem como DTO `TimelineReactionSummary` (counts só > 0 +
`mine` como `TimelineReaction`), seguindo a preferência do projeto por
DTOs no lugar de arrays associativos. O agregado do Discord
(`activity_reactions`) não entra na soma.

Teste feature com `DB::listen` confirma ≤ 2 consultas para 3 e para 30
posts, e 1 consulta sem usuário.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- A agregação `group by` agora usa `toBase()`: a linha é um agregado, não
  um `UserReaction`, e hidratar o model produzia registros parciais (sem
  id) fáceis de confundir com linhas reais. O tipo do enum volta via
  `TimelineReaction::from()`, com narrowing explícito das colunas.
- Helpers globais do teste ganham prefixo (`seedReactionSummaryPosts`,
  `reactionSummaryQueries`) para não colidir com outros arquivos Pest,
  que compartilham o mesmo processo.
- A contagem de consultas usa o query log da conexão, com reset em
  `finally`, no lugar de um `DB::listen` que nunca era removido; o helper
  devolve `[$result, $count]` e elimina o `use (&$summary)`.
- A asserção de `mine` nulo sem usuário lê como o critério de aceite
  (`pluck('mine')->filter()` vazio) sem disputar com o Rector.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GabrielFVDev
GabrielFVDev previously approved these changes Sep 16, 2026

@GabrielFVDev GabrielFVDev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread app-modules/activity/src/Reaction/Queries/ReactionSummary.php
Refactor query to map user reactions to timeline reactions.

Signed-off-by: Davi Castello Branco Tavares de Oliveira <davicbtoliveira@hotmail.com>

@hefeus hefeus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@stherzada stherzada linked an issue Sep 16, 2026 that may be closed by this pull request
6 tasks
@stherzada stherzada added this to the Reactions Timeline milestone Sep 16, 2026

@BrunaDomingues BrunaDomingues left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(activity): S5 — query object ReactionSummary

6 participants