feat/implementando tabela de última atividade para usuários#405
feat/implementando tabela de última atividade para usuários#405mateusmrosa wants to merge 2 commits intohomolfrom
Conversation
WalkthroughAdds a new table and Prisma model to track each person's last activity, introduces a standalone PostgreSQL function to upsert last-activity and conditionally log activity, removes an in-migration copy of the function, and surfaces last-activity timestamp in the Pessoa listing API. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant Service as PessoaService
participant DB as PostgreSQL
Client->>Service: Request list of pessoas
Service->>DB: Prisma findMany (include: ultimaAtividade.ultima_atividade_em)
DB-->>Service: Pessoas with optional ultima_atividade_em
Note right of Service: Map p.ultimaAtividade?.ultima_atividade_em -> ListPessoa.ultima_atividade_em
Service-->>Client: ListPessoa[] (includes ultima_atividade_em)
sequenceDiagram
autonumber
actor App as Application
participant DB as PostgreSQL
App->>DB: CALL f_insere_log_atividade(p_pessoa_id, p_ip, p_pessoa_sessao_id)
rect rgba(230,245,255,0.6)
Note over DB: Upsert last activity
DB->>DB: INSERT INTO pessoa_ultima_atividade (...) ON CONFLICT(pessoa_id) DO UPDATE SET ...
end
rect rgba(240,255,230,0.6)
Note over DB: Throttle activity log (1-minute window)
DB->>DB: SELECT COUNT(*) FROM pessoa_atividade_log WHERE pessoa_sessao_id = ... AND criado_em > NOW() - INTERVAL '1 minute'
alt count == 0
DB->>DB: INSERT INTO pessoa_atividade_log (pessoa_id, ip, pessoa_sessao_id, criado_em = NOW())
else count > 0
Note right of DB: Skip insert
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (6)
backend/prisma/migrations/20250826182728_add_pessoa_ultima_atividade/migration.sql (1)
12-16: Consider indexing pessoa_sessao_id for join performance.If you fetch/update by session (e.g., on each request), an index on pessoa_sessao_id avoids seq scans.
Apply this diff to add the index:
-- CreateIndex CREATE INDEX "pessoa_ultima_atividade_ultima_atividade_em_idx" ON "pessoa_ultima_atividade"("ultima_atividade_em"); +-- CreateIndex +CREATE INDEX "pessoa_ultima_atividade_pessoa_sessao_id_idx" ON "pessoa_ultima_atividade"("pessoa_sessao_id");backend/prisma/schema.prisma (3)
504-505: Add a brief doc comment to clarify the 1:1 relation semantics.Minor clarity: note that this relation is a 1:1 pointer to the “last activity” row keyed by pessoa_id and is populated through DB upserts.
Apply this diff:
- ultimaAtividade PessoaUltimaAtividade? + /// Última atividade do usuário (1:1 via pessoa_id). Atualizada por função SQL de upsert. + ultimaAtividade PessoaUltimaAtividade?
4858-4862: Optionally make the relation name explicit to avoid future ambiguity.There’s a single relation between PessoaSessao and PessoaUltimaAtividade today, but being explicit prevents name inference issues if new relations are added later.
Apply this diff here and in the model below to name the relation:
- PessoaUltimaAtividade PessoaUltimaAtividade[] + PessoaUltimaAtividade PessoaUltimaAtividade[] @relation("PessoaSessaoUltimaAtividade")And mirror it in the PessoaUltimaAtividade.sessao field (see suggested diff on Lines 7836-7838).
7829-7842: Model maps cleanly to migration; add an index on pessoa_sessao_id and (optionally) an explicit relation name.
- 1:1 via @@unique([pessoa_id]) matches the SQL unique index. Good.
- Add @@index([pessoa_sessao_id]) to mirror the SQL index (and speed lookups by session).
- Optional: name the relation to PessoaSessao to future-proof relation inference.
Apply this diff:
model PessoaUltimaAtividade { id Int @id @default(autoincrement()) pessoa_id Int pessoa_sessao_id Int? ip String @db.Inet ultima_atividade_em DateTime @default(now()) @db.Timestamptz(6) - pessoa Pessoa @relation(fields: [pessoa_id], references: [id], onDelete: Cascade) - sessao PessoaSessao? @relation(fields: [pessoa_sessao_id], references: [id], onDelete: SetNull) + pessoa Pessoa @relation(fields: [pessoa_id], references: [id], onDelete: Cascade) + sessao PessoaSessao? @relation("PessoaSessaoUltimaAtividade", fields: [pessoa_sessao_id], references: [id], onDelete: SetNull) @@unique([pessoa_id]) @@index([ultima_atividade_em]) + @@index([pessoa_sessao_id]) @@map("pessoa_ultima_atividade") }backend/prisma/manual-copy/0055-f_insere_log_atividade.pgsql (2)
21-33: Use EXISTS instead of COUNT and drop the unused variable; reduces work and contention.COUNT(*) scans more than needed and you don’t use the exact count. EXISTS short-circuits on first match. Also remove the v_existing_count variable.
Apply this diff:
-DECLARE - v_existing_count INTEGER; +-- no declarations needed @@ - SELECT COUNT(*) - INTO v_existing_count - FROM pessoa_atividade_log - WHERE pessoa_sessao_id = p_pessoa_sessao_id - AND criado_em > (NOW() - INTERVAL '1 minute'); - - IF v_existing_count = 0 THEN - INSERT INTO pessoa_atividade_log - (pessoa_id, ip, pessoa_sessao_id, criado_em) - VALUES - (p_pessoa_id, p_ip, p_pessoa_sessao_id, NOW()); - END IF; + PERFORM 1 + FROM pessoa_atividade_log + WHERE pessoa_sessao_id = p_pessoa_sessao_id + AND criado_em > (NOW() - INTERVAL '1 minute'); + + IF NOT FOUND THEN + INSERT INTO pessoa_atividade_log (pessoa_id, ip, pessoa_sessao_id, criado_em) + VALUES (p_pessoa_id, p_ip, p_pessoa_sessao_id, NOW()); + END IF;
21-33: Consider concurrency-safe dedup and indexing for the 1-minute throttle.Two concurrent calls can still race and insert twice. If that matters, enforce at-most-one-per-minute per session with a unique constraint on (pessoa_sessao_id, date_trunc('minute', criado_em)) and switch to ON CONFLICT DO NOTHING. Also index to accelerate the time-window check.
Add these (in a migration):
-- Speeds up the EXISTS/WHERE lookup CREATE INDEX idx_pessoa_atividade_log_sessao_criado_em ON pessoa_atividade_log (pessoa_sessao_id, criado_em DESC); -- Enforce one row per minute per session (optional). CREATE UNIQUE INDEX idx_pessoa_atividade_log_unique_minute ON pessoa_atividade_log (pessoa_sessao_id, date_trunc('minute', criado_em)); -- Optionally, bind the unique index to a named constraint if you plan to use ON CONFLICT ON CONSTRAINT: -- ALTER TABLE pessoa_atividade_log -- ADD CONSTRAINT pessoa_atividade_log_unique_minute -- UNIQUE USING INDEX idx_pessoa_atividade_log_unique_minute;Then the insert can become:
INSERT INTO pessoa_atividade_log (pessoa_id, ip, pessoa_sessao_id, criado_em) VALUES (p_pessoa_id, p_ip, p_pessoa_sessao_id, NOW()) ON CONFLICT ON CONSTRAINT pessoa_atividade_log_unique_minute DO NOTHING;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
backend/prisma/manual-copy/0055-f_insere_log_atividade.pgsql(1 hunks)backend/prisma/migrations/20231228211508_session/migration.sql(0 hunks)backend/prisma/migrations/20250826182728_add_pessoa_ultima_atividade/migration.sql(1 hunks)backend/prisma/schema.prisma(4 hunks)backend/src/pessoa/entities/list-pessoa.entity.ts(1 hunks)backend/src/pessoa/pessoa.service.ts(2 hunks)
💤 Files with no reviewable changes (1)
- backend/prisma/migrations/20231228211508_session/migration.sql
🔇 Additional comments (7)
backend/prisma/migrations/20250826182728_add_pessoa_ultima_atividade/migration.sql (2)
2-10: Solid table design for last-activity tracking (1:1 via pessoa_id).Schema choices look correct: INET supports IPv4/IPv6, TIMESTAMPTZ(6) with CURRENT_TIMESTAMP default is consistent with the Prisma mapping, and a surrogate PK keeps upsert patterns simple.
19-22: FK actions are appropriate.ON DELETE CASCADE for pessoa ensures cleanup; ON DELETE SET NULL for pessoa_sessao preserves record if a session is pruned. Good balance for integrity vs. retention.
backend/prisma/schema.prisma (1)
3507-3507: Cosmetic attribute reordering only.Changing to @default("{}") @db.Json is a no-op in Prisma and should not generate a migration. OK to keep for consistency.
backend/src/pessoa/pessoa.service.ts (2)
1419-1421: LGTM: selecting the relation to expose last activity is correct.The Prisma select for ultimaAtividade with ultima_atividade_em keeps the payload lean and matches the intended surface.
1437-1439: LGTM: safe mapping of optional relation.Using optional chaining with null fallback avoids runtime errors when there’s no last-activity row.
backend/prisma/manual-copy/0055-f_insere_log_atividade.pgsql (2)
1-7: No action needed: parameter order is correct in all call sites
- Verified in
backend/src/common/middleware/logger.middleware.ts(line 171) that the call is now
SELECT f_insere_log_atividade(${user.id}::int, ${ip}::INET, ${user.session_id}::int);
which aligns with the new signature(p_pessoa_id, p_ip, p_pessoa_sessao_id).- No other invocations of
f_insere_log_atividadefound elsewhere in the codebase.All callers have been updated to match the new parameter order—no further changes required.
12-20: Upsert on pessoa_ultima_atividade validated ✅
- The migration at backend/prisma/migrations/20250826182728_add_pessoa_ultima_atividade/migration.sql defines
• a PRIMARY KEY on “id” (pessoa_ultima_atividade_pkey)
• a UNIQUE INDEX on “pessoa_id” (pessoa_ultima_atividade_pessoa_id_key), satisfying the ON CONFLICT target.No further changes needed here.
|
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/src/pessoa/pessoa.service.ts (1)
133-134: Remover log de senha em texto puro (exposição de segredo/PII).A linha loga a nova senha e o objeto
pessoa. Isso vaza credenciais e PII em logs. Remova imediatamente.Aplique este diff:
- this.logger.log(`new password: ${newPass}`, pessoa); + // Evitar logar senhas ou dados sensíveis; mantenha no máximo um trace sem segredos, se necessário. + // this.logger.debug(`Nova senha gerada para pessoaId=${pessoa.id}`);
🧹 Nitpick comments (2)
backend/src/pessoa/pessoa.service.ts (2)
1419-1421: Confirm 1:1 cardinalidade deultimaAtividadeou ajuste para 1:N (senão o mapeamento pode quebrar).A seleção e o mapeamento assumem que
Pessoa.ultimaAtividadeé uma relação 1:1. Se o modelo permitir múltiplos registros (1:N), o código deve ordenar/limitar e mapear o primeiro registro. Além disso, garanta uma restrição única empessoa_ultima_atividade.pessoa_idpara manter a consistência.Se a relação for 1:N, aplique algo assim:
- ultimaAtividade: { select: { ultima_atividade_em: true } }, + ultimaAtividade: { + orderBy: { ultima_atividade_em: 'desc' }, + take: 1, + select: { ultima_atividade_em: true }, + },E no mapeamento:
- ultima_atividade_em: p.ultimaAtividade?.ultima_atividade_em ?? null, + ultima_atividade_em: p.ultimaAtividade?.[0]?.ultima_atividade_em ?? null,Caso a relação seja verdadeiramente 1:1, mantenha o código como está e apenas confirme que existe índice/unique key em
pessoa_ultima_atividade(pessoa_id). Posso revisar o schema/migration se enviar o trecho relevante.Also applies to: 1437-1438
1369-1372: Evitarconsole.logem produção; use o logger da aplicação.Mantém padronização e facilita controle de nível/roteamento de logs.
Aplique este diff:
- console.log('ehAdmin', ehAdmin); + this.logger.debug(`ehAdmin=${ehAdmin}`);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
backend/src/pessoa/pessoa.service.ts(2 hunks)



Summary by CodeRabbit
New Features
Chores