Skip to content

Sprint 13.1: Implementações e otimizações extras#215

Merged
frigini merged 19 commits intomasterfrom
feature/extra-implementations-minimize-tech-debts
Apr 29, 2026
Merged

Sprint 13.1: Implementações e otimizações extras#215
frigini merged 19 commits intomasterfrom
feature/extra-implementations-minimize-tech-debts

Conversation

@frigini
Copy link
Copy Markdown
Owner

@frigini frigini commented Apr 28, 2026

Summary by CodeRabbit

  • Documentação
    • Roadmap, histórico e dívida técnica atualizados para Sprint 13/13.1; remoção de geradores Postman/README de collections.
  • Novos Recursos / Streaming
    • SSE em tempo real para bookings e verificação de providers com endpoints server-side, hub e hooks cliente (toasts e reconexão).
  • Novos Recursos / UI
    • Atualizações de identidade visual no Portal Admin e correção de data-testid em títulos de cartão.
  • Correções
    • Rejeição consistente de localizações malformadas, melhorias em logging e otimizações de permissões/rotas.
  • Internacionalização
    • i18n integrado nos apps e mocks de teste com suporte a default/strings vazias.
  • Performance
    • Reduções de alocação em caminhos críticos.
  • Testes
    • Novos testes unitários e de integração, cobertura ampliada.
  • Chore
    • CI: imagem de serviço fixada; monorepo/workspaces e dependência de fetch-event-source adicionadas.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@frigini has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 31 minutes and 57 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b8d20970-ac76-4b4f-aeb6-bf6d31874693

📥 Commits

Reviewing files that changed from the base of the PR and between 2087e61 and a0a064c.

📒 Files selected for processing (4)
  • src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru
  • src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/GetAllAllowedCities.bru
  • src/Web/e2e/provider/portal.spec.ts
  • src/Web/e2e/support/auth.ts
📝 Walkthrough

Walkthrough

Adiciona suporte SSE (hub, tópicos, handlers, endpoints, hooks), converte vários parsers para Span-based para reduzir alocações, endurece validações de headers/paths, altera convenção de tópicos Rebus, atualiza docs/roadmap/technical-debt, ajusta testes e adiciona dependências/front-end para SSE.

Changes

Cohort / File(s) Summary
Docs & Roadmap
docs/roadmap-history.md, docs/roadmap.md, docs/technical-debt.md
Registra Sprint 13/13.1; atualiza status/backlog; documenta i18n, theming e correções de data-testid.
Middlewares
src/Bootstrapper/.../GeographicRestrictionMiddleware.cs, src/Bootstrapper/.../RequestLoggingMiddleware.cs
Parsing e comparações reescritos com Span; headers malformados agora rejeitados; X-Forwarded-For e skip-logging mais precisos sem alocações desnecessárias.
Authorization & Monitoring
src/Shared/Authorization/Middleware/PermissionOptimizationMiddleware.cs, src/Shared/Monitoring/BusinessMetricsMiddleware.cs
Path matching e verificação de permissões reimplementadas com spans/StringComparison.OrdinalIgnoreCase; regras de rota e versionamento mais estritas.
Users & Identity
src/Modules/Users/.../RegisterCustomerCommandHandler.cs, src/Modules/Users/Infrastructure/Identity/Keycloak/KeycloakService.cs
Extrações de e-mail/user-id reescritas para slicing/span; lógica funcional preservada, menos alocações.
Shared Utilities & PII
src/Shared/Extensions/DocumentExtensions.cs, src/Shared/Utilities/PhoneNumberValidator.cs, src/Shared/Utilities/PiiMaskingHelper.cs
Validações CPF/CNPJ, telefone e mascaramento reescritas com stackalloc/spans e aritmética de dígitos para reduzir alocações.
Messaging & DI
src/Shared/Messaging/Options/MessageBusOptions.cs, src/Shared/Messaging/MessagingExtensions.cs
TopicNamingConvention refatorado com spans; adicionado OptionsTopicNameConvention público e registrado no pipeline Rebus.
Caching & Metrics
src/Shared/Caching/CacheMetrics.cs
NormalizeCacheKey reescrito usando spans (evita Split) mantendo fallback atual.
SSE — Core & Topics
src/Shared/Streaming/SseHub.cs, src/Shared/Streaming/SseTopic.cs, src/Shared/Extensions/ServiceCollectionExtensions.cs
Introduce ISseHub<T>/SseHub<T>, utilitário SseTopic e AddStreaming() (registra SseHub<> como singleton).
Bookings — SSE
src/Modules/Bookings/Application/DTOs/BookingStatusSseDto.cs, src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs, src/Modules/Bookings/API/Endpoints/BookingEventsEndpoint.cs, src/Modules/Bookings/API/Endpoints/BookingsEndpoints.cs, src/Modules/Bookings/Application/Extensions.cs
Adiciona DTO, handler que publica via SseHub, endpoint SSE GET /{id:guid}/events, DI e mapeamento de endpoint.
Providers — SSE
src/Modules/Providers/Application/DTOs/ProviderVerificationSseDto.cs, src/Modules/Providers/Application/Events/ProviderVerificationStatusUpdatedSseHandler.cs, src/Modules/Providers/API/Endpoints/ProviderVerificationEventsEndpoint.cs, src/Modules/Providers/API/Endpoints/ProvidersModuleEndpoints.cs, src/Modules/Providers/Application/Extensions.cs
Adiciona DTO, handler e endpoint SSE para verificação de providers; registra handler/endpoint.
Frontend — Hooks & Integration
src/Web/MeAjudaAi.Web.Customer/hooks/use-sse.ts, src/Web/MeAjudaAi.Web.Customer/hooks/use-booking-events.ts, src/Web/MeAjudaAi.Web.Customer/hooks/use-provider-verification.ts, src/Web/MeAjudaAi.Web.Customer/components/providers/dashboard-client.tsx, src/Web/MeAjudaAi.Web.Customer/package.json, src/Web/package.json, package.json
Adiciona useSse genérico e hooks específicos; integra uso no dashboard; adiciona @microsoft/fetch-event-source; configura workspaces no root package.json.
Tests, CI & Tooling
tests/..., .github/workflows/ci-backend.yml, tools/api-collections/*, src/Web/e2e/*
Novos/ajustados testes para headers malformados, skip-logging, convenção de tópico, métricas e máscaras; integração espera rejeição (451) em casos malformados; Azurite CI pinado; gerador de collections removido; mocks/e2e atualizados.

Sequence Diagram(s)

sequenceDiagram
  participant Domain as "Domain Events\n(e.g. BookingCreated)"
  participant Handler as "EventHandler\n(BookingRealtimeEventsHandler)"
  participant SseHub as "SseHub<T>"
  participant Endpoint as "SSE Endpoint\n(GET /.../events)"
  participant Client as "Browser Hook\n(useBookingEvents / useProviderVerification)"

  rect rgba(200,220,255,0.5)
  Domain->>Handler: Publica evento de domínio
  Handler->>SseHub: PublishAsync(topic, DTO)
  end

  rect rgba(200,255,200,0.5)
  Endpoint->>SseHub: SubscribeAsync(topic)
  SseHub-->>Endpoint: Emite eventos via IAsyncEnumerable
  Endpoint->>Client: Envia mensagens SSE (data: {...})
  Client->>Client: Atualiza cache / mostra toast
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 Pulei no Sprint treze, span no lugar do split,

streams a cantar, tópicos prontos no kit,
cabeçalhos agora firmes, testes a conferir,
o hub dispara eventos e o cliente vai ouvir,
pequenos saltos, grande release — vamos seguir!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning O PR não possui descrição. O template exige seções como Summary, Problem, Solution, Changes, Impact, Testing e Checklist. Adicione uma descrição seguindo o template do repositório, documentando o resumo, problemas resolvidos, soluções implementadas, mudanças principais, impacto esperado e resultados de testes.
Docstring Coverage ⚠️ Warning Docstring coverage is 24.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed O título refere-se a uma sprint de implementações e otimizações, alinhado com as mudanças estruturantes presentes (refatorações span, SSE, testes). É específico e transmite claramente o propósito principal.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/extra-implementations-minimize-tech-debts

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 31 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/roadmap.md`:
- Around line 38-40: A seção que lista "**Sprint 13**" como concluída entra em
conflito com o bloco de status que ainda mostra "Em Execução"; update o bloco de
status (onde aparece "Em Execução") para refletir que Sprint 13 está "Concluída"
e inclua a data de término "28 Abr 2026" ou similar para consistência; localize
ocorrências de "Sprint 13" e do texto "Em Execução" no documento (por exemplo no
resumo de sprints e no bloco de status) e sincronize ambos para evitar
ambiguidade.

In `@docs/technical-debt.md`:
- Around line 25-28: The title "i18n Frontend Apps (Admin/Provider)" and the
description text referencing Admin/Provider/Customer are inconsistent; update
either the title or description so the scope matches: either change the title to
"i18n Frontend Apps (Admin/Provider/Customer)" or restrict the description to
only Admin/Provider and remove Customer mentions; ensure related symbols like
useTranslation, translation of labels via i18n, and mocks of i18next with
support for defaultValue are still accurately described after the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 194c370f-090a-4ad1-8b8c-2b14825abc6f

📥 Commits

Reviewing files that changed from the base of the PR and between f5f11da and 43a519a.

📒 Files selected for processing (3)
  • docs/roadmap-history.md
  • docs/roadmap.md
  • docs/technical-debt.md

Comment thread docs/roadmap.md
Comment thread docs/technical-debt.md Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
docs/roadmap.md (1)

38-40: Conteúdo preciso e bem documentado.

Os detalhes das Sprints 13, 12 e 11 estão corretos e alinhados com o histórico. A ferramenta de análise estática sugere variar a estrutura das frases (três bullets consecutivos começam com "**Sprint"), mas isso é puramente estilístico.

✍️ Sugestão opcional de estilo

Se desejar adicionar variação:

 *   **Sprint 13**: RabbitMQ Excellence (infraestrutura real com RabbitMqInfrastructureManager, deadlocks corrigidos, dispose seguro), i18n mocks para testes (Provider/Admin/Customer), fail-fast em DI de Messaging.
-*   **Sprint 12**: Bookings Module completo, Command Handlers (Reject/Complete), queries de listagem, automação com Domain Events, integração frontend de agenda.
+*   **Módulo de Bookings (Sprint 12)**: Implementação completa com Command Handlers (Reject/Complete), queries de listagem, automação com Domain Events, integração frontend de agenda.
 *   **Sprint 11**: Monetização completa (Checkout, Webhooks, Billing Portal, Renovação Automática), Localização i18n Frontend, Skeleton Loaders e cobertura de testes abrangente.

Mas não é necessário — a clareza já está garantida.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/roadmap.md` around lines 38 - 40, The three consecutive bullets all
start with the exact phrase "**Sprint X**", which is stylistically repetitive;
update the three list items referencing "**Sprint 13**", "**Sprint 12**" and
"**Sprint 11**" to vary their openings (for example: "In Sprint 13 we...",
"Completed in Sprint 12:", "Sprint 11 delivered...") or restructure one or two
items to lead with verbs or results so the bullets read less repetitive while
preserving the existing content and details.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/roadmap.md`:
- Around line 38-40: The three consecutive bullets all start with the exact
phrase "**Sprint X**", which is stylistically repetitive; update the three list
items referencing "**Sprint 13**", "**Sprint 12**" and "**Sprint 11**" to vary
their openings (for example: "In Sprint 13 we...", "Completed in Sprint 12:",
"Sprint 11 delivered...") or restructure one or two items to lead with verbs or
results so the bullets read less repetitive while preserving the existing
content and details.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cf1055e7-bd0a-44c3-84a6-35f3443ef303

📥 Commits

Reviewing files that changed from the base of the PR and between 43a519a and 9fffbf2.

📒 Files selected for processing (2)
  • docs/roadmap.md
  • docs/technical-debt.md
✅ Files skipped from review due to trivial changes (1)
  • docs/technical-debt.md

@frigini frigini changed the title docs: initialize technical debt tracking, roadmap history, and projec… Sprint 13.1: Extra implementations Apr 29, 2026
@frigini
Copy link
Copy Markdown
Owner Author

frigini commented Apr 29, 2026

@coderabbitai está é uma sprint para pequenas melhorias e otimizações, gostaria de saber, para iniciar, se é uma boa ideia otimizar partes do código com o uso de Span ou ReadOnlySpan ou é preciosismo? Se sim, se for recomendável, analise onde posso refatorar (em todo código) e elabore um plano.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Vou analisar o código C# do repositório para identificar os pontos com maior potencial de ganho com Span<T> / ReadOnlySpan<char>.


🏁 Script executed:

#!/bin/bash
echo "=== .Substring( usage ==="
rg --type cs -n '\.Substring\(' -A2 -B2

echo ""
echo "=== string.Split usage ==="
rg --type cs -n '\.Split\(' -A2 -B2

echo ""
echo "=== byte[] declarations and usages ==="
rg --type cs -n 'byte\[\]' -A2 -B2

echo ""
echo "=== IndexOf / LastIndexOf usage ==="
rg --type cs -n '\.(IndexOf|LastIndexOf)\(' -A2 -B2

echo ""
echo "=== string.Contains usage ==="
rg --type cs -n '\.Contains\(' -A2 -B2

echo ""
echo "=== MemoryStream / BinaryReader / BinaryWriter usage ==="
rg --type cs -n '(MemoryStream|BinaryReader|BinaryWriter)' -A2 -B2

echo ""
echo "=== Encoding.UTF8 / ASCII GetBytes / GetString ==="
rg --type cs -n 'Encoding\.(UTF8|ASCII|Unicode)\.(GetBytes|GetString)' -A2 -B2

echo ""
echo "=== string concatenation in loops or hot paths ==="
rg --type cs -n 'for.*\n.*\+=' -A2 -B2 || true

echo ""
echo "=== string.Format or interpolation in loops ==="
rg --type cs -n 'string\.Format' -A2 -B2

echo ""
echo "=== Array.Copy usage ==="
rg --type cs -n 'Array\.Copy' -A2 -B2

echo ""
echo "=== stackalloc usage (already using Span) ==="
rg --type cs -n 'stackalloc' -A2 -B2

echo ""
echo "=== AsSpan / AsMemory already used ==="
rg --type cs -n '(AsSpan|AsMemory|MemoryMarshal)' -A2 -B2

echo ""
echo "=== String parsing / TryParse patterns ==="
rg --type cs -n '(int\.TryParse|double\.TryParse|Guid\.TryParse|DateTime\.TryParse)' -A2 -B2

echo ""
echo "=== List all C# source files ==="
fd --type f --extension cs | grep -v '/obj/' | grep -v '/bin/' | sort

Length of output: 262901


@frigini Ótima pergunta para iniciar a sprint! A resposta curta é: não é preciosismo para os hot paths de middleware, mas seria para a maioria dos outros casos. Segue a análise completa.


Diagnóstico: Vale a pena usar Span<T> / ReadOnlySpan<char>?

✅ Já está sendo usado corretamente em dois lugares:

  • InspectAuthMiddleware.csstring.Concat(permString.AsSpan(0, 1000), "...")
  • RabbitMqDeadLetterService.csEncoding.UTF8.GetString(result.Body.Span) (3 ocorrências)

🔴 NÃO vale a pena (seria preciosismo):

Contexto Motivo
Código de teste (*Tests.cs) Zero impacto em produção
EF Core LINQ .Contains() / .Where() Traduzido para SQL, Span<T> não se aplica
Código de startup/migração (MigrationExtensions.cs, MigrationHostedService.cs, BaseDesignTimeDbContextFactory.cs) Executado uma vez, sem impacto mensurável
Reflexão sobre assemblies (ModuleDiscoveryHelper, etc.) Idem

🟡 Vale a pena (ROI real) — Plano por prioridade:

P0 — Middlewares (hot path: toda requisição HTTP)

1. RequestLoggingMiddleware.cs — Extração do IP do X-Forwarded-For

// ❌ Atual: aloca um array e uma nova string
return xForwardedFor.Split(',')[0].Trim();

// ✅ Proposto: zero allocation
var span = xForwardedFor.AsSpan();
var commaIndex = span.IndexOf(',');
return (commaIndex >= 0 ? span[..commaIndex] : span).Trim().ToString();

2. PermissionOptimizationMiddleware.cs — Detecção de segmento /admin/ no path

// ❌ Atual: aloca string[]
var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
return segments.Any(segment => string.Equals(segment, "admin", StringComparison.OrdinalIgnoreCase));

// ✅ Proposto: iteração zero-alloc
var span = path.AsSpan();
while (span.Length > 0)
{
    var slash = span.IndexOf('/');
    var segment = slash >= 0 ? span[..slash] : span;
    if (segment.Equals("admin", StringComparison.OrdinalIgnoreCase)) return true;
    if (slash < 0) break;
    span = span[(slash + 1)..];
}
return false;

3. BusinessMetricsMiddleware.cs — Verificação de path contendo /help-requests/

// ❌ Atual: string.Contains em cada requisição
path.Contains("/help-requests/") && path.EndsWith("/complete")

// ✅ Proposto: ReadOnlySpan<char>
var span = path.AsSpan();
span.Contains("/help-requests/".AsSpan(), StringComparison.Ordinal) 
    && span.EndsWith("/complete".AsSpan(), StringComparison.Ordinal)

P1 — Application Layer (fluxos de registro e cache)

4. RegisterCustomerCommandHandler.cs — Parsing de e-mail para gerar username

// ❌ Atual: 4x Split + 2x Substring → várias alocações intermediárias
var fullLocalPart = emailAsValueObject.Value.Split('@')[0];
var noTagLocalPart = fullLocalPart.Split('+')[0];
// ...
sanitizedLocalPart = sanitizedLocalPart.Substring(0, maxLocalPartLength);

// ✅ Proposto: ReadOnlySpan<char> do início ao fim
ReadOnlySpan<char> email = emailAsValueObject.Value.AsSpan();
var atIdx = email.IndexOf('@');
var local = atIdx >= 0 ? email[..atIdx] : email;
var plusIdx = local.IndexOf('+');
local = plusIdx >= 0 ? local[..plusIdx] : local;
if (local.Length > maxLocalPartLength) local = local[..maxLocalPartLength];
// Regex ainda precisa de string, mas ao menos reduz alocações intermediárias
var sanitized = SanitizationRegex().Replace(local.ToString(), "");

5. CacheMetrics.cs — Extração de prefixo de chave de cache (chamado a cada operação de cache)

// ❌ Atual:
var parts = key.Split(':');
if (parts.Length >= 2) { ... parts[0] ... }

// ✅ Proposto:
var span = key.AsSpan();
var colon = span.IndexOf(':');
var prefix = colon >= 0 ? span[..colon].ToString() : key;

P2 — Utilitários

6. PiiMaskingHelper.cs — Mascaramento de e-mail (usado em logs)

// ❌ Atual:
var parts = email.Split('@');
if (parts.Length != 2) return "***@***";

// ✅ Proposto:
var span = email.AsSpan();
var atIdx = span.IndexOf('@');
if (atIdx < 0) return "***@***";
var localLen = Math.Min(3, atIdx);
return $"{new string('*', localLen)}@{span[(atIdx + 1)..].ToString()}";

7. KeycloakService.cs — Extração do ID do usuário a partir do header Location

// ❌ Atual:
var segments = locationHeader.Split('/');
var keycloakUserId = segments[segments.Length - 1];

// ✅ Proposto:
var span = locationHeader.AsSpan();
var lastSlash = span.LastIndexOf('/');
var keycloakUserId = (lastSlash >= 0 ? span[(lastSlash + 1)..] : span).ToString();

📊 Resumo da decisão

Prioridade Arquivos Ganho esperado
P0 RequestLoggingMiddleware, PermissionOptimizationMiddleware, BusinessMetricsMiddleware Alto — zero-alloc em toda requisição
P1 CacheMetrics, RegisterCustomerCommandHandler Médio — reduz pressão no GC em fluxos frequentes
P2 PiiMaskingHelper, KeycloakService Baixo-Médio — legibilidade + pequena melhoria
Skip Testes, EF LINQ, startup/migration Não se aplica

Conclusão: Para os middlewares (P0) é definitivamente justificável e alinhado com uma sprint de otimização. Para P1 e P2 é opcional, mas o ganho é real e o esforço baixo. Quer que eu elabore o `` para alguma dessas prioridades?


🧠 Learnings used
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-01-16T21:50:23.593Z
Learning: For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups; prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:32:51.210Z
Learning: Além de manter logs em inglês, os comentários e a documentação XML (/// <summary>, <param>, etc.) devem ser escritos em português em todo o código MeAjudaAi.

Failed to handle agent chat message. Please try again.

@frigini
Copy link
Copy Markdown
Owner Author

frigini commented Apr 29, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain
Vou analisar o código C# do repositório para identificar os pontos com maior potencial de ganho com Span<T> / ReadOnlySpan<char>.

🏁 Script executed:

#!/bin/bash
echo "=== .Substring( usage ==="
rg --type cs -n '\.Substring\(' -A2 -B2

echo ""
echo "=== string.Split usage ==="
rg --type cs -n '\.Split\(' -A2 -B2

echo ""
echo "=== byte[] declarations and usages ==="
rg --type cs -n 'byte\[\]' -A2 -B2

echo ""
echo "=== IndexOf / LastIndexOf usage ==="
rg --type cs -n '\.(IndexOf|LastIndexOf)\(' -A2 -B2

echo ""
echo "=== string.Contains usage ==="
rg --type cs -n '\.Contains\(' -A2 -B2

echo ""
echo "=== MemoryStream / BinaryReader / BinaryWriter usage ==="
rg --type cs -n '(MemoryStream|BinaryReader|BinaryWriter)' -A2 -B2

echo ""
echo "=== Encoding.UTF8 / ASCII GetBytes / GetString ==="
rg --type cs -n 'Encoding\.(UTF8|ASCII|Unicode)\.(GetBytes|GetString)' -A2 -B2

echo ""
echo "=== string concatenation in loops or hot paths ==="
rg --type cs -n 'for.*\n.*\+=' -A2 -B2 || true

echo ""
echo "=== string.Format or interpolation in loops ==="
rg --type cs -n 'string\.Format' -A2 -B2

echo ""
echo "=== Array.Copy usage ==="
rg --type cs -n 'Array\.Copy' -A2 -B2

echo ""
echo "=== stackalloc usage (already using Span) ==="
rg --type cs -n 'stackalloc' -A2 -B2

echo ""
echo "=== AsSpan / AsMemory already used ==="
rg --type cs -n '(AsSpan|AsMemory|MemoryMarshal)' -A2 -B2

echo ""
echo "=== String parsing / TryParse patterns ==="
rg --type cs -n '(int\.TryParse|double\.TryParse|Guid\.TryParse|DateTime\.TryParse)' -A2 -B2

echo ""
echo "=== List all C# source files ==="
fd --type f --extension cs | grep -v '/obj/' | grep -v '/bin/' | sort

Length of output: 262901

@frigini Ótima pergunta para iniciar a sprint! A resposta curta é: não é preciosismo para os hot paths de middleware, mas seria para a maioria dos outros casos. Segue a análise completa.

Diagnóstico: Vale a pena usar Span<T> / ReadOnlySpan<char>?

✅ Já está sendo usado corretamente em dois lugares:

  • InspectAuthMiddleware.csstring.Concat(permString.AsSpan(0, 1000), "...")
  • RabbitMqDeadLetterService.csEncoding.UTF8.GetString(result.Body.Span) (3 ocorrências)

🔴 NÃO vale a pena (seria preciosismo):

Contexto Motivo
Código de teste (*Tests.cs) Zero impacto em produção
EF Core LINQ .Contains() / .Where() Traduzido para SQL, Span<T> não se aplica
Código de startup/migração (MigrationExtensions.cs, MigrationHostedService.cs, BaseDesignTimeDbContextFactory.cs) Executado uma vez, sem impacto mensurável
Reflexão sobre assemblies (ModuleDiscoveryHelper, etc.) Idem

🟡 Vale a pena (ROI real) — Plano por prioridade:

P0 — Middlewares (hot path: toda requisição HTTP)

1. RequestLoggingMiddleware.cs — Extração do IP do X-Forwarded-For

// ❌ Atual: aloca um array e uma nova string
return xForwardedFor.Split(',')[0].Trim();

// ✅ Proposto: zero allocation
var span = xForwardedFor.AsSpan();
var commaIndex = span.IndexOf(',');
return (commaIndex >= 0 ? span[..commaIndex] : span).Trim().ToString();

2. PermissionOptimizationMiddleware.cs — Detecção de segmento /admin/ no path

// ❌ Atual: aloca string[]
var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
return segments.Any(segment => string.Equals(segment, "admin", StringComparison.OrdinalIgnoreCase));

// ✅ Proposto: iteração zero-alloc
var span = path.AsSpan();
while (span.Length > 0)
{
    var slash = span.IndexOf('/');
    var segment = slash >= 0 ? span[..slash] : span;
    if (segment.Equals("admin", StringComparison.OrdinalIgnoreCase)) return true;
    if (slash < 0) break;
    span = span[(slash + 1)..];
}
return false;

3. BusinessMetricsMiddleware.cs — Verificação de path contendo /help-requests/

// ❌ Atual: string.Contains em cada requisição
path.Contains("/help-requests/") && path.EndsWith("/complete")

// ✅ Proposto: ReadOnlySpan<char>
var span = path.AsSpan();
span.Contains("/help-requests/".AsSpan(), StringComparison.Ordinal) 
    && span.EndsWith("/complete".AsSpan(), StringComparison.Ordinal)

P1 — Application Layer (fluxos de registro e cache)

4. RegisterCustomerCommandHandler.cs — Parsing de e-mail para gerar username

// ❌ Atual: 4x Split + 2x Substring → várias alocações intermediárias
var fullLocalPart = emailAsValueObject.Value.Split('@')[0];
var noTagLocalPart = fullLocalPart.Split('+')[0];
// ...
sanitizedLocalPart = sanitizedLocalPart.Substring(0, maxLocalPartLength);

// ✅ Proposto: ReadOnlySpan<char> do início ao fim
ReadOnlySpan<char> email = emailAsValueObject.Value.AsSpan();
var atIdx = email.IndexOf('@');
var local = atIdx >= 0 ? email[..atIdx] : email;
var plusIdx = local.IndexOf('+');
local = plusIdx >= 0 ? local[..plusIdx] : local;
if (local.Length > maxLocalPartLength) local = local[..maxLocalPartLength];
// Regex ainda precisa de string, mas ao menos reduz alocações intermediárias
var sanitized = SanitizationRegex().Replace(local.ToString(), "");

5. CacheMetrics.cs — Extração de prefixo de chave de cache (chamado a cada operação de cache)

// ❌ Atual:
var parts = key.Split(':');
if (parts.Length >= 2) { ... parts[0] ... }

// ✅ Proposto:
var span = key.AsSpan();
var colon = span.IndexOf(':');
var prefix = colon >= 0 ? span[..colon].ToString() : key;

P2 — Utilitários

6. PiiMaskingHelper.cs — Mascaramento de e-mail (usado em logs)

// ❌ Atual:
var parts = email.Split('@');
if (parts.Length != 2) return "***@***";

// ✅ Proposto:
var span = email.AsSpan();
var atIdx = span.IndexOf('@');
if (atIdx < 0) return "***@***";
var localLen = Math.Min(3, atIdx);
return $"{new string('*', localLen)}@{span[(atIdx + 1)..].ToString()}";

7. KeycloakService.cs — Extração do ID do usuário a partir do header Location

// ❌ Atual:
var segments = locationHeader.Split('/');
var keycloakUserId = segments[segments.Length - 1];

// ✅ Proposto:
var span = locationHeader.AsSpan();
var lastSlash = span.LastIndexOf('/');
var keycloakUserId = (lastSlash >= 0 ? span[(lastSlash + 1)..] : span).ToString();

📊 Resumo da decisão

Prioridade Arquivos Ganho esperado
P0 RequestLoggingMiddleware, PermissionOptimizationMiddleware, BusinessMetricsMiddleware Alto — zero-alloc em toda requisição
P1 CacheMetrics, RegisterCustomerCommandHandler Médio — reduz pressão no GC em fluxos frequentes
P2 PiiMaskingHelper, KeycloakService Baixo-Médio — legibilidade + pequena melhoria
Skip Testes, EF LINQ, startup/migration Não se aplica
Conclusão: Para os middlewares (P0) é definitivamente justificável e alinhado com uma sprint de otimização. Para P1 e P2 é opcional, mas o ganho é real e o esforço baixo. Quer que eu elabore o `` para alguma dessas prioridades?

🧠 Learnings used

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-01-16T21:50:23.593Z
Learning: For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups; prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:32:51.210Z
Learning: Além de manter logs em inglês, os comentários e a documentação XML (/// <summary>, <param>, etc.) devem ser escritos em português em todo o código MeAjudaAi.

Failed to handle agent chat message. Please try again.

@coderabbitai elabore o plano para P0 e P1 apenas, o mais completo possível (ou seja, cobrindo todo o projeto).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== RequestLoggingMiddleware.cs ==="
fd --type f -e cs --full-path -g '*RequestLoggingMiddleware*' | grep -v '/obj/' | grep -v '/bin/' | xargs cat -n

echo ""
echo "=== PermissionOptimizationMiddleware.cs ==="
fd --type f -e cs --full-path -g '*PermissionOptimizationMiddleware*' | grep -v '/obj/' | grep -v '/bin/' | xargs cat -n

echo ""
echo "=== BusinessMetricsMiddleware.cs ==="
fd --type f -e cs --full-path -g '*BusinessMetricsMiddleware*' | grep -v '/obj/' | grep -v '/bin/' | xargs cat -n

echo ""
echo "=== RegisterCustomerCommandHandler.cs ==="
fd --type f -e cs --full-path -g '*RegisterCustomerCommandHandler*' | grep -v '/obj/' | grep -v '/bin/' | xargs cat -n

echo ""
echo "=== CacheMetrics.cs ==="
fd --type f -e cs --full-path -g '*CacheMetrics*' | grep -v '/obj/' | grep -v '/bin/' | xargs cat -n

Length of output: 187


🏁 Script executed:

#!/bin/bash
echo "=== Finding files ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE '(RequestLoggingMiddleware|PermissionOptimizationMiddleware|BusinessMetricsMiddleware|RegisterCustomerCommandHandler|CacheMetrics)' | sort

echo ""
echo "=== RequestLoggingMiddleware ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -i 'RequestLoggingMiddleware' | xargs -I{} cat -n "{}"

echo ""
echo "=== PermissionOptimizationMiddleware ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -i 'PermissionOptimizationMiddleware' | xargs -I{} cat -n "{}"

echo ""
echo "=== BusinessMetricsMiddleware ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -i 'BusinessMetricsMiddleware' | xargs -I{} cat -n "{}"

echo ""
echo "=== RegisterCustomerCommandHandler ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -i 'RegisterCustomerCommandHandler' | xargs -I{} cat -n "{}"

echo ""
echo "=== CacheMetrics ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -i 'CacheMetrics' | xargs -I{} cat -n "{}"

Length of output: 108891


@frigini Aqui está o plano completo e detalhado cobrindo todos os 5 arquivos de P0 e P1, com diffs precisos baseados no código atual:

Plano: Refatoração Span<T> / ReadOnlySpan<char> — P0 + P1

  • P0.1 RequestLoggingMiddleware.cs — remover ToLowerInvariant() + Split(',') zero-alloc
  • P0.2 PermissionOptimizationMiddleware.cs — remover 2× ToLowerInvariant() + Split('/') → loop por segmento
  • P0.3 BusinessMetricsMiddleware.cs — remover ToLowerInvariant() + spans com OrdinalIgnoreCase
  • P1.1 RegisterCustomerCommandHandler.cs — 2× Split + 3× SubstringAsSpan + range operators
  • P1.2 CacheMetrics.csSplit(':')AsSpan + IndexOf


📊 Resumo do impacto por requisito

# Arquivo Alocações eliminadas Frequência
P0.1 RequestLoggingMiddleware string (ToLower) + 1× string[] + 1× string (Split+Trim) Cada request HTTP
P0.2 PermissionOptimizationMiddleware string (ToLower) + 1× string[] (Split) + 1× enumerador LINQ Cada request autenticado
P0.3 BusinessMetricsMiddleware string (ToLower) Cada request HTTP
P1.1 RegisterCustomerCommandHandler string[] (Split) + 3× string (Substring) Cada cadastro de cliente
P1.2 CacheMetrics string[] (Split) Cada operação de cache

Nota sobre ReadOnlySpan<char>.Equals: Utiliza a extensão MemoryExtensions.Equals(ReadOnlySpan<char>, ReadOnlySpan<char>, StringComparison) disponível desde .NET 5. Nota sobre ReadOnlySpan<char>.Contains: requer using System (já presente nos arquivos) pois é MemoryExtensions.Contains, disponível desde .NET 5.


🧠 Learnings used
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-20T22:04:25.820Z
Learning: MeAjudaAi (PRs futuros): para cobertura global, NÃO excluir *Configuration e *Extensions (há smoke tests exercitando-os); também não excluir *.Monitoring.*, MeAjudaAi.Shared.Jobs.* e MeAjudaAi.Shared.Mediator.*. Manter excluídos apenas Request/Response/Dto/DTO/IntegrationEvent e *DbContextFactory; Endpoints podem ficar excluídos globalmente, a menos que sejam white-listados por módulo com E2E (ex.: +MeAjudaAi.Modules.Payments.API.*Endpoint; -*Endpoint).

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-01-16T21:50:23.593Z
Learning: For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups; prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-19T15:37:29.889Z
Learning: MeAjudaAi: objetivo de cobertura global é ≥90% (linhas), mantendo thresholds 90/80 no CI; quando a meta estiver ameaçada, preferir excluir “glue/DTO” (Endpoints/Extensions/Options/IntegrationEvent/DbContextFactory) e adicionar testes de alto impacto, em vez de reduzir o threshold.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi Shared coverage: Classes de infraestrutura de messaging que dependem de broker RabbitMQ real (RabbitMqDeadLetterService, DeadLetterExtensions, FailedMessageInfo, FailureAttempt, EnvironmentMetadata) são candidatas a [ExcludeFromCodeCoverage] pois não são testáveis unitariamente. Utilitários puros (PhoneNumberValidator, PiiMaskingHelper, SlugHelper, UuidGenerator) devem ter testes unitários adicionados em vez de serem excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Providers/Tests/Unit/Application/Queries/GetProvidersByIdsQueryTests.cs:65-77
Timestamp: 2025-12-18T00:35:33.829Z
Learning: MeAjudaAi repo: Exceção à política de idioma para comentários — em testes, os cabeçalhos AAA (Arrange, Act, Assert) devem permanecer em inglês e não devem ser traduzidos. Comentários/XML continuam em português; logs em inglês; mensagens ao usuário (validações/APIs) em português.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi ci-backend.yml (linha 272): Para tornar os endpoints de um módulo visíveis na cobertura do ReportGenerator, adicionar o assembly antes de `-*Endpoint` no classfilters, ex: `+MeAjudaAi.Modules.Bookings.API.*Endpoint` (após `+MeAjudaAi.Modules.Payments.API.*Endpoint`). Isso é necessário apenas para módulos com cobertura E2E. Módulos sem E2E mantêm os endpoints excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi coverlet.runsettings: `ExcludeByAttribute` deve incluir `ExcludeFromCodeCoverage` (além de Obsolete,GeneratedCode,CompilerGenerated). Sem isso, todas as classes decoradas com [ExcludeFromCodeCoverage] continuam sendo instrumentadas pelo coverlet e contam como linhas não cobertas no relatório, reduzindo a cobertura global. Correção: `<ExcludeByAttribute>Obsolete,GeneratedCode,CompilerGenerated,ExcludeFromCodeCoverage</ExcludeByAttribute>`.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:32:51.210Z
Learning: Além de manter logs em inglês, os comentários e a documentação XML (/// <summary>, <param>, etc.) devem ser escritos em português em todo o código MeAjudaAi.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:26:37.409Z
Learning: MeAjudaAi logging policy: logs must be in English, comments may remain in Portuguese. Portuguese logs were additionally found in Users (API/Extensions.cs:83), Providers (API/Extensions.cs:74), ServiceCatalogs (API/Extensions.cs:78,86,93), and Locations (multiple clients/services lines listed). SearchProviders module currently has no Portuguese logs.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:09:54.585Z
Learning: In the MeAjudaAi codebase, when reviewing log messages in application handlers, verify they are in English. The file src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs had Portuguese logs that violated the English-only logging policy.

The requested changes have been implemented and a pull request has been created: View PR

@frigini frigini changed the title Sprint 13.1: Extra implementations Sprint 13.1: Implementações e otimizações extras Apr 29, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Shared/Authorization/Middleware/PermissionOptimizationMiddleware.cs (1)

123-132: ⚠️ Potential issue | 🟡 Minor

O preload ficou sensível a maiúsculas/minúsculas.

Ao parar de normalizar path, GetRequiredPermissionsForPath continua usando StartsWith/Contains case-sensitive. Com rotas válidas como /API/V1/Users/Profile, o middleware deixa de pré-carregar permissões e perde a otimização downstream. Troque os matches internos para OrdinalIgnoreCase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Authorization/Middleware/PermissionOptimizationMiddleware.cs`
around lines 123 - 132, O middleware perdeu normalização do path, então chamadas
dentro de GetRequiredPermissionsForPath ainda usam StartsWith/Contains
case-sensitive e falham com rotas maiúsculas; abra a implementação de
GetRequiredPermissionsForPath e alterar todas as comparações de string (ex.:
path.StartsWith(...), path.Contains(...)) para versões que usam
StringComparison.OrdinalIgnoreCase (ou String.Equals com OrdinalIgnoreCase) para
garantir matching case-insensitive ao avaliar permissões com base no path e no
método.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/GeographicRestrictionMiddleware.cs`:
- Around line 105-111: The parser in GeographicRestrictionMiddleware currently
splits locationHeader by the first '|' (headerSpan, separatorIndex) and assigns
locationCity and locationState from slices, which allows extra '|' characters
(e.g., "São Paulo|SP|Capital") producing an invalid state; update the logic in
the blocks around headerSpan/separatorIndex and the similar code at the later
region (lines ~208-230) to detect any additional '|' after separatorIndex (e.g.,
using IndexOf on the remainder or checking for another separator) and treat the
header as malformed if another '|' exists, returning the proper
rejection/validation path instead of accepting the full remainder as state; keep
using the same symbols locationHeader, headerSpan, separatorIndex, locationCity,
locationState and ensure consistent error handling when rejecting malformed
input.

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/RequestLoggingMiddleware.cs`:
- Around line 125-130: The current exclusion logic in RequestLoggingMiddleware
uses pathSpan.Contains for "/health", "/metrics", and "/swagger", which also
matches internal routes like "/api/v1/reports/health-summary"; change these
checks to match by prefix or segment instead (e.g., use
pathSpan.StartsWith("/health", StringComparison.OrdinalIgnoreCase) or split the
PathString into segments and compare the first segment) so only exact top-level
endpoints are skipped; update the method that evaluates pathSpan (the exclusion
check in RequestLoggingMiddleware) to use StartsWith/segment comparison
consistently and keep the StringComparison.OrdinalIgnoreCase semantics.

In `@src/Shared/Extensions/DocumentExtensions.cs`:
- Around line 18-27: Substitua a filtragem que usa char.IsDigit(c) pelo teste
ASCII explícito c is >= '0' and <= '9' nas rotinas que extraem dígitos (o trecho
com Span<char> digitsOnly e as outras duas validações correspondentes) para
garantir que apenas dígitos ASCII 0–9 sejam admitidos; atualize todas as
ocorrências relacionadas em DocumentExtensions (incluindo a rotina
IsValidDocument e qualquer lugar que monte digitsOnly ou faça cálculos com c -
'0') para usar esse teste e assim manter consistência com os cálculos dos
dígitos verificadores.

In `@src/Shared/Messaging/Options/MessageBusOptions.cs`:
- Line 25: The current fallback for a null/empty type.Namespace always returning
the literal "events.events" can cause topic collisions; update the fallback in
the logic that computes the Namespace (where type.Namespace is checked in
MessageBusOptions.cs) to include type.Name (e.g., use type.Name or combine both
like $"events.{type.Name}") instead of the fixed "events.events" so each type
gets a minimally unique topic namespace when Namespace is missing.
- Around line 22-35: TopicNamingConvention currently isn't used by the Rebus
wiring because Rebus resolves ITopicNameConvention (and uses
AttributeTopicNameConvention) instead of the Func in MessageBusOptions; create
an adapter class that implements ITopicNameConvention and delegates to
MessageBusOptions.TopicNamingConvention (e.g., class OptionsTopicNameConvention
: ITopicNameConvention { constructor(MessageBusOptions opts) ... }), then
register that implementation in DI so it is used by Rebus (or ensure
MessagingExtensions resolves and passes MessageBusOptions.TopicNamingConvention
into the existing AttributeTopicNameConvention registration) so the optimized
naming function is actually applied at runtime.

In `@src/Shared/Monitoring/BusinessMetricsMiddleware.cs`:
- Around line 56-57: The route matching allows non-versioned paths because
StartsWith("/api/v") matches things like "/api/validation"; update the
condition(s) that use pathSpan (in BusinessMetricsMiddleware, where pathSpan is
compared to "/api/users" or StartsWith("/api/v") && EndsWith("/users")) to
ensure the "v" is followed by one or more digits before treating it as a
versioned route. Replace the crude StartsWith check with a digit-aware check
(e.g., Regex.IsMatch(pathSpan, @"^/api/v\d+(?:/|$)", RegexOptions.IgnoreCase) or
manually verify characters after "/api/v" are digits) for each occurrence that
currently uses StartsWith("/api/v") (the conditions around
pathSpan.Equals("/api/users", ...) and the repeated checks on lines noted) so
only true /api/v{n} routes are matched.

In `@src/Shared/Utilities/PiiMaskingHelper.cs`:
- Around line 31-41: The current masking logic uses emailSpan, atIndex, name and
domain but doesn't treat empty local or domain parts as invalid; update the
method to check that both name.Length > 0 and domain.Length > 0 after splitting
(i.e., validate name and domain) and if either is empty return the safe fallback
"***@***"; otherwise proceed with the existing branch logic (keep the
name.Length <= 2 and name[..2] branches) so inputs like "@example.com", "foo@"
or "@" yield the fallback instead of partial masks.

---

Outside diff comments:
In `@src/Shared/Authorization/Middleware/PermissionOptimizationMiddleware.cs`:
- Around line 123-132: O middleware perdeu normalização do path, então chamadas
dentro de GetRequiredPermissionsForPath ainda usam StartsWith/Contains
case-sensitive e falham com rotas maiúsculas; abra a implementação de
GetRequiredPermissionsForPath e alterar todas as comparações de string (ex.:
path.StartsWith(...), path.Contains(...)) para versões que usam
StringComparison.OrdinalIgnoreCase (ou String.Equals com OrdinalIgnoreCase) para
garantir matching case-insensitive ao avaliar permissões com base no path e no
método.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7ac75c48-2d43-403b-b474-b8b916d92b0e

📥 Commits

Reviewing files that changed from the base of the PR and between 9fffbf2 and 80198c7.

📒 Files selected for processing (12)
  • docs/roadmap.md
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/GeographicRestrictionMiddleware.cs
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/RequestLoggingMiddleware.cs
  • src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs
  • src/Modules/Users/Infrastructure/Identity/Keycloak/KeycloakService.cs
  • src/Shared/Authorization/Middleware/PermissionOptimizationMiddleware.cs
  • src/Shared/Caching/CacheMetrics.cs
  • src/Shared/Extensions/DocumentExtensions.cs
  • src/Shared/Messaging/Options/MessageBusOptions.cs
  • src/Shared/Monitoring/BusinessMetricsMiddleware.cs
  • src/Shared/Utilities/PhoneNumberValidator.cs
  • src/Shared/Utilities/PiiMaskingHelper.cs
✅ Files skipped from review due to trivial changes (1)
  • src/Modules/Users/Infrastructure/Identity/Keycloak/KeycloakService.cs

Comment thread src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/RequestLoggingMiddleware.cs Outdated
Comment thread src/Shared/Extensions/DocumentExtensions.cs
Comment thread src/Shared/Messaging/Options/MessageBusOptions.cs
Comment thread src/Shared/Messaging/Options/MessageBusOptions.cs Outdated
Comment thread src/Shared/Monitoring/BusinessMetricsMiddleware.cs Outdated
Comment thread src/Shared/Utilities/PiiMaskingHelper.cs
…s, PII masking, and request handling middlewares
@frigini
Copy link
Copy Markdown
Owner Author

frigini commented Apr 29, 2026

@coderabbitai próximo tópico: a paginação implementada em todos os projetos foi do tipo offset, mas é sabido que a cursor tem melhor performance e é mais estável. Recomenda fazer mais esta melhoria (alterar paginação offset para cursor)? Qual seria o impacto (quantidade de mudança) versus o benefício? Teria que alterar os projetos frontend também?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Pagination Request/Response types ==="
rg --type cs -n -i '(PagedRequest|PagedResult|PagedResponse|PaginatedResult|PaginationRequest|CursorPage|OffsetPage)' -A3 -B3

echo ""
echo "=== Skip/Take usage (offset pattern) ==="
rg --type cs -n '\.Skip\(|\.Take\(' -A2 -B2

echo ""
echo "=== PageNumber / PageSize / Page / Offset ==="
rg --type cs -n -i '(PageNumber|PageSize|\.Offset|TotalPages|TotalCount|TotalItems)' -A2 -B2 | head -300

echo ""
echo "=== Frontend pagination (TypeScript/JS) ==="
rg --type ts --type tsx -n -i '(page|pageSize|pageNumber|cursor|offset|pagination)' -A2 -B2 2>/dev/null | head -200
rg -n -i '(page|pageSize|pageNumber|cursor|offset|pagination)' --glob '*.tsx' --glob '*.ts' --glob '*.js' --glob '*.jsx' -A2 -B2 | head -200

echo ""
echo "=== All Paged/Pagination model files ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE '(paged|pagination|paginated|cursor)' | sort

echo ""
echo "=== Queries using pagination ==="
rg --type cs -n -i 'IPagedQuery\|IPagedRequest\|PagedQuery\|IPaged' -A3 -B3

echo ""
echo "=== EF Core pagination queries ==="
rg --type cs -n 'Skip|Take' -A3 -B3 | grep -v '//.*Skip\|//.*Take' | head -300

Length of output: 209715


@frigini Ótima pergunta estratégica. Fiz a varredura completa do repositório. A conclusão é: não recomendável para esta sprint, e possivelmente nunca para a maioria dos endpoints do projeto. Segue a análise completa.


Cursor vs Offset: análise de custo × benefício para o MeAjudaAi

📊 Escopo atual de offset pagination (o que teria que mudar)

Camada Arquivos afetados
Contratos compartilhados PagedResult<T>, PagedResponse<T> (+ cópia em IProvidersApi.cs)
Repositórios UserRepository, ProviderRepository, ProviderQueryService, BookingRepository, CommunicationLogRepository, ReviewRepository, SearchableProviderRepository (SQL raw com Dapper)
Query Handlers GetUsersQueryHandler, GetProvidersQueryHandler, GetBookingsByClientQueryHandler, GetBookingsByProviderQueryHandler, SearchProvidersQueryHandler, CommunicationsModuleApi
Endpoints GetUsersEndpoint, GetProvidersEndpoint, GetMyBookingsEndpoint, GetProviderBookingsEndpoint, SearchProvidersEndpoint
Frontend Admin use-dashboard.ts (loop com pageNumber/totalPages), mocks E2E (totalPages/totalCount)
Client SDK IProvidersApi.cs (pageNumber, pageSize como [Query])
Testes Todos os testes de repositório, handler, integração e E2E que validam PageNumber, PageSize, TotalItems, TotalPages

É uma mudança de API breaking que exige deploy coordenado backend + frontend.


⚖️ Benefícios do cursor — e por que não se aplicam aqui

Argumento pró-cursor Realidade do MeAjudaAi
Evita "drift" (itens inseridos/deletados entre páginas) Listas admin de usuários/prestadores têm baixíssima mutação durante navegação; usuário dificilmente percebe
Melhor performance em páginas profundas (OFFSET 10000 é custoso) Datasets por entidade são limitados: provedores por admin, agendamentos por cliente. Ninguém chega à página 50
Ideal para scroll infinito Nenhuma UI de scroll infinito implementada atualmente
Estabilidade de resultados Relevante para feeds sociais em tempo real — não é o caso aqui

🔴 Problemas específicos que a migração criaria

1. SearchProviders usa PostGIS + Dapper SQL bruto:

OFFSET `@Skip` LIMIT `@Take`

O cursor aqui seria composto por (distância, subscriptionTier, id) — cálculo não-trivial com coordenadas geográficas. Seria a refatoração mais complexa do projeto.

2. Frontend use-dashboard.ts depende de totalPages:

// use-dashboard.ts — loop para buscar todos os providers
do {
  const response = await apiProvidersGet2({ query: { pageNumber: currentPage, pageSize: 100 } });
  totalPages = data.totalPages ?? 1;
  currentPage++;
} while (currentPage <= totalPages);

Cursor pagination não fornece totalPages sem um COUNT(*) separado — eliminaria o benefício e quebraria esse fluxo.

3. TotalItems é exibido na UI (PagedResult.TotalItems, PagedResponse.TotalCount) — cursor puro não fornece contagem total de forma eficiente.


✅ Veredito

Critério Resultado
ROI de performance Baixo — datasets são pequenos e bounded por entidade
Impacto da mudança Muito alto — 6 módulos + frontend + contratos + testes
Risco de regressão Alto — breaking change de API pública
Adequação ao modelo de dados Baixo — geo-search e admin listings não combinam com cursor simples
Recomendação Não recomendável

Conclusão: É preciosismo para o estágio atual do projeto. A paginação offset com Skip/Take é perfeitamente adequada para os volumes e use cases do MeAjudaAi. O único cenário futuro onde revisitar faria sentido seria se o CommunicationLogRepository crescer para milhões de registros — e mesmo assim, apenas nesse endpoint isolado.

💡 Alternativa de baixo custo e alto impacto: se quiser melhorar performance de paginação, adicionar índices compostos em (CreatedAt, Id) nas tabelas paginadas (já garante ORDER BY sem sort em memória) seria muito mais efetivo e sem nenhum risco de breaking change.


🧠 Learnings used
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-20T22:04:25.820Z
Learning: MeAjudaAi (PRs futuros): para cobertura global, NÃO excluir *Configuration e *Extensions (há smoke tests exercitando-os); também não excluir *.Monitoring.*, MeAjudaAi.Shared.Jobs.* e MeAjudaAi.Shared.Mediator.*. Manter excluídos apenas Request/Response/Dto/DTO/IntegrationEvent e *DbContextFactory; Endpoints podem ficar excluídos globalmente, a menos que sejam white-listados por módulo com E2E (ex.: +MeAjudaAi.Modules.Payments.API.*Endpoint; -*Endpoint).

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi ci-backend.yml (linha 272): Para tornar os endpoints de um módulo visíveis na cobertura do ReportGenerator, adicionar o assembly antes de `-*Endpoint` no classfilters, ex: `+MeAjudaAi.Modules.Bookings.API.*Endpoint` (após `+MeAjudaAi.Modules.Payments.API.*Endpoint`). Isso é necessário apenas para módulos com cobertura E2E. Módulos sem E2E mantêm os endpoints excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi Shared coverage: Classes de infraestrutura de messaging que dependem de broker RabbitMQ real (RabbitMqDeadLetterService, DeadLetterExtensions, FailedMessageInfo, FailureAttempt, EnvironmentMetadata) são candidatas a [ExcludeFromCodeCoverage] pois não são testáveis unitariamente. Utilitários puros (PhoneNumberValidator, PiiMaskingHelper, SlugHelper, UuidGenerator) devem ter testes unitários adicionados em vez de serem excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi Bookings.API: Classes não-endpoint dentro do assembly da API (Extensions, BookingsEndpoints, ProviderAuthorizationResult, ProviderAuthorizationResultExtensions) não são capturadas pelo classfilter `+MeAjudaAi.Modules.Bookings.API.*Endpoint` e devem receber [ExcludeFromCodeCoverage] individualmente (são glue/wiring). Já `ProviderAuthorizationResolver` contém lógica real de autorização e deve ter testes unitários próprios.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi coverlet.runsettings: `ExcludeByAttribute` deve incluir `ExcludeFromCodeCoverage` (além de Obsolete,GeneratedCode,CompilerGenerated). Sem isso, todas as classes decoradas com [ExcludeFromCodeCoverage] continuam sendo instrumentadas pelo coverlet e contam como linhas não cobertas no relatório, reduzindo a cobertura global. Correção: `<ExcludeByAttribute>Obsolete,GeneratedCode,CompilerGenerated,ExcludeFromCodeCoverage</ExcludeByAttribute>`.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-19T15:37:29.889Z
Learning: MeAjudaAi: objetivo de cobertura global é ≥90% (linhas), mantendo thresholds 90/80 no CI; quando a meta estiver ameaçada, preferir excluir “glue/DTO” (Endpoints/Extensions/Options/IntegrationEvent/DbContextFactory) e adicionar testes de alto impacto, em vez de reduzir o threshold.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-01-16T21:50:23.593Z
Learning: For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups; prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Providers/Tests/Unit/Application/Queries/GetProvidersByIdsQueryTests.cs:65-77
Timestamp: 2025-12-18T00:35:33.829Z
Learning: MeAjudaAi repo: Exceção à política de idioma para comentários — em testes, os cabeçalhos AAA (Arrange, Act, Assert) devem permanecer em inglês e não devem ser traduzidos. Comentários/XML continuam em português; logs em inglês; mensagens ao usuário (validações/APIs) em português.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:26:37.409Z
Learning: MeAjudaAi logging policy: logs must be in English, comments may remain in Portuguese. Portuguese logs were additionally found in Users (API/Extensions.cs:83), Providers (API/Extensions.cs:74), ServiceCatalogs (API/Extensions.cs:78,86,93), and Locations (multiple clients/services lines listed). SearchProviders module currently has no Portuguese logs.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:18:43.521Z
Learning: In the MeAjudaAi codebase, Portuguese log messages were found across 9 files: Documents module handlers/ModuleApi/Jobs (35+ logs), Shared Jobs/Seeding/RabbitMq (7 logs), and integration test infrastructure (5 logs). The Documents module is the primary violator with most Portuguese logs requiring translation to English per the logging policy.

@frigini
Copy link
Copy Markdown
Owner Author

frigini commented Apr 29, 2026

@coderabbitai o .net10 introduziu uma nova feature, SSE (serverSentEvents), pode ser aplicado neste projeto? Onde poderia ser utilizado?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/Shared/Extensions/DocumentExtensions.cs (1)

130-142: ⚠️ Potential issue | 🟠 Major

Evite Math.Abs(BitConverter.ToInt32(...)) % 10 na geração de dígitos.

Na linha 140, Math.Abs(int.MinValue) permanece negativo em C#, então o módulo pode gerar valor < 0 e produzir caractere inválido em document[i]. Além disso, há alocação de byte[4] por iteração.

💡 Sugestão de ajuste
-        using var random = RandomNumberGenerator.Create();
         var document = new char[baseLength + 2];
@@
-            var bytes = new byte[4];
-            random.GetBytes(bytes);
-            var randomNumber = Math.Abs(BitConverter.ToInt32(bytes, 0)) % 10;
+            var randomNumber = RandomNumberGenerator.GetInt32(10);
             document[i] = (char)('0' + randomNumber);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Extensions/DocumentExtensions.cs` around lines 130 - 142, In
GenerateValidDocument replace the current random digit generation that uses
Math.Abs(BitConverter.ToInt32(...)) % 10 and per-iteration byte[4] allocations
with RandomNumberGenerator.GetInt32(0, 10) (or a single buffer reused) to
produce a safe 0-9 integer without risk of int.MinValue and without allocating a
new byte[] each loop; update the loop that sets document[i] to use the GetInt32
result and remove the byte[] and BitConverter usage to eliminate negative modulo
bugs and reduce allocations.
src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/GeographicRestrictionMiddleware.cs (1)

103-127: ⚠️ Potential issue | 🔴 Critical

Não deixe X-User-Location malformado cair no fail-open.

Hoje só o caso com segundo | retorna o sentinel de rejeição. Valores presentes, mas inválidos, como City|, |State ou City, ainda passam para o fallback e podem liberar a request indevidamente, enfraquecendo a restrição geográfica.

💡 Ajuste sugerido
         if (context.Request.Headers.TryGetValue("X-User-Location", out var locationHeader))
         {
             var headerSpan = locationHeader.ToString().AsSpan();
             var separatorIndex = headerSpan.IndexOf('|');
-            
-            if (separatorIndex >= 0)
+
+            if (separatorIndex < 0)
+            {
+                return (string.Empty, string.Empty);
+            }
+
+            if (separatorIndex >= 0)
             {
                 var remainder = headerSpan[(separatorIndex + 1)..];
                 if (remainder.IndexOf('|') >= 0)
                 {
-                    // Malformed header, more than one separator. 
-                    // Return empty string instead of null to signal malformed but present.
                     return (string.Empty, string.Empty);
                 }
 
                 var locationCity = headerSpan[..separatorIndex].Trim().ToString();
                 var locationState = remainder.Trim().ToString();
 
-                // Rejeitar entradas malformadas onde city ou state estão vazios
-                // Exemplos rejeitados: "City|", "|State", "City| ", " |State"
-                if (!string.IsNullOrEmpty(locationCity) && !string.IsNullOrEmpty(locationState))
+                if (string.IsNullOrEmpty(locationCity) || string.IsNullOrEmpty(locationState))
                 {
-                    return (locationCity, locationState);
+                    return (string.Empty, string.Empty);
                 }
+
+                return (locationCity, locationState);
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/GeographicRestrictionMiddleware.cs`
around lines 103 - 127, A rotina de parsing em GeographicRestrictionMiddleware
atualmente só trata o caso de múltiplos '|' como malformado; ajuste o parsing do
header X-User-Location (no método que lê headerSpan / separatorIndex) para
também considerar malformados os casos sem separador, ou com city/state vazios
ou contendo apenas whitespace (ex.: "City|", "|State", "City" ou " |State");
após extrair locationCity e locationState (usando Trim()), se separatorIndex < 0
ou se qualquer um dos dois valores for vazio/whitespace, retorne o sentinel de
rejeição (string.Empty, string.Empty) para impedir o fail-open, caso contrário
continue retornando (locationCity, locationState).
🧹 Nitpick comments (1)
tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs (1)

10-23: O nome do teste promete “delegação”, mas a asserção cobre só a convenção default

Na Line 10, o nome indica validação de delegação para MessageBusOptions.TopicNamingConvention; porém nas Lines 13-22 você valida apenas o resultado padrão. Sugiro injetar um delegate customizado para garantir que a delegação ocorre de fato.

Diff sugerido
 [Fact]
 public void GetTopic_Should_DelegateToOptionsTopicNamingConvention()
 {
     // Arrange
-    var options = new MessageBusOptions();
+    var options = new MessageBusOptions
+    {
+        TopicNamingConvention = _ => "custom.events"
+    };
     var sut = new OptionsTopicNameConvention(options);
     var eventType = typeof(TestEvent);

     // Act
     var result = sut.GetTopic(eventType);

     // Assert
-    // Default convention should be "{lastPart}.events"
-    result.Should().Be("conventions.events");
+    result.Should().Be("custom.events");
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs`
around lines 10 - 23, The test
GetTopic_Should_DelegateToOptionsTopicNamingConvention currently only asserts
the default behavior; update it to verify delegation to
MessageBusOptions.TopicNamingConvention by assigning a custom
TopicNamingConvention delegate on the MessageBusOptions instance, e.g. one that
returns a distinct string (or captures the incoming Type) and then assert that
OptionsTopicNameConvention.GetTopic(eventType) returns that custom value (or
that the delegate received the TestEvent type). Update references in the test to
MessageBusOptions.TopicNamingConvention, OptionsTopicNameConvention, GetTopic
and TestEvent accordingly so the test proves real delegation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/GeographicRestrictionMiddleware.cs`:
- Around line 113-114: No arquivo da classe GeographicRestrictionMiddleware,
substitua os dois comentários inline em inglês por versões em português mantendo
o sentido original: explique que o cabeçalho está malformado por conter mais de
um separador e que deve ser retornada string vazia em vez de null para sinalizar
"malformado, mas presente". Atualize os comentários próximos ao trecho que lida
com parsing do header (no bloco comentado que atualmente começa com "Malformed
header...") para português e mantenha o contexto técnico intacto.

In `@src/Shared/Monitoring/BusinessMetricsMiddleware.cs`:
- Around line 57-59: O atual check permite matches parciais (Ex.:
"/api/v1/admin/users"); ajuste para aceitar somente rota exata "users" com ou
sem versão: quando avaliar, mantenha a condição pathSpan.Equals("/api/users",
StringComparison.OrdinalIgnoreCase) para rota não versionada e substitua a
combinação VersionedApiPattern.IsMatch(pathValue) && pathSpan.EndsWith("/users",
...) por um teste que faça match completo contra a versão + recurso (por exemplo
extrair o resultado de VersionedApiPattern.Match(pathValue) e verificar que o
grupo/segmento de recurso é exatamente "users" e que o match abrange toda a
string, ou usar um regex equivalente ^/api/v\d+/users$), aplicando a mesma
correção para as ocorrências que usam VersionedApiPattern/pathValue/pathSpan (as
três outras posições mencionadas) para garantir apenas correspondência exata do
endpoint "users".

In
`@tests/MeAjudaAi.ApiService.Tests/Unit/Middlewares/RequestLoggingMiddlewareTests.cs`:
- Line 336: Padronize o comentário em RequestLoggingMiddlewareTests (no arquivo
RequestLoggingMiddlewareTests.cs) na linha que atualmente diz "Verify that
logging DID happen (at least the start log)" para português; localize o
comentário dentro da classe RequestLoggingMiddlewareTests (no método de teste
correspondente) e substitua por uma versão em pt-BR concisa, por exemplo
"Verifica que o logging ocorreu (pelo menos o log de início)", mantendo o
significado e a clareza do teste.

In
`@tests/MeAjudaAi.Shared.Tests/Unit/Monitoring/BusinessMetricsMiddlewareTests.cs`:
- Around line 186-187: O comentário acima da variável registrationMetrics
("Should NOT record any business-specific metrics if the path is not recognized
as versioned") está em inglês e deve ser padronizado para Português; atualize
esse comentário para uma versão em Português (por exemplo: "Não deve registrar
métricas específicas de negócio se o caminho não for reconhecido como
versionado") mantendo intactos cabeçalhos AAA em inglês nos testes; procure o
comentário que precede a variável registrationMetrics no teste em
BusinessMetricsMiddlewareTests.cs e substitua apenas o texto do comentário.

---

Outside diff comments:
In
`@src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/GeographicRestrictionMiddleware.cs`:
- Around line 103-127: A rotina de parsing em GeographicRestrictionMiddleware
atualmente só trata o caso de múltiplos '|' como malformado; ajuste o parsing do
header X-User-Location (no método que lê headerSpan / separatorIndex) para
também considerar malformados os casos sem separador, ou com city/state vazios
ou contendo apenas whitespace (ex.: "City|", "|State", "City" ou " |State");
após extrair locationCity e locationState (usando Trim()), se separatorIndex < 0
ou se qualquer um dos dois valores for vazio/whitespace, retorne o sentinel de
rejeição (string.Empty, string.Empty) para impedir o fail-open, caso contrário
continue retornando (locationCity, locationState).

In `@src/Shared/Extensions/DocumentExtensions.cs`:
- Around line 130-142: In GenerateValidDocument replace the current random digit
generation that uses Math.Abs(BitConverter.ToInt32(...)) % 10 and per-iteration
byte[4] allocations with RandomNumberGenerator.GetInt32(0, 10) (or a single
buffer reused) to produce a safe 0-9 integer without risk of int.MinValue and
without allocating a new byte[] each loop; update the loop that sets document[i]
to use the GetInt32 result and remove the byte[] and BitConverter usage to
eliminate negative modulo bugs and reduce allocations.

---

Nitpick comments:
In
`@tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs`:
- Around line 10-23: The test
GetTopic_Should_DelegateToOptionsTopicNamingConvention currently only asserts
the default behavior; update it to verify delegation to
MessageBusOptions.TopicNamingConvention by assigning a custom
TopicNamingConvention delegate on the MessageBusOptions instance, e.g. one that
returns a distinct string (or captures the incoming Type) and then assert that
OptionsTopicNameConvention.GetTopic(eventType) returns that custom value (or
that the delegate received the TestEvent type). Update references in the test to
MessageBusOptions.TopicNamingConvention, OptionsTopicNameConvention, GetTopic
and TestEvent accordingly so the test proves real delegation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0b2590c8-d08d-499d-9eea-e429fa742282

📥 Commits

Reviewing files that changed from the base of the PR and between 80198c7 and 660da09.

📒 Files selected for processing (13)
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/GeographicRestrictionMiddleware.cs
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/RequestLoggingMiddleware.cs
  • src/Shared/Authorization/Middleware/PermissionOptimizationMiddleware.cs
  • src/Shared/Extensions/DocumentExtensions.cs
  • src/Shared/Messaging/MessagingExtensions.cs
  • src/Shared/Messaging/Options/MessageBusOptions.cs
  • src/Shared/Monitoring/BusinessMetricsMiddleware.cs
  • src/Shared/Utilities/PiiMaskingHelper.cs
  • tests/MeAjudaAi.ApiService.Tests/Unit/Middlewares/GeographicRestrictionMiddlewareTests.cs
  • tests/MeAjudaAi.ApiService.Tests/Unit/Middlewares/RequestLoggingMiddlewareTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Monitoring/BusinessMetricsMiddlewareTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Utilities/PiiMaskingHelperTests.cs
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/RequestLoggingMiddleware.cs
  • src/Shared/Messaging/Options/MessageBusOptions.cs
  • src/Shared/Authorization/Middleware/PermissionOptimizationMiddleware.cs

Comment thread src/Shared/Monitoring/BusinessMetricsMiddleware.cs Outdated
Comment thread tests/MeAjudaAi.Shared.Tests/Unit/Monitoring/BusinessMetricsMiddlewareTests.cs Outdated
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@frigini Excelente tópico! A resposta é sim, é aplicável e agrega valor real — mas com um caveat importante sobre autenticação. Segue a análise completa:


.NET 10 SSE (TypedResults.ServerSentEvents) — Análise para o MeAjudaAi

O que é a nova feature

O ASP.NET Core 10 passou a suportar nativamente o retorno de ServerSentEvents via a API TypedResults.ServerSentEvents, disponível tanto em Minimal APIs quanto em controllers. Internamente, ela reutiliza o tipo SseItem<T> do namespace System.Net.ServerSentEvents (adicionado ao BCL no .NET 9 para parsing client-side) e o framework cuida do content-type text/event-stream, do formato do wire, da serialização JSON, do keep-alive da conexão e do cancelamento — basta fornecer um IAsyncEnumerable<T>.

O ASP.NET Core 10 introduz SSE nativo como uma alternativa leve e HTTP-nativa ao SignalR para atualizações simples unidirecionais em tempo real, como dashboards e notificações.


Onde pode ser aplicado no MeAjudaAi

O projeto não usa SignalR nem WebSockets hoje. Considerando os módulos existentes (Bookings, Payments, HelpRequests, Communications, Admin dashboard), há 4 candidatos com ROI real:

# Caso de uso Módulo Impacto
1 Status de agendamento em tempo real Bookings 🔴 Alto
2 Resultado de pagamento assíncrono Payments 🔴 Alto
3 Notificação de ajuda aceita/concluída HelpRequests 🟡 Médio
4 Métricas do painel admin (substituir polling) Admin dashboard 🟡 Médio

Arquitetura proposta (padrão recomendado para todos os casos)

O padrão ideal é um IHostedService que produz eventos em um Channel<SseItem<T>> e um endpoint Minimal API que expõe esse canal como SSE — isso dá back pressure e um único produtor alimentando vários consumidores.

IntegrationEvent (RabbitMQ) → IHostedService → Channel<SseItem<T>> → SSE Endpoint → Frontend

Exemplo (Bookings — status ao vivo):

// Endpoint Minimal API
app.MapGet("/api/v1/bookings/{id}/status-stream",
    (Guid id, BookingStatusChannel channel, CancellationToken ct) =>
    {
        async IAsyncEnumerable<SseItem<BookingStatusDto>> StreamStatus()
        {
            await foreach (var update in channel.ReadForBookingAsync(id, ct))
                yield return SseItem.Create(update, eventType: "booking-status");
        }
        return TypedResults.ServerSentEvents(StreamStatus());
    })
    .RequireAuthorization();

⚠️ Caveat crítico: autenticação JWT com EventSource

Não há suporte para adição de headers customizados nas requisições do EventSource nativo do browser — portanto não é possível usar a API nativa para aplicações que enviam tokens no header.

Como o MeAjudaAi usa Keycloak com Bearer tokens, isso é um problema direto. As soluções práticas são:

Opção Como funciona Trade-off
@microsoft/fetch-event-source (recomendada) Lib npm baseada em fetch com suporte a headers Adiciona 1 dependência leve
Token de curta duração em query string Endpoint /sse-token retorna token temporário (30s) Mais complexo no backend
Cookie-based Auth via cookie HttpOnly junto ao JWT Requer mudança na infra de auth

A biblioteca fetch-event-source e a Fetch API permitem SSE com método POST, headers customizados e esquemas de autenticação complexos.

Exemplo frontend (React + @microsoft/fetch-event-source):

import { fetchEventSource } from '@microsoft/fetch-event-source';

function useBookingStatusStream(bookingId: string, token: string) {
  useEffect(() => {
    const ctrl = new AbortController();
    fetchEventSource(`/api/v1/bookings/${bookingId}/status-stream`, {
      headers: { Authorization: `Bearer ${token}` },
      signal: ctrl.signal,
      onmessage(ev) {
        if (ev.event === 'booking-status') {
          const status = JSON.parse(ev.data);
          setBookingStatus(status);
        }
      },
    });
    return () => ctrl.abort();
  }, [bookingId, token]);
}

📊 Custo × benefício

Critério Avaliação
Esforço de implementação Médio — backend relativamente simples; frontend precisa da lib fetch-event-source
Benefício para UX Alto — elimina polling, atualização instantânea de status de bookings e pagamentos
Complexidade operacional Baixo-Médio — conexões HTTP longas (verificar timeout em load balancers/proxies)
Escopo da mudança Aditivo — não quebra nada existente, novos endpoints SSE ao lado dos REST existentes
Compatibilidade com Minimal APIs ✅ — o projeto já usa o padrão
Risco Baixo — SSE é sobre HTTP simples, não há upgrade de protocolo

Recomendação

O SSE no .NET 10 é a solução ideal para atualizações simples e unidirecionais como dashboards, sinos de notificação e barras de progresso. É leve, HTTP-nativo e fácil de proteger com o middleware existente. Para o MeAjudaAi, o caso mais impactante seria status de agendamentos em tempo real (Bookings), que hoje provavelmente requer polling ou atualização manual. Pode ser entregue de forma aditiva, sem risco de regressão.

💡 Ponto de atenção operacional: Configure o timeout de leitura do load balancer/proxy para valores altos (ex.: 300s+) nos endpoints SSE, caso contrário a conexão será fechada antes de qualquer evento ser enviado.

Quer que eu elabore o plano de implementação para algum dos casos de uso específicos?


🏁 Script executed:

#!/bin/bash
echo "=== SignalR usage ==="
rg --type cs -n -i '(SignalR|IHubContext|Hub\b|WebSocket)' -A3 -B3

echo ""
echo "=== Notification/Event push patterns ==="
rg --type cs -n -i '(notification|INotif|push|broadcast|realtime|real-time)' -A3 -B3 | head -300

echo ""
echo "=== Booking status change events ==="
rg --type cs -n -i '(BookingStatus|StatusChanged|StatusUpdate|BookingUpdated|BookingCreated|BookingConfirmed|BookingCompleted|BookingCancelled)' -A3 -B3 | head -300

echo ""
echo "=== Integration events (domain events that could feed SSE) ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE '(IntegrationEvent|DomainEvent)' | sort

echo ""
echo "=== Polling patterns on frontend ==="
rg -n -i '(setInterval|polling|refetch|useQuery.*refetch|refresh)' --glob '*.ts' --glob '*.tsx' --glob '*.js' --glob '*.jsx' -A3 -B3 | head -300

echo ""
echo "=== Communication/Chat/Message real-time ==="
rg --type cs -n -i '(CommunicationLog|Message|Chat|SendMessage)' -A3 -B3 | head -300

echo ""
echo "=== Payment/Order status streaming candidates ==="
rg --type cs -n -i '(PaymentStatus|OrderStatus|PaymentUpdated|PaymentCompleted|PaymentFailed)' -A3 -B3 | head -200

echo ""
echo "=== Help request status events ==="
rg --type cs -n -i '(HelpRequest|ServiceRequest|RequestStatus|RequestCompleted|RequestCreated)' -A3 -B3 | head -200

echo ""
echo "=== Current target framework ==="
fd -e csproj | xargs grep -l 'TargetFramework' | head -5 | xargs grep 'TargetFramework'

Length of output: 178239


🏁 Script executed:

#!/bin/bash
echo "=== Current target framework ==="
fd -e csproj | grep -v '/obj/' | grep -v '/bin/' | xargs grep 'TargetFramework' 2>/dev/null | head -20

echo ""
echo "=== SignalR usage ==="
rg --type cs -n -i '(SignalR|IHubContext|\.Hub\b|WebSocket)' -A3 -B3 2>/dev/null | head -200

echo ""
echo "=== Polling/real-time frontend patterns ==="
rg -n -i '(setInterval|polling|refetch|EventSource|eventsource)' --glob '*.ts' --glob '*.tsx' --glob '*.js' --glob '*.jsx' -A3 -B3 2>/dev/null | head -300

echo ""
echo "=== Integration events list ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'IntegrationEvent' | sort

echo ""
echo "=== Domain events list ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'DomainEvent' | sort

echo ""
echo "=== Booking status domain events ==="
rg --type cs -n -i '(BookingStatus|BookingCreated|BookingConfirmed|BookingCompleted|BookingCancelled|BookingApproved|BookingRejected)' -A2 -B2 | head -300

echo ""
echo "=== Payment status events ==="
rg --type cs -n -i '(PaymentStatus|PaymentCreated|PaymentCompleted|PaymentFailed|PaymentPending)' -A2 -B2 | head -200

echo ""
echo "=== Notification-related files ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'Notification' | sort

echo ""
echo "=== Help request events ==="
rg --type cs -n -i '(HelpRequestCreated|HelpRequestCompleted|HelpRequestStatus|HelpRequestUpdated)' -A2 -B2 | head -200

Length of output: 62082


🏁 Script executed:

#!/bin/bash
echo "=== Current target framework ==="
fd -e csproj | grep -v '/obj/' | grep -v '/bin/' | xargs grep -l 'TargetFramework' 2>/dev/null | head -5 | xargs grep 'TargetFramework' 2>/dev/null

echo ""
echo "=== SignalR / WebSocket usage ==="
rg --type cs -n -i '(SignalR|IHubContext|\.Hub\b|WebSocket|EventSource)' -A2 -B2 2>/dev/null | head -100

echo ""
echo "=== Notification-related files ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'Notification' | sort

echo ""
echo "=== Polling/setInterval on frontend ==="
rg -n -i '(setInterval|EventSource|eventsource|polling)' --glob '*.ts' --glob '*.tsx' --glob '*.js' --glob '*.jsx' -A2 -B2 2>/dev/null | head -200

echo ""
echo "=== Integration events list ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'IntegrationEvent' | sort

echo ""
echo "=== Domain events list ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'DomainEvent' | sort

echo ""
echo "=== Booking status / domain events ==="
rg --type cs -n -i '(BookingStatus|BookingCreated|BookingConfirmed|BookingCompleted|BookingCancelled|BookingApproved|BookingRejected)' -A2 -B2 | head -200

echo ""
echo "=== Payment events ==="
rg --type cs -n -i '(PaymentStatus|PaymentCreated|PaymentCompleted|PaymentFailed|PaymentPending|PaymentCaptured)' -A2 -B2 | head -200

echo ""
echo "=== Helpdesk/help-requests events ==="
rg --type cs -n -i '(HelpRequestCreated|HelpRequestCompleted|HelpRequestUpdated|HelpRequestStatus)' -A2 -B2 | head -150

echo ""
echo "=== Communication/Notification modules ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE '(Communication|Push|Email|Notification)' | sort | head -30

echo ""
echo "=== Admin dashboard data refresh (frontend) ==="
rg -n -i '(dashboard|refetch|refresh|polling|interval)' --glob '*.ts' --glob '*.tsx' -A2 -B2 2>/dev/null | head -200

echo ""
echo "=== use-dashboard.ts content ==="
fd -e ts | grep -i 'use-dashboard' | xargs cat -n 2>/dev/null | head -100

Length of output: 68454


🌐 Web query:

.NET 10 Server-Sent Events native support new feature ASP.NET Core

💡 Result:

.NET 10 / ASP.NET Core 10 adds first-class (native) Server-Sent Events (SSE) support via a built-in HTTP result, so you no longer need to hand-format text/event-stream responses yourself. [1][2]

What’s new

  • New result type + helpers: TypedResults.ServerSentEvents(...) (and the non-typed Results.ServerSentEvents(...)) returns an SSE stream from an IAsyncEnumerable<...> source. [1][2][3]
  • Event model: SSE messages are represented as SseItem<T> (can include event type, id, and data payload). [2]
  • Works in Minimal APIs and MVC controllers. [2]
  • Optional .NET client parsing: System.Net.ServerSentEvents provides SseParser for consuming SSE streams. [4]

Minimal API example (shape)

app.MapGet("/stream", (CancellationToken ct) =>
{
    async IAsyncEnumerable<SseItem<MyDto>> GetEvents()
    {
        while (!ct.IsCancellationRequested)
        {
            yield return new SseItem<MyDto>(new MyDto(...), eventType: "update", id: "1");
            await Task.Delay(1000, ct);
        }
    }

    return TypedResults.ServerSentEvents(GetEvents());
});

(Uses TypedResults.ServerSentEvents + SseItem<T> as described in the official docs.) [2][3]


@frigini Sim — dá para aplicar e com bom ROI em alguns fluxos. O projeto já está em net10.0, então podemos usar o SSE nativo do ASP.NET Core via TypedResults.ServerSentEvents e SseItem sem libs externas no servidor. O framework formata automaticamente text/event-stream, mantém a conexão e faz o flush dos eventos a partir de um IAsyncEnumerable. (learn.microsoft.com)

Onde usar primeiro (alto impacto e pouca mudança)

  • Status de agendamentos em tempo real
    • Backend: emitir eventos quando Booking for Created/Confirmed/Rejected/Cancelled/Completed (já existem Domain Events). Expor endpoint SSE por bookingId.
    • Pontos de encaixe:
      • Domínio/Handlers: criar handlers em src/Modules/Bookings/Infrastructure/Events/Handlers/ para repassar para um broadcaster SSE.
      • Endpoint: src/Modules/Bookings/API/*Endpoints.cs (rota sugerida: GET /api/v1/bookings/{id}/events).
  • Verificação de prestador (ProviderVerificationStatusUpdated)
    • Backend: já há ProviderVerificationStatusUpdatedDomainEvent + IntegrationEvent/Handler. Acrescentar broadcast SSE no handler existente em src/Modules/Providers/Infrastructure/Events/Handlers/ProviderVerificationStatusUpdatedDomainEventHandler.cs para notificar o próprio prestador.
    • Endpoint: GET /api/v1/providers/{id}/verification-events.

Outros candidatos (médio impacto)

  • Pagamentos: PaymentTransaction muda de Pending→Succeeded/Failed/Refunded (status está na entidade). Podemos introduzir pequenos Domain Events ou publicar no SSE após persistir a mudança no repositório. Rota: GET /api/v1/payments/{transactionId}/events.
  • Dashboard Admin: hoje há refetch manual em app/(admin)/dashboard/page.tsx; podemos substituir por um stream de métricas (ex.: contadores de BusinessMetricsMiddleware) em GET /api/v1/admin/metrics-stream.

Exemplo enxuto (backend)

  • Broadcaster (compartilhado) baseado em Channel: criaremos um serviço singleton por “tópico” para publicar/ler SseItem sem alocações desnecessárias e com backpressure.
// Shared/Realtime/SseTopic.cs
using System.Net.ServerSentEvents;
using System.Threading.Channels;

public sealed class SseTopic<T>
{
    private readonly Channel<SseItem<T>> _ch = Channel.CreateBounded<SseItem<T>>(new BoundedChannelOptions(1024)
    { SingleReader = false, SingleWriter = false, FullMode = BoundedChannelFullMode.DropOldest });

    public bool TryPublish(T data, string eventType, string? id = null) =>
        _ch.Writer.TryWrite(new SseItem<T>(data, eventType: eventType) { Id = id });

    public IAsyncEnumerable<SseItem<T>> ReadAllAsync(CancellationToken ct) => _ch.Reader.ReadAllAsync(ct);
}
  • Registro DI e hub simples para lookup por chave (ex.: bookingId, providerId):
// Shared/Realtime/SseHub.cs
public interface ISseHub<T>
{
    SseTopic<T> Get(string key);
}
public sealed class SseHub<T> : ISseHub<T>
{
    private readonly ConcurrentDictionary<string, SseTopic<T>> _topics = new();
    public SseTopic<T> Get(string key) => _topics.GetOrAdd(key, _ => new SseTopic<T>());
}
  • Endpoint SSE de status de agendamento:
// Modules/Bookings/API/BookingsEndpoints.cs (trecho)
app.MapGet("/api/v1/bookings/{id:guid}/events",
    ([AsParameters] Guid id, ISseHub<BookingStatusDto> hub, HttpContext http, CancellationToken ct) =>
{
    http.Response.Headers.CacheControl = "no-store";
    var topic = hub.Get(id.ToString());
    async IAsyncEnumerable<SseItem<BookingStatusDto>> Stream([EnumeratorCancellation] CancellationToken token)
    {
        await foreach (var item in topic.ReadAllAsync(token)) yield return item;
    }
    return TypedResults.ServerSentEvents(Stream(ct));
})
.RequireAuthorization();
  • Emissor no handler (ex.: provider verificado):
// Modules/Providers/Infrastructure/Events/Handlers/ProviderVerificationStatusUpdatedDomainEventHandler.cs (trecho)
public sealed class ProviderVerificationStatusUpdatedDomainEventHandler(
    IMessageBus bus, ProvidersDbContext ctx, ISearchProvidersModuleApi search,
    ILogger<...> logger, ISseHub<ProviderVerificationDto> sseHub) 
  : IEventHandler<ProviderVerificationStatusUpdatedDomainEvent>
{
    public async Task HandleAsync(ProviderVerificationStatusUpdatedDomainEvent e, CancellationToken ct = default)
    {
        // ... lógica existente (indexação + publish integration event)
        var topic = sseHub.Get(e.AggregateId.ToString());
        topic.TryPublish(new ProviderVerificationDto { ProviderId = e.AggregateId, Status = e.NewStatus.ToString() },
                         eventType: "provider-verification", id: DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString());
    }
}

Exemplo (frontend) com autenticação

  • Nativo EventSource não aceita headers customizados (logo, não envia Authorization). Para apps que usam Bearer/JWT (Keycloak), use um client SSE baseado em fetch (ex.: fetch-event-source) para incluir o header. (developer.mozilla.org)
// hooks/useBookingStatusStream.ts
import { fetchEventSource } from '@microsoft/fetch-event-source'; // ou compatível
export function subscribeBookingStatus(bookingId: string, token: string, onMsg: (data: any) => void) {
  const ctrl = new AbortController();
  fetchEventSource(`/api/v1/bookings/${bookingId}/events`, {
    headers: { Authorization: `Bearer ${token}` },
    signal: ctrl.signal,
    onmessage(ev) {
      if (ev.event === 'booking-status') onMsg(JSON.parse(ev.data));
    },
    onerror(err) { console.error('SSE error', err); },
  });
  return () => ctrl.abort();
}

Notas de implementação

  • Autorização/escopo: nas rotas SSE, validar que o usuário tem direito ao recurso (ex.: booking do próprio cliente/prestador; providerId == subject).
  • Back-end simples: o próprio ASP.NET Core 10 retorna ServerSentEvents a partir de IAsyncEnumerable<SseItem>; também existe Results.ServerSentEvents se preferir não tipado. (learn.microsoft.com)
  • Reconexão/Last-Event-ID: use SseItem.Id e, no cliente, envie Last-Event-ID automaticamente em reconexões; o framework formata os eventos conforme o padrão SSE. (learn.microsoft.com)
  • Operação: ajustar timeouts de LB/proxy (idle/read ≥ 300s) nas rotas SSE; desabilitar buffering/caching de resposta (mostrado acima).
  • CSP: garantir connect-src para o domínio da API nas páginas dos frontends (se origem diferente). No API Service, o seu ContentSecurityPolicyMiddleware já cuida de connect-src para "'self'" e websocketUrl; para SSE no mesmo host, isso já basta na API; no front (Next), adicione o host da API em connect-src se necessário.

Proposta de piloto (2 semanas, aditivo, sem breaking changes)

  • Semana 1
    • Infra Realtime: SseTopic/SseHub (Shared) + registro DI.
    • Providers: emitir no ProviderVerificationStatusUpdatedDomainEventHandler e expor GET /api/v1/providers/{id}/verification-events.
    • Front Customer/Provider: hook simples na página de perfil do prestador para atualizar status em tempo real.
  • Semana 2
    • Bookings: criar handlers de domínio para cada transição e expor GET /api/v1/bookings/{id}/events com payload BookingStatusDto.
    • Admin: endpoint opcional /api/v1/admin/metrics-stream com bateladas periódicas das métricas.
    • Observabilidade: contadores de conexões SSE por rota e duração média.

Se quiser, elaboro o plano detalhado (com arquivos/assinaturas exatas e etapas de teste) para o piloto “Providers + Bookings”, e já preparo os trechos de código prontos para colar nos pontos indicados.

Fontes principais

  • Documentação oficial do ASP.NET Core 10 para SSE (TypedResults.ServerSentEvents, SseItem, exemplos com Minimal APIs). (learn.microsoft.com)
  • Alternativa Results.ServerSentEvents (não tipado). (learn.microsoft.com)
  • EventSource básico (MDN) e limitação de headers do EventSource nativo (usar client baseado em fetch quando precisar de Authorization). (developer.mozilla.org)

🧠 Learnings used
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-20T22:04:25.820Z
Learning: MeAjudaAi (PRs futuros): para cobertura global, NÃO excluir *Configuration e *Extensions (há smoke tests exercitando-os); também não excluir *.Monitoring.*, MeAjudaAi.Shared.Jobs.* e MeAjudaAi.Shared.Mediator.*. Manter excluídos apenas Request/Response/Dto/DTO/IntegrationEvent e *DbContextFactory; Endpoints podem ficar excluídos globalmente, a menos que sejam white-listados por módulo com E2E (ex.: +MeAjudaAi.Modules.Payments.API.*Endpoint; -*Endpoint).

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi ci-backend.yml (linha 272): Para tornar os endpoints de um módulo visíveis na cobertura do ReportGenerator, adicionar o assembly antes de `-*Endpoint` no classfilters, ex: `+MeAjudaAi.Modules.Bookings.API.*Endpoint` (após `+MeAjudaAi.Modules.Payments.API.*Endpoint`). Isso é necessário apenas para módulos com cobertura E2E. Módulos sem E2E mantêm os endpoints excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi Shared coverage: Classes de infraestrutura de messaging que dependem de broker RabbitMQ real (RabbitMqDeadLetterService, DeadLetterExtensions, FailedMessageInfo, FailureAttempt, EnvironmentMetadata) são candidatas a [ExcludeFromCodeCoverage] pois não são testáveis unitariamente. Utilitários puros (PhoneNumberValidator, PiiMaskingHelper, SlugHelper, UuidGenerator) devem ter testes unitários adicionados em vez de serem excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi Bookings.API: Classes não-endpoint dentro do assembly da API (Extensions, BookingsEndpoints, ProviderAuthorizationResult, ProviderAuthorizationResultExtensions) não são capturadas pelo classfilter `+MeAjudaAi.Modules.Bookings.API.*Endpoint` e devem receber [ExcludeFromCodeCoverage] individualmente (são glue/wiring). Já `ProviderAuthorizationResolver` contém lógica real de autorização e deve ter testes unitários próprios.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-19T15:37:29.889Z
Learning: MeAjudaAi: objetivo de cobertura global é ≥90% (linhas), mantendo thresholds 90/80 no CI; quando a meta estiver ameaçada, preferir excluir “glue/DTO” (Endpoints/Extensions/Options/IntegrationEvent/DbContextFactory) e adicionar testes de alto impacto, em vez de reduzir o threshold.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-01-16T21:50:23.593Z
Learning: For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups; prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Providers/Tests/Unit/Application/Queries/GetProvidersByIdsQueryTests.cs:65-77
Timestamp: 2025-12-18T00:35:33.829Z
Learning: MeAjudaAi repo: Exceção à política de idioma para comentários — em testes, os cabeçalhos AAA (Arrange, Act, Assert) devem permanecer em inglês e não devem ser traduzidos. Comentários/XML continuam em português; logs em inglês; mensagens ao usuário (validações/APIs) em português.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi coverlet.runsettings: `ExcludeByAttribute` deve incluir `ExcludeFromCodeCoverage` (além de Obsolete,GeneratedCode,CompilerGenerated). Sem isso, todas as classes decoradas com [ExcludeFromCodeCoverage] continuam sendo instrumentadas pelo coverlet e contam como linhas não cobertas no relatório, reduzindo a cobertura global. Correção: `<ExcludeByAttribute>Obsolete,GeneratedCode,CompilerGenerated,ExcludeFromCodeCoverage</ExcludeByAttribute>`.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:26:37.409Z
Learning: MeAjudaAi logging policy: logs must be in English, comments may remain in Portuguese. Portuguese logs were additionally found in Users (API/Extensions.cs:83), Providers (API/Extensions.cs:74), ServiceCatalogs (API/Extensions.cs:78,86,93), and Locations (multiple clients/services lines listed). SearchProviders module currently has no Portuguese logs.

@frigini
Copy link
Copy Markdown
Owner Author

frigini commented Apr 29, 2026

Se quiser, elaboro o plano detalhado (com arquivos/assinaturas exatas e etapas de teste) para o piloto “Providers + Bookings”, e já preparo os trechos de código prontos para colar nos pontos indicados.

@coderabbitai Respondendo a isto: Se quiser, elaboro o plano detalhado (com arquivos/assinaturas exatas e etapas de teste) para o piloto “Providers + Bookings”, e já preparo os trechos de código prontos para colar nos pontos indicados. -> sim eu quero, poderia fazer isso detalhadamente?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@frigini Vou buscar os detalhes exatos do código dos módulos Providers e Bookings para montar o plano completo com arquivos e assinaturas precisas.


🏁 Script executed:

#!/bin/bash
echo "=== Bookings module structure ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -i 'booking' | sort

echo ""
echo "=== Providers module structure ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -i 'provider' | grep -v 'SearchProvider' | sort

echo ""
echo "=== Existing Bookings domain events ==="
rg --type cs -n -i '(IDomainEvent|DomainEvent|IEventHandler|EventHandler)' --glob '*Booking*' -A3 -B3

echo ""
echo "=== BookingStatus enum ==="
rg --type cs -n 'enum.*Booking\|BookingStatus' -A15 -B2

echo ""
echo "=== ProviderVerificationStatusUpdatedDomainEventHandler content ==="
fd -e cs | grep -i 'ProviderVerificationStatusUpdatedDomain' | xargs cat -n 2>/dev/null

echo ""
echo "=== Bookings endpoints file ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -i 'BookingsEndpoint\|BookingEndpoint' | xargs cat -n 2>/dev/null

echo ""
echo "=== DI registration patterns in Bookings and Providers ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE '(BookingsModule|ProvidersModule|Extensions)' | grep -iE '(Module|Extension)' | head -10 | xargs cat -n 2>/dev/null | head -200

echo ""
echo "=== Bookings domain events files ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'Booking.*Event|Event.*Booking' | sort | xargs cat -n 2>/dev/null | head -400

echo ""
echo "=== Providers domain events ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -i 'Provider' | grep -iE '(Event|Handler)' | sort | xargs cat -n 2>/dev/null | head -300

echo ""
echo "=== Shared Extensions / DI registration pattern ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'SharedExtensions|AddShared' | head -5 | xargs cat -n 2>/dev/null | head -200

echo ""
echo "=== Existing integration test fixtures (WebApplicationFactory) ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE '(WebApplicationFactory|IntegrationTest|TestBase|Fixture)' | sort | head -10 | xargs cat -n 2>/dev/null | head -300

echo ""
echo "=== Booking aggregate root ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iP 'Booking\.cs|Booking\b' | grep -v 'Test\|Event\|Handler\|Dto\|Request\|Response\|Query\|Command' | head -5 | xargs cat -n 2>/dev/null | head -300

echo ""
echo "=== Provider aggregate root ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iP '\/Provider\.cs' | xargs cat -n 2>/dev/null | head -300

echo ""
echo "=== Existing Booking handlers (commands/events) ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -i 'Booking' | grep -i 'Handler' | sort | xargs cat -n 2>/dev/null | head -500

echo ""
echo "=== ContentSecurityPolicyMiddleware ==="
fd -e cs | grep -i 'ContentSecurity\|CSP' | xargs cat -n 2>/dev/null | head -100

echo ""
echo "=== ApiService Program.cs / Startup ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE '(Program|Startup)' | grep -i 'ApiService' | xargs cat -n 2>/dev/null | head -200

Length of output: 201569


@frigini perfeito — segue o plano detalhado do piloto SSE “Providers + Bookings”, com arquivos, assinaturas e trechos prontos para colar. Mantive os handlers existentes intocados e criei handlers SSE paralelos para não quebrar testes. Respeitei as learnings de linguagem (logs em inglês; comentários/XML em português) e cobertura.

Piloto SSE Providers + Bookings (aditivo, sem breaking changes)

  • Shared
    • Criar infraestrutura de streaming: ISseHub, SseHub, SseTopic
    • Registrar hubs tipados para Bookings e Providers em AddSharedServices
  • Bookings
    • DTO de stream BookingStatusSseDto
    • Handler BookingRealtimeEventsHandler publicando eventos no hub
    • Endpoint BookingEventsEndpoint GET /api/v1/bookings/{id}/events com autorização forte
    • Mapear o endpoint no BookingsEndpoints
  • Providers
    • DTO de stream ProviderVerificationSseDto
    • Handler ProviderVerificationStatusUpdatedSseHandler publicando no hub
    • Endpoint ProviderVerificationEventsEndpoint GET /api/v1/providers/{id}/verification-events (admin ou o próprio prestador)
    • Mapear o endpoint no aggregator de Providers
  • Testes e verificação
    • Unit: validar publicação nos hubs dos handlers SSE
    • Integração manual: curl -N e fluxo de confirmação/remoção
    • Front: snippet com @microsoft/fetch-event-source para enviar Authorization

Fases sugeridas e estimativa

  • Dia 1: Infra Shared + registros DI + DTOs
  • Dia 2: Handlers SSE (Bookings/Providers) + endpoints + mapas nos aggregators
  • Dia 3: Tests unitários dos handlers + validação manual com curl
  • Dia 4: Hook opcional no dashboard/provider page (frontend) com fetch-event-source

Quer que eu já prepare um PR de rascunho com esses arquivos, ou prefere que eu abra issues para cada requisito com checklist?


🧠 Learnings used
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-20T22:04:25.820Z
Learning: MeAjudaAi (PRs futuros): para cobertura global, NÃO excluir *Configuration e *Extensions (há smoke tests exercitando-os); também não excluir *.Monitoring.*, MeAjudaAi.Shared.Jobs.* e MeAjudaAi.Shared.Mediator.*. Manter excluídos apenas Request/Response/Dto/DTO/IntegrationEvent e *DbContextFactory; Endpoints podem ficar excluídos globalmente, a menos que sejam white-listados por módulo com E2E (ex.: +MeAjudaAi.Modules.Payments.API.*Endpoint; -*Endpoint).

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi ci-backend.yml (linha 272): Para tornar os endpoints de um módulo visíveis na cobertura do ReportGenerator, adicionar o assembly antes de `-*Endpoint` no classfilters, ex: `+MeAjudaAi.Modules.Bookings.API.*Endpoint` (após `+MeAjudaAi.Modules.Payments.API.*Endpoint`). Isso é necessário apenas para módulos com cobertura E2E. Módulos sem E2E mantêm os endpoints excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi Shared coverage: Classes de infraestrutura de messaging que dependem de broker RabbitMQ real (RabbitMqDeadLetterService, DeadLetterExtensions, FailedMessageInfo, FailureAttempt, EnvironmentMetadata) são candidatas a [ExcludeFromCodeCoverage] pois não são testáveis unitariamente. Utilitários puros (PhoneNumberValidator, PiiMaskingHelper, SlugHelper, UuidGenerator) devem ter testes unitários adicionados em vez de serem excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi Bookings.API: Classes não-endpoint dentro do assembly da API (Extensions, BookingsEndpoints, ProviderAuthorizationResult, ProviderAuthorizationResultExtensions) não são capturadas pelo classfilter `+MeAjudaAi.Modules.Bookings.API.*Endpoint` e devem receber [ExcludeFromCodeCoverage] individualmente (são glue/wiring). Já `ProviderAuthorizationResolver` contém lógica real de autorização e deve ter testes unitários próprios.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi coverlet.runsettings: `ExcludeByAttribute` deve incluir `ExcludeFromCodeCoverage` (além de Obsolete,GeneratedCode,CompilerGenerated). Sem isso, todas as classes decoradas com [ExcludeFromCodeCoverage] continuam sendo instrumentadas pelo coverlet e contam como linhas não cobertas no relatório, reduzindo a cobertura global. Correção: `<ExcludeByAttribute>Obsolete,GeneratedCode,CompilerGenerated,ExcludeFromCodeCoverage</ExcludeByAttribute>`.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-19T15:37:29.889Z
Learning: MeAjudaAi: objetivo de cobertura global é ≥90% (linhas), mantendo thresholds 90/80 no CI; quando a meta estiver ameaçada, preferir excluir “glue/DTO” (Endpoints/Extensions/Options/IntegrationEvent/DbContextFactory) e adicionar testes de alto impacto, em vez de reduzir o threshold.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-01-16T21:50:23.593Z
Learning: For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups; prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Providers/Tests/Unit/Application/Queries/GetProvidersByIdsQueryTests.cs:65-77
Timestamp: 2025-12-18T00:35:33.829Z
Learning: MeAjudaAi repo: Exceção à política de idioma para comentários — em testes, os cabeçalhos AAA (Arrange, Act, Assert) devem permanecer em inglês e não devem ser traduzidos. Comentários/XML continuam em português; logs em inglês; mensagens ao usuário (validações/APIs) em português.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:26:37.409Z
Learning: MeAjudaAi logging policy: logs must be in English, comments may remain in Portuguese. Portuguese logs were additionally found in Users (API/Extensions.cs:83), Providers (API/Extensions.cs:74), ServiceCatalogs (API/Extensions.cs:78,86,93), and Locations (multiple clients/services lines listed). SearchProviders module currently has no Portuguese logs.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:32:51.210Z
Learning: Além de manter logs em inglês, os comentários e a documentação XML (/// <summary>, <param>, etc.) devem ser escritos em português em todo o código MeAjudaAi.

The requested changes have been implemented and a pull request has been created: View PR

@frigini
Copy link
Copy Markdown
Owner Author

frigini commented Apr 29, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs (1)

211-224: ⚠️ Potential issue | 🟡 Minor

Atualize a documentação do teste para refletir a nova regra de rejeição.

O resumo XML dos casos extremos ainda descreve fail-open, mas este cenário agora valida rejeição de entrada malformada. Isso gera ambiguidade de intenção.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs`
around lines 211 - 224, Update the XML summary for the test method
GetProviders_WithMalformedLocationHeader_ShouldBeRejected to state that
malformed "X-User-Location" headers are rejected (status
HttpStatusCode.UnavailableForLegalReasons) instead of describing a fail-open
behavior; locate the XML doc comment above the method and replace any wording
that mentions "fail-open" or allowing malformed entries with wording that
clearly documents rejection and the expected status code.
🧹 Nitpick comments (6)
.github/workflows/ci-backend.yml (1)

70-70: Considere fixar também o digest SHA256 para máxima segurança e reprodutibilidade.

A tag 3.33.0 já melhora a reprodutibilidade em relação a latest. Para seguir a best practice recomendada pelo GitHub e Docker contra supply chain attacks, considere usar:

image: mcr.microsoft.com/azure-storage/azurite:3.33.0@sha256:2628ee10a72833cc344b9d194cd8b245543892b307d16cf26a2cf55a15b816af

Isso garante que a imagem exata seja usada independentemente de reatribuições de tags.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci-backend.yml at line 70, Replace the mutable tag-only
image declaration "image: mcr.microsoft.com/azure-storage/azurite:3.33.0" with
the pinned digest form by appending the provided sha256 digest (e.g., change to
"image:
mcr.microsoft.com/azure-storage/azurite:3.33.0@sha256:2628ee10a72833cc344b9d194cd8b245543892b307d16cf26a2cf55a15b816af")
so the workflow uses the exact immutable image artifact and improves security
and reproducibility.
src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs (2)

95-99: Pequena inconsistência com a estratégia de Span no firstName (ramo sem espaço).

No else, usar nameSpan.ToString() mantém o padrão das otimizações e evita um Trim() extra em command.Name.

✨ Diff sugerido
-        var firstName = firstSpace >= 0 ? nameSpan[..firstSpace].ToString() : command.Name.Trim();
+        var firstName = firstSpace >= 0 ? nameSpan[..firstSpace].ToString() : nameSpan.ToString();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs`
around lines 95 - 99, In RegisterCustomerCommandHandler, the else branch for
computing firstName breaks the Span-based pattern by calling
command.Name.Trim(); change that branch to use nameSpan.ToString() so both the
space and no-space paths consistently use the trimmed Span (variables: nameSpan,
firstSpace, firstName, lastName) and avoid the extra Trim() allocation.

81-85: Centralize o masking em PiiMaskingHelper para evitar drift de regra.

A lógica aqui duplica comportamento já implementado em src/Shared/Utilities/PiiMaskingHelper.cs (mesmo padrão de parsing com Span). Vale consolidar para manter consistência global de mascaramento.

♻️ Diff sugerido
-        var rawEmailSpan = command.Email.AsSpan();
-        var rawAtIndex = rawEmailSpan.IndexOf('@');
-        var maskedEmail = rawAtIndex >= 0 
-            ? $"{new string('*', Math.Min(3, rawAtIndex))}@{rawEmailSpan[(rawAtIndex + 1)..].ToString()}" 
-            : "***@***";
+        var maskedEmail = PiiMaskingHelper.MaskEmail(command.Email);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs`
around lines 81 - 85, Substitua a lógica de mascaramento inline em
RegisterCustomerCommandHandler (onde são usados
rawEmailSpan/rawAtIndex/maskedEmail) por uma chamada ao utilitário central
PiiMaskingHelper para evitar duplicação de regras; localize o trecho que
constrói maskedEmail e invocar PiiMaskingHelper.MaskEmail(command.Email) (ou o
método equivalente exposto em PiiMaskingHelper), removendo o bloqueio de
Span/IndexOf e garantindo que o helper trate nulos/strings vazias conforme o
comportamento atual.
src/Shared/Extensions/DocumentExtensions.cs (1)

96-97: Adicionar guard clauses para tamanhos de multiplicadores evita regressões futuras.

Hoje os parâmetros estão corretos, mas sem validação explícita um ajuste futuro nos arrays pode gerar IndexOutOfRangeException em runtime. Recomendo proteger isso no ponto de entrada dos métodos internos.

💡 Diff sugerido
 private static bool IsValidDocument(ReadOnlySpan<char> document, int expectedLength, ReadOnlySpan<int> firstMultipliers, ReadOnlySpan<int> secondMultipliers)
 {
+    if (firstMultipliers.Length != expectedLength - 2 || secondMultipliers.Length != expectedLength - 1)
+        return false;
+
     if (document.Length != expectedLength)
         return false;
 private static string GenerateValidDocument(int baseLength, ReadOnlySpan<int> firstMultipliers, ReadOnlySpan<int> secondMultipliers)
 {
+    if (firstMultipliers.Length != baseLength || secondMultipliers.Length != baseLength + 1)
+        throw new ArgumentException("Multipliers length does not match document base length.");
+
     var document = new char[baseLength + 2];

Also applies to: 130-131

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Extensions/DocumentExtensions.cs` around lines 96 - 97, Add
explicit guard clauses at the start of IsValidDocument to validate that
document.Length equals expectedLength and that firstMultipliers.Length and
secondMultipliers.Length match the expected multiplier length (or expectedLength
as appropriate); if any check fails, throw an
ArgumentException/ArgumentOutOfRangeException with a clear message naming the
offending parameter (document, expectedLength, firstMultipliers,
secondMultipliers) to prevent IndexOutOfRangeException later. Apply the same
validations to the other internal document-validation overloads in this file
(the nearby IsValidDocument/validation helper overloads) so all entry points
enforce consistent size contracts.
src/Shared/Monitoring/BusinessMetricsMiddleware.cs (1)

86-93: Match de conclusão de help-request ainda usa Contains.

A lógica para help-requests/{id}/complete ainda usa Contains("/help-requests/", ...) que poderia potencialmente casar com paths como /api/v1/admin/help-requests/123/complete. Se isso for uma preocupação, considere usar um regex mais restritivo como ^/api/v\d+/help-requests/\d+/complete$.

Porém, para esta sprint de otimização, o comportamento atual é aceitável dado que o cenário de admin aninhado é improvável.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Monitoring/BusinessMetricsMiddleware.cs` around lines 86 - 93, The
current check for help-request completion uses
pathSpan.Contains("/help-requests/", ...) which can match nested admin routes;
replace that loose Contains-based check by matching the full versioned route
with a stricter regex (e.g., anchor to ^/api/v\d+/help-requests/\d+/complete$)
or extend VersionedApiPattern to validate the full path before calling
businessMetrics.RecordHelpRequestCompleted and logger.LogInformation; update the
conditional that references VersionedApiPattern.IsMatch, pathValue, pathSpan,
method and statusCode so it only records completions for exact matches of the
intended route prior to invoking
businessMetrics.RecordHelpRequestCompleted("general", elapsed).
tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs (1)

1-30: LGTM! Teste unitário valida a delegação corretamente.

O teste verifica que OptionsTopicNameConvention.GetTopic delega para MessageBusOptions.TopicNamingConvention, garantindo que a convenção customizada seja aplicada. A estrutura AAA está clara e o escopo do teste é adequado.

Considere adicionar casos de teste adicionais em sprints futuras para cobrir cenários como tipos com namespaces diferentes ou a convenção default quando TopicNamingConvention não é customizado.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs`
around lines 1 - 30, No changes required; the test
OptionsTopicNameConventionTests verifies that
OptionsTopicNameConvention.GetTopic delegates to
MessageBusOptions.TopicNamingConvention correctly for TestEvent, but if you want
to extend coverage later add additional unit tests exercising different event
types (with namespaces) and the default behavior when
MessageBusOptions.TopicNamingConvention is null or not set to ensure fallback
logic is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs`:
- Around line 251-253: The CI is failing because the integration test in
GeographicRestrictionIntegrationTests expects 451 for empty city/state headers
but the current middleware returns 200; to fix, either implement the middleware
check in the geographic restriction component to reject empty "city"/"state"
headers and return HttpStatusCode.UnavailableForLegalReasons (451) or update the
test assertions in GeographicRestrictionIntegrationTests to expect
HttpStatusCode.OK for the scenarios at the two failing asserts (previously at
lines asserting 451). Locate the failing assertions in the test class (search
for the assertions comparing response.StatusCode to
HttpStatusCode.UnavailableForLegalReasons) and make the decision: implement
header validation in the middleware that handles geographic restrictions (reject
empty city/state and return 451) or change those assertions to HttpStatusCode.OK
to match current behavior.

---

Outside diff comments:
In
`@tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs`:
- Around line 211-224: Update the XML summary for the test method
GetProviders_WithMalformedLocationHeader_ShouldBeRejected to state that
malformed "X-User-Location" headers are rejected (status
HttpStatusCode.UnavailableForLegalReasons) instead of describing a fail-open
behavior; locate the XML doc comment above the method and replace any wording
that mentions "fail-open" or allowing malformed entries with wording that
clearly documents rejection and the expected status code.

---

Nitpick comments:
In @.github/workflows/ci-backend.yml:
- Line 70: Replace the mutable tag-only image declaration "image:
mcr.microsoft.com/azure-storage/azurite:3.33.0" with the pinned digest form by
appending the provided sha256 digest (e.g., change to "image:
mcr.microsoft.com/azure-storage/azurite:3.33.0@sha256:2628ee10a72833cc344b9d194cd8b245543892b307d16cf26a2cf55a15b816af")
so the workflow uses the exact immutable image artifact and improves security
and reproducibility.

In
`@src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs`:
- Around line 95-99: In RegisterCustomerCommandHandler, the else branch for
computing firstName breaks the Span-based pattern by calling
command.Name.Trim(); change that branch to use nameSpan.ToString() so both the
space and no-space paths consistently use the trimmed Span (variables: nameSpan,
firstSpace, firstName, lastName) and avoid the extra Trim() allocation.
- Around line 81-85: Substitua a lógica de mascaramento inline em
RegisterCustomerCommandHandler (onde são usados
rawEmailSpan/rawAtIndex/maskedEmail) por uma chamada ao utilitário central
PiiMaskingHelper para evitar duplicação de regras; localize o trecho que
constrói maskedEmail e invocar PiiMaskingHelper.MaskEmail(command.Email) (ou o
método equivalente exposto em PiiMaskingHelper), removendo o bloqueio de
Span/IndexOf e garantindo que o helper trate nulos/strings vazias conforme o
comportamento atual.

In `@src/Shared/Extensions/DocumentExtensions.cs`:
- Around line 96-97: Add explicit guard clauses at the start of IsValidDocument
to validate that document.Length equals expectedLength and that
firstMultipliers.Length and secondMultipliers.Length match the expected
multiplier length (or expectedLength as appropriate); if any check fails, throw
an ArgumentException/ArgumentOutOfRangeException with a clear message naming the
offending parameter (document, expectedLength, firstMultipliers,
secondMultipliers) to prevent IndexOutOfRangeException later. Apply the same
validations to the other internal document-validation overloads in this file
(the nearby IsValidDocument/validation helper overloads) so all entry points
enforce consistent size contracts.

In `@src/Shared/Monitoring/BusinessMetricsMiddleware.cs`:
- Around line 86-93: The current check for help-request completion uses
pathSpan.Contains("/help-requests/", ...) which can match nested admin routes;
replace that loose Contains-based check by matching the full versioned route
with a stricter regex (e.g., anchor to ^/api/v\d+/help-requests/\d+/complete$)
or extend VersionedApiPattern to validate the full path before calling
businessMetrics.RecordHelpRequestCompleted and logger.LogInformation; update the
conditional that references VersionedApiPattern.IsMatch, pathValue, pathSpan,
method and statusCode so it only records completions for exact matches of the
intended route prior to invoking
businessMetrics.RecordHelpRequestCompleted("general", elapsed).

In
`@tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs`:
- Around line 1-30: No changes required; the test
OptionsTopicNameConventionTests verifies that
OptionsTopicNameConvention.GetTopic delegates to
MessageBusOptions.TopicNamingConvention correctly for TestEvent, but if you want
to extend coverage later add additional unit tests exercising different event
types (with namespaces) and the default behavior when
MessageBusOptions.TopicNamingConvention is null or not set to ensure fallback
logic is covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ddaae320-d72c-4ed8-84fc-437a7d1c3835

📥 Commits

Reviewing files that changed from the base of the PR and between f5f11da and d264a38.

📒 Files selected for processing (22)
  • .github/workflows/ci-backend.yml
  • docs/roadmap-history.md
  • docs/roadmap.md
  • docs/technical-debt.md
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/GeographicRestrictionMiddleware.cs
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/RequestLoggingMiddleware.cs
  • src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs
  • src/Modules/Users/Infrastructure/Identity/Keycloak/KeycloakService.cs
  • src/Shared/Authorization/Middleware/PermissionOptimizationMiddleware.cs
  • src/Shared/Caching/CacheMetrics.cs
  • src/Shared/Extensions/DocumentExtensions.cs
  • src/Shared/Messaging/MessagingExtensions.cs
  • src/Shared/Messaging/Options/MessageBusOptions.cs
  • src/Shared/Monitoring/BusinessMetricsMiddleware.cs
  • src/Shared/Utilities/PhoneNumberValidator.cs
  • src/Shared/Utilities/PiiMaskingHelper.cs
  • tests/MeAjudaAi.ApiService.Tests/Unit/Middlewares/GeographicRestrictionMiddlewareTests.cs
  • tests/MeAjudaAi.ApiService.Tests/Unit/Middlewares/RequestLoggingMiddlewareTests.cs
  • tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Monitoring/BusinessMetricsMiddlewareTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Utilities/PiiMaskingHelperTests.cs

… real-time notification handlers for bookings and provider verifications.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/GeographicRestrictionMiddleware.cs (1)

225-266: ⚠️ Potential issue | 🟠 Major

Evite decidir no primeiro match de cidade.

Hoje a ordem de AllowedCities muda o resultado. Ex.: com ["Santa Maria|RS", "Santa Maria|DF"], uma request para Santa Maria/DF bate no primeiro configCity e retorna false sem avaliar a segunda entrada. O mesmo acontece com combinações City + City|State.

💡 Ajuste sugerido
                 if (!string.IsNullOrEmpty(configCityOnly) &&
                     configCityOnly.Equals(city, StringComparison.OrdinalIgnoreCase))
                 {
                     // Se não temos estado informado, aceitar apenas pelo nome da cidade
                     if (string.IsNullOrEmpty(state))
                         return true;

                     // Com estado informado, dependerá da regra de estados permitidos
-                    return options.CurrentValue.AllowedStates?.Any(s =>
-                        s.Equals(state, StringComparison.OrdinalIgnoreCase)) == true;
+                    if (options.CurrentValue.AllowedStates?.Any(s =>
+                        s.Equals(state, StringComparison.OrdinalIgnoreCase)) == true)
+                    {
+                        return true;
+                    }
+
+                    continue;
                 }
                 continue;
             }
@@
                 if (configCity.Equals(city, StringComparison.OrdinalIgnoreCase))
                 {
                     // Se temos state, validar também
                     if (!string.IsNullOrEmpty(state))
                     {
-                        return configState.Equals(state, StringComparison.OrdinalIgnoreCase);
+                        if (configState.Equals(state, StringComparison.OrdinalIgnoreCase))
+                        {
+                            return true;
+                        }
+
+                        continue;
                     }
                     // Se não temos state, match apenas por cidade
                     return true;
                 }
🧹 Nitpick comments (4)
src/Modules/Bookings/API/Endpoints/BookingEventsEndpoint.cs (1)

36-39: JsonSerializerOptions recriado a cada evento.

Uma nova instância de JsonSerializerOptions é criada para cada evento serializado. Para conexões SSE de longa duração com muitos eventos, isso gera alocações desnecessárias.

♻️ Sugestão de cache das opções de serialização
+/// <summary>
+/// Endpoint para streaming SSE de eventos de uma reserva específica.
+/// </summary>
 public class BookingEventsEndpoint : IEndpoint
 {
+    private static readonly JsonSerializerOptions JsonOptions = new()
+    {
+        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+    };
+
     public static void Map(IEndpointRouteBuilder app)
     {
         // ...
             await foreach (var `@event` in stream.WithCancellation(ct))
             {
-                var json = JsonSerializer.Serialize(`@event`, new JsonSerializerOptions 
-                { 
-                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase 
-                });
+                var json = JsonSerializer.Serialize(`@event`, JsonOptions);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Bookings/API/Endpoints/BookingEventsEndpoint.cs` around lines 36
- 39, The code recreates JsonSerializerOptions per event in
BookingEventsEndpoint: stop allocating on each JsonSerializer.Serialize call by
introducing a reusable options instance (e.g., a private static readonly
JsonSerializerOptions) configured with PropertyNamingPolicy =
JsonNamingPolicy.CamelCase and use that cached instance in the
JsonSerializer.Serialize(`@event`, cachedOptions) call instead of newing options
each time.
src/Modules/Providers/API/Endpoints/ProviderVerificationEventsEndpoint.cs (1)

33-36: Evitar alocação de JsonSerializerOptions a cada evento

Em Lines 33-36, criar JsonSerializerOptions dentro do await foreach gera alocação contínua em conexão longa. Mova para campo estático reutilizável.

♻️ Ajuste sugerido
 public class ProviderVerificationEventsEndpoint : IEndpoint
 {
+    private static readonly JsonSerializerOptions SseJsonOptions = new()
+    {
+        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+    };
+
     public static void Map(IEndpointRouteBuilder app)
     {
@@
-                var json = JsonSerializer.Serialize(`@event`, new JsonSerializerOptions 
-                { 
-                    PropertyNamingPolicy = JsonNamingPolicy.CamelCase 
-                });
+                var json = JsonSerializer.Serialize(`@event`, SseJsonOptions);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Providers/API/Endpoints/ProviderVerificationEventsEndpoint.cs`
around lines 33 - 36, The code currently creates a new JsonSerializerOptions
inside the event loop when calling JsonSerializer.Serialize(`@event`, new
JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }),
causing repeated allocations; refactor by extracting that options instance to a
single reusable static readonly field (e.g., private static readonly
JsonSerializerOptions _camelCaseSerializerOptions) and change the Serialize call
to use that field instead so the same JsonSerializerOptions is reused across
iterations.
src/Shared/Streaming/SseHub.cs (1)

26-27: Atenção para escala horizontal do hub em memória

SseHub<T> singleton in-memory funciona para piloto em instância única, mas não propaga eventos entre réplicas. Em produção com múltiplas instâncias, considere backplane (Redis/RabbitMQ) ou sticky sessions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Streaming/SseHub.cs` around lines 26 - 27, SseHub<T> is an
in-memory singleton that won't forward events across multiple app instances;
modify the implementation so it uses a distributed backplane or
session-affinity: replace or augment the current SseHub<T>/ISseHub<T> in-memory
event store with a pub/sub layer (e.g., Redis pub/sub or RabbitMQ) so
Publish/Subscribe methods on SseHub<T> publish to the backplane and subscribe
handlers receive cross-instance events, or make session affinity changes in
hosting (sticky sessions) and document that requirement; update the SseHub<T>
methods that enqueue/broadcast events to call the chosen backplane APIs and
ensure subscription/cleanup logic maps to backplane subscriptions.
src/Web/MeAjudaAi.Web.Customer/hooks/use-provider-verification.ts (1)

27-35: Evitar literals de status no hook

Em Lines 27-35, os valores (Verified, Rejected, etc.) estão hardcoded. Prefira constantes/enum compartilhadas para evitar drift entre frontend e backend.

Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-provider-verification.ts` around
lines 27 - 35, Replace the hardcoded status strings in the
use-provider-verification hook by importing and using the shared enum/constants
from Shared.Contracts (e.g. Shared.Contracts.ProviderStatus) instead of literal
values; update the statusLabels map (symbol: statusLabels) to use those enum
members as keys and type the map accordingly, and use data.status (from the
useProviderVerification hook) against the shared enum so frontend stays aligned
with backend/shared definitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Modules/Bookings/API/Endpoints/BookingEventsEndpoint.cs`:
- Around line 18-47: The SSE endpoint GetBookingEvents currently only enforces
authentication but not per-booking authorization; before calling
SseTopic.ForBooking(id) / sseHub.SubscribeAsync(...) retrieve the booking (e.g.,
via the same service/repository used by GetBookingByIdEndpoint such as
IBookingRepository/GetBookingByIdAsync or BookingService) and check the current
user (context.User.Claims or helper like GetUserId()) is the booking's client or
provider; if not, short-circuit with Forbid/Unauthorized; only then create the
topic and subscribe. Ensure you reference GetBookingEvents, SseTopic.ForBooking,
sseHub.SubscribeAsync and the booking lookup method used elsewhere, and keep
cancellation handling and streaming behavior unchanged.

In `@src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs`:
- Around line 21-53: The handler methods (the various HandleAsync overloads)
accept a CancellationToken but never forward it to PublishUpdate or to
sseHub.PublishAsync; modify each HandleAsync to pass the received
cancellationToken into PublishUpdate, change PublishUpdate signature to accept a
CancellationToken parameter (e.g., PublishUpdate(Guid bookingId, string status,
string message, CancellationToken cancellationToken)), and pass that token
through to the sseHub.PublishAsync call so the SSE publish can be cancelled
cooperatively.

In `@src/Modules/Providers/API/Endpoints/ProviderVerificationEventsEndpoint.cs`:
- Around line 18-45: The endpoint mapped by
MapGet("/{id:guid}/verification-events") currently calls .RequireAuthorization()
which only ensures authentication but does not prevent a non-owner authenticated
user from subscribing to another provider's SSE stream; update the authorization
so only the provider owner or admin can subscribe: either apply a dedicated
policy (e.g., use .RequireAuthorization("ProviderOwnerOrAdmin")) that checks the
route id against the user's provider id/roles, or perform an explicit check
inside the handler (read HttpContext.User claims, compare to the Guid id, and
return Forbid/Unauthorized before creating SseTopic.ForProviderVerification and
calling sseHub.SubscribeAsync) to block horizontal access.

In
`@src/Modules/Providers/Application/Events/ProviderVerificationStatusUpdatedSseHandler.cs`:
- Around line 17-27: The HandleAsync method accepts a CancellationToken but
doesn't pass it to the SSE publish call; update the call to
sseHub.PublishAsync(...) in
ProviderVerificationStatusUpdatedSseHandler.HandleAsync to pass the received
cancellationToken so PublishAsync(topic, data, cancellationToken) (or the
overload your sseHub exposes) is used, ensuring the
ProviderVerificationStatusUpdatedDomainEvent handling respects cancellation.

In `@src/Shared/Extensions/ServiceCollectionExtensions.cs`:
- Around line 141-145: O SseHub<T> está armazenando Channel<T> em um
ConcurrentBag e, por ser registrado via AddStreaming como Singleton, canais
nunca removidos causam vazamento de memória; corrija isso trocando ConcurrentBag
por uma estrutura removível (ex.: ConcurrentDictionary<string, Channel<T>>) em
SseHub<T>, atribuindo um ID único por conexão ao criar o Channel<T> em
SubscribeAsync, adicionando o par id->channel ao dicionário ao inscrever,
removendo explicitamente esse id do dicionário no bloco finally do
SubscribeAsync e encerrando/limpando o channel, e adicionalmente implemente um
mecanismo de limpeza periódica (ex.: timer interno no SseHub<T> ou
IHostedService associado) que percorre o dicionário e remove canais
fechados/inalcançáveis; mantenha a extensão AddStreaming (AddStreaming/
IServiceCollection) como ponto de registro, mas ensure SseHub<T> gerencia a
remoção/cleanup conforme descrito.

In `@src/Shared/Streaming/SseHub.cs`:
- Around line 28-29: O problema é que canais removidos não são excluídos de
_topics, causando vazamento durante churn SSE; altere a estrutura para permitir
remoção eficiente e atualize os pontos que manipulam assinaturas: troque _topics
de ConcurrentDictionary<string, ConcurrentBag<Channel<T>>> para
ConcurrentDictionary<string, ConcurrentDictionary<Channel<T>, byte>> (ou similar
que suporte TryRemove), atualize o fluxo de Subscribe/Unsubscribe para
adicionar/remover o Channel<T> do dicionário interno (usando TryAdd/TryRemove) e
ajuste PublishAsync para iterar nas chaves do dicionário interno em vez do
ConcurrentBag; isso garante que ao encerrar uma assinatura o canal é
efetivamente removido e PublishAsync não acumule canais mortos.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-sse.ts`:
- Around line 17-27: O hook useSse depende de useSession() do next-auth/react e
os testes (ex.: dashboard-client.test.tsx) não estão a fornecer esse provider;
para corrigir adicione um mock global de useSession() no arquivo de setup de
testes (__tests__/setup.ts) ou garanta que os testes que importam useSse
envolvam os componentes com SessionProvider; especificamente, exporte/forneça um
wrapper de teste que injete um session com accessToken ou stub useSession para
retornar { data: { accessToken: 'mock' } } e documente esse requisito para que
useSse, useSession e SessionProvider estejam corretamente simulados em todos os
testes.

---

Nitpick comments:
In `@src/Modules/Bookings/API/Endpoints/BookingEventsEndpoint.cs`:
- Around line 36-39: The code recreates JsonSerializerOptions per event in
BookingEventsEndpoint: stop allocating on each JsonSerializer.Serialize call by
introducing a reusable options instance (e.g., a private static readonly
JsonSerializerOptions) configured with PropertyNamingPolicy =
JsonNamingPolicy.CamelCase and use that cached instance in the
JsonSerializer.Serialize(`@event`, cachedOptions) call instead of newing options
each time.

In `@src/Modules/Providers/API/Endpoints/ProviderVerificationEventsEndpoint.cs`:
- Around line 33-36: The code currently creates a new JsonSerializerOptions
inside the event loop when calling JsonSerializer.Serialize(`@event`, new
JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }),
causing repeated allocations; refactor by extracting that options instance to a
single reusable static readonly field (e.g., private static readonly
JsonSerializerOptions _camelCaseSerializerOptions) and change the Serialize call
to use that field instead so the same JsonSerializerOptions is reused across
iterations.

In `@src/Shared/Streaming/SseHub.cs`:
- Around line 26-27: SseHub<T> is an in-memory singleton that won't forward
events across multiple app instances; modify the implementation so it uses a
distributed backplane or session-affinity: replace or augment the current
SseHub<T>/ISseHub<T> in-memory event store with a pub/sub layer (e.g., Redis
pub/sub or RabbitMQ) so Publish/Subscribe methods on SseHub<T> publish to the
backplane and subscribe handlers receive cross-instance events, or make session
affinity changes in hosting (sticky sessions) and document that requirement;
update the SseHub<T> methods that enqueue/broadcast events to call the chosen
backplane APIs and ensure subscription/cleanup logic maps to backplane
subscriptions.

In `@src/Web/MeAjudaAi.Web.Customer/hooks/use-provider-verification.ts`:
- Around line 27-35: Replace the hardcoded status strings in the
use-provider-verification hook by importing and using the shared enum/constants
from Shared.Contracts (e.g. Shared.Contracts.ProviderStatus) instead of literal
values; update the statusLabels map (symbol: statusLabels) to use those enum
members as keys and type the map accordingly, and use data.status (from the
useProviderVerification hook) against the shared enum so frontend stays aligned
with backend/shared definitions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 88cef375-4487-4960-85c6-90a0775cef1e

📥 Commits

Reviewing files that changed from the base of the PR and between 660da09 and ba54dfc.

⛔ Files ignored due to path filters (6)
  • package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
  • src/Web/MeAjudaAi.Web.Provider/app/page.tsx is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/hooks/use-provider-verification.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/hooks/use-sse.ts is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/MeAjudaAi.Web.Provider/package.json is excluded by !**/MeAjudaAi.Web.Provider/**
  • src/Web/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
📒 Files selected for processing (28)
  • .github/workflows/ci-backend.yml
  • package.json
  • src/Bootstrapper/MeAjudaAi.ApiService/Middlewares/GeographicRestrictionMiddleware.cs
  • src/Modules/Bookings/API/Endpoints/BookingEventsEndpoint.cs
  • src/Modules/Bookings/API/Endpoints/BookingsEndpoints.cs
  • src/Modules/Bookings/Application/DTOs/BookingStatusSseDto.cs
  • src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs
  • src/Modules/Bookings/Application/Extensions.cs
  • src/Modules/Providers/API/Endpoints/ProviderVerificationEventsEndpoint.cs
  • src/Modules/Providers/API/Endpoints/ProvidersModuleEndpoints.cs
  • src/Modules/Providers/Application/DTOs/ProviderVerificationSseDto.cs
  • src/Modules/Providers/Application/Events/ProviderVerificationStatusUpdatedSseHandler.cs
  • src/Modules/Providers/Application/Extensions.cs
  • src/Shared/Extensions/DocumentExtensions.cs
  • src/Shared/Extensions/ServiceCollectionExtensions.cs
  • src/Shared/Monitoring/BusinessMetricsMiddleware.cs
  • src/Shared/Streaming/SseHub.cs
  • src/Shared/Streaming/SseTopic.cs
  • src/Web/MeAjudaAi.Web.Customer/components/providers/dashboard-client.tsx
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-booking-events.ts
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-provider-verification.ts
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-sse.ts
  • src/Web/MeAjudaAi.Web.Customer/package.json
  • src/Web/package.json
  • tests/MeAjudaAi.ApiService.Tests/Unit/Middlewares/RequestLoggingMiddlewareTests.cs
  • tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Monitoring/BusinessMetricsMiddlewareTests.cs
✅ Files skipped from review due to trivial changes (6)
  • src/Web/MeAjudaAi.Web.Customer/package.json
  • src/Web/package.json
  • src/Shared/Streaming/SseTopic.cs
  • .github/workflows/ci-backend.yml
  • package.json
  • src/Modules/Providers/Application/DTOs/ProviderVerificationSseDto.cs
🚧 Files skipped from review as they are similar to previous changes (5)
  • tests/MeAjudaAi.Shared.Tests/Unit/Monitoring/BusinessMetricsMiddlewareTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs
  • src/Shared/Monitoring/BusinessMetricsMiddleware.cs
  • tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs
  • src/Shared/Extensions/DocumentExtensions.cs

Comment thread src/Modules/Bookings/API/Endpoints/BookingEventsEndpoint.cs
Comment thread src/Shared/Extensions/ServiceCollectionExtensions.cs
Comment thread src/Shared/Streaming/SseHub.cs Outdated
Comment thread src/Web/MeAjudaAi.Web.Customer/hooks/use-sse.ts
Filipe Frigini added 2 commits April 29, 2026 12:55
@frigini
Copy link
Copy Markdown
Owner Author

frigini commented Apr 29, 2026

@coderabbitai a cobertura geral do backend caiu para 89.87% com as ultimas alterações, preciso recuperar para talvez 91%, poderia encontrar os gaps?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Vou analisar os arquivos novos e modificados neste PR para mapear os gaps de cobertura.


🏁 Script executed:

#!/bin/bash
echo "=== New SSE handler files — check for [ExcludeFromCodeCoverage] ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -v 'Test' | grep -iE '(SseHub|SseTopic|BookingRealtimeEvents|ProviderVerificationStatusUpdatedSseHandler|OptionsTopicNameConvention|BookingEventsEndpoint|ProviderVerificationEventsEndpoint)' | sort | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== DocumentExtensions tests ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'DocumentExtension.*Test|Test.*DocumentExtension' | sort | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== PhoneNumberValidator tests ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'PhoneNumberValidator.*Test|Test.*PhoneNumberValidator' | sort | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== PiiMaskingHelper tests ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'PiiMasking.*Test|Test.*PiiMasking' | sort | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== CacheMetrics tests ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'CacheMetrics.*Test|Test.*CacheMetrics' | sort | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== RegisterCustomerCommandHandler tests ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'RegisterCustomer.*Test|Test.*RegisterCustomer' | sort | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== GeographicRestriction tests ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'GeographicRestriction.*Test|Test.*GeographicRestriction' | sort | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== BusinessMetrics tests ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'BusinessMetrics.*Test|Test.*BusinessMetrics' | sort | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== All test files in Shared.Tests ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep 'Shared.Tests\|Shared\.Tests' | sort

echo ""
echo "=== All test files in ApiService.Tests ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep 'ApiService.Tests\|ApiService\.Tests' | sort

echo ""
echo "=== DocumentExtensions production file ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'DocumentExtensions\.cs' | grep -v Test | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== PhoneNumberValidator production file ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'PhoneNumberValidator\.cs' | grep -v Test | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== CacheMetrics production file ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'CacheMetrics\.cs' | grep -v Test | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== MessageBusOptions/OptionsTopicNameConvention ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'MessageBusOptions\.cs' | grep -v Test | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== coverlet.runsettings — ExcludeByAttribute ==="
fd -e runsettings | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"'

echo ""
echo "=== ci-backend.yml classfilters (coverage section) ==="
fd -e yml | grep -i 'ci-backend' | xargs -I{} sh -c 'grep -n "classfilter\|ExcludeByAttribute\|Endpoint\|coverage\|threshold" "{}"' 2>/dev/null | head -80

Length of output: 120951


@frigini Análise concluída. Aqui estão os gaps específicos que causaram a queda para 89.87%, em ordem de impacto:


Diagnóstico de Coverage Gaps

🔴 Gap 1 — BookingRealtimeEventsHandler (~51 linhas, sem testes, sem [ExcludeFromCodeCoverage])

src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs

5 métodos HandleAsync + 1 PublishUpdate privado — zero cobertura. É o maior gap isolado desta sprint.

🔴 Gap 2 — SseHub<T> (~57 linhas, sem testes, sem [ExcludeFromCodeCoverage])

src/Shared/Streaming/SseHub.cs

PublishAsync e SubscribeAsync completos sem nenhuma cobertura.

🟡 Gap 3 — ProviderVerificationStatusUpdatedSseHandler (~25 linhas, sem testes, sem [ExcludeFromCodeCoverage])

src/Modules/Providers/Application/Events/ProviderVerificationStatusUpdatedSseHandler.cs

1 método HandleAsync com lógica de publicação SSE.

🟡 Gap 4 — SseTopic (~10 linhas, sem [ExcludeFromCodeCoverage])

src/Shared/Streaming/SseTopic.cs

2 métodos utilitários simples, mas contam no total.

🟡 Gap 5 — DocumentExtensions — branches de guard internos inacessíveis (Lines 102–109)

// src/Shared/Extensions/DocumentExtensions.cs
if (firstMultipliers.Length >= expectedLength)
    throw new ArgumentException(...); // ← nunca executado pelos callers internos
if (secondMultipliers.Length >= expectedLength)
    throw new ArgumentException(...); // ← idem
if (secondMultipliers.Length != firstMultipliers.Length + 1)
    throw new ArgumentException(...); // ← idem

São guards num método private chamado apenas com arrays hardcoded em IsValidCpf/IsValidCnpj. Essas 3 condições de throw são linhas não cobertas porque os callers sempre passam valores válidos.

🟡 Gap 6 — Endpoints SSE (se contados via classfilter)

src/Modules/Bookings/API/Endpoints/BookingEventsEndpoint.cs       (~68 linhas)
src/Modules/Providers/API/Endpoints/ProviderVerificationEventsEndpoint.cs (~57 linhas)

Sem [ExcludeFromCodeCoverage]. Se +MeAjudaAi.Modules.Bookings.API.*Endpoint estiver no classfilter do CI, o BookingEventsEndpoint está contando.


Plano de Recuperação para ≥ 91%

Ação 1 — Marcar infraestrutura SSE como excluída (rápido, zero teste necessário)

// src/Shared/Streaming/SseHub.cs
using System.Diagnostics.CodeAnalysis;

[ExcludeFromCodeCoverage]
public class SseHub<T> : ISseHub<T> { ... }
// src/Shared/Streaming/SseTopic.cs
using System.Diagnostics.CodeAnalysis;

[ExcludeFromCodeCoverage]
public static class SseTopic { ... }
// src/Modules/Bookings/API/Endpoints/BookingEventsEndpoint.cs
[ExcludeFromCodeCoverage]
public class BookingEventsEndpoint : IEndpoint { ... }

// src/Modules/Providers/API/Endpoints/ProviderVerificationEventsEndpoint.cs
[ExcludeFromCodeCoverage]
public class ProviderVerificationEventsEndpoint : IEndpoint { ... }

Ação 2 — Adicionar testes para BookingRealtimeEventsHandler (maior ganho)

Arquivo: src/Modules/Bookings/Tests/Unit/Application/Events/BookingRealtimeEventsHandlerTests.cs

using MeAjudaAi.Modules.Bookings.Application.DTOs;
using MeAjudaAi.Modules.Bookings.Application.Events;
using MeAjudaAi.Modules.Bookings.Domain.Events;
using MeAjudaAi.Shared.Streaming;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;

namespace MeAjudaAi.Modules.Bookings.Tests.Unit.Application.Events;

[Trait("Category", "Unit")]
public class BookingRealtimeEventsHandlerTests
{
    private readonly Mock<ISseHub<BookingStatusSseDto>> _sseHubMock = new();
    private readonly Mock<ILogger<BookingRealtimeEventsHandler>> _loggerMock = new();
    private readonly BookingRealtimeEventsHandler _sut;

    public BookingRealtimeEventsHandlerTests()
    {
        _sseHubMock
            .Setup(h => h.PublishAsync(It.IsAny<string>(), It.IsAny<BookingStatusSseDto>(), It.IsAny<CancellationToken>()))
            .Returns(Task.CompletedTask);
        _sut = new BookingRealtimeEventsHandler(_sseHubMock.Object, _loggerMock.Object);
    }

    [Fact]
    public async Task HandleAsync_BookingCreated_ShouldPublishCreatedStatus()
    {
        // Arrange
        var bookingId = Guid.NewGuid();
        var `@event` = new BookingCreatedDomainEvent(bookingId);

        // Act
        await _sut.HandleAsync(`@event`);

        // Assert
        _sseHubMock.Verify(h => h.PublishAsync(
            $"bookings:{bookingId}",
            It.Is<BookingStatusSseDto>(d => d.BookingId == bookingId && d.Status == "Created"),
            It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task HandleAsync_BookingConfirmed_ShouldPublishConfirmedStatus()
    {
        // Arrange
        var bookingId = Guid.NewGuid();
        var `@event` = new BookingConfirmedDomainEvent(bookingId);

        // Act
        await _sut.HandleAsync(`@event`);

        // Assert
        _sseHubMock.Verify(h => h.PublishAsync(
            $"bookings:{bookingId}",
            It.Is<BookingStatusSseDto>(d => d.Status == "Confirmed"),
            It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task HandleAsync_BookingCancelled_ShouldPublishCancelledStatus()
    {
        // Arrange
        var `@event` = new BookingCancelledDomainEvent(Guid.NewGuid());

        // Act
        await _sut.HandleAsync(`@event`);

        // Assert
        _sseHubMock.Verify(h => h.PublishAsync(It.IsAny<string>(),
            It.Is<BookingStatusSseDto>(d => d.Status == "Cancelled"),
            It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task HandleAsync_BookingRejected_ShouldPublishRejectedStatus()
    {
        // Arrange
        var `@event` = new BookingRejectedDomainEvent(Guid.NewGuid());

        // Act
        await _sut.HandleAsync(`@event`);

        // Assert
        _sseHubMock.Verify(h => h.PublishAsync(It.IsAny<string>(),
            It.Is<BookingStatusSseDto>(d => d.Status == "Rejected"),
            It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task HandleAsync_BookingCompleted_ShouldPublishCompletedStatus()
    {
        // Arrange
        var `@event` = new BookingCompletedDomainEvent(Guid.NewGuid());

        // Act
        await _sut.HandleAsync(`@event`);

        // Assert
        _sseHubMock.Verify(h => h.PublishAsync(It.IsAny<string>(),
            It.Is<BookingStatusSseDto>(d => d.Status == "Completed"),
            It.IsAny<CancellationToken>()), Times.Once);
    }
}

⚠️ Ajuste os construtores de BookingCreatedDomainEvent, BookingConfirmedDomainEvent, etc. conforme as assinaturas reais dos eventos de domínio no projeto.

Ação 3 — Adicionar testes para ProviderVerificationStatusUpdatedSseHandler

Arquivo: src/Modules/Providers/Tests/Unit/Application/Events/ProviderVerificationStatusUpdatedSseHandlerTests.cs

using MeAjudaAi.Modules.Providers.Application.DTOs;
using MeAjudaAi.Modules.Providers.Application.Events;
using MeAjudaAi.Modules.Providers.Domain.Events;
using MeAjudaAi.Shared.Streaming;
using Microsoft.Extensions.Logging;
using Moq;

namespace MeAjudaAi.Modules.Providers.Tests.Unit.Application.Events;

[Trait("Category", "Unit")]
public class ProviderVerificationStatusUpdatedSseHandlerTests
{
    [Fact]
    public async Task HandleAsync_ShouldPublishVerificationStatusToHub()
    {
        // Arrange
        var sseHubMock = new Mock<ISseHub<ProviderVerificationSseDto>>();
        sseHubMock
            .Setup(h => h.PublishAsync(It.IsAny<string>(), It.IsAny<ProviderVerificationSseDto>(), It.IsAny<CancellationToken>()))
            .Returns(Task.CompletedTask);

        var logger = Mock.Of<ILogger<ProviderVerificationStatusUpdatedSseHandler>>();
        var sut = new ProviderVerificationStatusUpdatedSseHandler(sseHubMock.Object, logger);

        var providerId = Guid.NewGuid();
        // Ajuste o construtor conforme a assinatura real do evento de domínio:
        var `@event` = new ProviderVerificationStatusUpdatedDomainEvent(providerId, /* outros parâmetros */);

        // Act
        await sut.HandleAsync(`@event`);

        // Assert
        sseHubMock.Verify(h => h.PublishAsync(
            $"providers:{providerId}:verification",
            It.Is<ProviderVerificationSseDto>(d => d.ProviderId == providerId),
            It.IsAny<CancellationToken>()), Times.Once);
    }
}

Ação 4 — Remover guards inacessíveis em DocumentExtensions (ou simplificar)

As 3 linhas de throw new ArgumentException(...) no método privado IsValidDocument (Lines 103, 106, 109) são dead code defensivo — úteis em biblioteca pública, mas num método private com callers internos hardcoded são apenas linhas não cobertas. Recomendo removê-las:

-        if (firstMultipliers.Length >= expectedLength)
-            throw new ArgumentException("Os multiplicadores do primeiro dígito devem ser menores que o tamanho total do documento.", nameof(firstMultipliers));
-        
-        if (secondMultipliers.Length >= expectedLength)
-            throw new ArgumentException("Os multiplicadores do segundo dígito devem ser menores que o tamanho total do documento.", nameof(secondMultipliers));
-
-        if (secondMultipliers.Length != firstMultipliers.Length + 1)
-            throw new ArgumentException("O segundo conjunto de multiplicadores deve ter exatamente um elemento a mais que o primeiro.");

Resumo do impacto esperado

Ação Linhas recuperadas (est.) Esforço
[ExcludeFromCodeCoverage] em SseHub<T>, SseTopic, 2 endpoints ~190 linhas excluídas do denominador 5 min
Testes BookingRealtimeEventsHandler +51 linhas cobertas 30 min
Testes ProviderVerificationStatusUpdatedSseHandler +25 linhas cobertas 15 min
Remover guards mortos DocumentExtensions +6 linhas cobertas 2 min

Com as ações 1+2+3+4 combinadas, a cobertura deve voltar facilmente para ≥ 91%.


🧠 Learnings used
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-20T22:04:25.820Z
Learning: MeAjudaAi (PRs futuros): para cobertura global, NÃO excluir *Configuration e *Extensions (há smoke tests exercitando-os); também não excluir *.Monitoring.*, MeAjudaAi.Shared.Jobs.* e MeAjudaAi.Shared.Mediator.*. Manter excluídos apenas Request/Response/Dto/DTO/IntegrationEvent e *DbContextFactory; Endpoints podem ficar excluídos globalmente, a menos que sejam white-listados por módulo com E2E (ex.: +MeAjudaAi.Modules.Payments.API.*Endpoint; -*Endpoint).

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi ci-backend.yml (linha 272): Para tornar os endpoints de um módulo visíveis na cobertura do ReportGenerator, adicionar o assembly antes de `-*Endpoint` no classfilters, ex: `+MeAjudaAi.Modules.Bookings.API.*Endpoint` (após `+MeAjudaAi.Modules.Payments.API.*Endpoint`). Isso é necessário apenas para módulos com cobertura E2E. Módulos sem E2E mantêm os endpoints excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi Shared coverage: Classes de infraestrutura de messaging que dependem de broker RabbitMQ real (RabbitMqDeadLetterService, DeadLetterExtensions, FailedMessageInfo, FailureAttempt, EnvironmentMetadata) são candidatas a [ExcludeFromCodeCoverage] pois não são testáveis unitariamente. Utilitários puros (PhoneNumberValidator, PiiMaskingHelper, SlugHelper, UuidGenerator) devem ter testes unitários adicionados em vez de serem excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi Bookings.API: Classes não-endpoint dentro do assembly da API (Extensions, BookingsEndpoints, ProviderAuthorizationResult, ProviderAuthorizationResultExtensions) não são capturadas pelo classfilter `+MeAjudaAi.Modules.Bookings.API.*Endpoint` e devem receber [ExcludeFromCodeCoverage] individualmente (são glue/wiring). Já `ProviderAuthorizationResolver` contém lógica real de autorização e deve ter testes unitários próprios.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-19T15:37:29.889Z
Learning: MeAjudaAi: objetivo de cobertura global é ≥90% (linhas), mantendo thresholds 90/80 no CI; quando a meta estiver ameaçada, preferir excluir “glue/DTO” (Endpoints/Extensions/Options/IntegrationEvent/DbContextFactory) e adicionar testes de alto impacto, em vez de reduzir o threshold.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-01-16T21:50:23.593Z
Learning: For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups; prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Providers/Tests/Unit/Application/Queries/GetProvidersByIdsQueryTests.cs:65-77
Timestamp: 2025-12-18T00:35:33.829Z
Learning: MeAjudaAi repo: Exceção à política de idioma para comentários — em testes, os cabeçalhos AAA (Arrange, Act, Assert) devem permanecer em inglês e não devem ser traduzidos. Comentários/XML continuam em português; logs em inglês; mensagens ao usuário (validações/APIs) em português.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi coverlet.runsettings: `ExcludeByAttribute` deve incluir `ExcludeFromCodeCoverage` (além de Obsolete,GeneratedCode,CompilerGenerated). Sem isso, todas as classes decoradas com [ExcludeFromCodeCoverage] continuam sendo instrumentadas pelo coverlet e contam como linhas não cobertas no relatório, reduzindo a cobertura global. Correção: `<ExcludeByAttribute>Obsolete,GeneratedCode,CompilerGenerated,ExcludeFromCodeCoverage</ExcludeByAttribute>`.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:26:37.409Z
Learning: MeAjudaAi logging policy: logs must be in English, comments may remain in Portuguese. Portuguese logs were additionally found in Users (API/Extensions.cs:83), Providers (API/Extensions.cs:74), ServiceCatalogs (API/Extensions.cs:78,86,93), and Locations (multiple clients/services lines listed). SearchProviders module currently has no Portuguese logs.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:09:54.585Z
Learning: In the MeAjudaAi codebase, when reviewing log messages in application handlers, verify they are in English. The file src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs had Portuguese logs that violated the English-only logging policy.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (6)
tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs (1)

271-273: Ajuste opcional de mensagem de assert para refletir o cenário real.

Aqui o teste usa headers separados (X-User-City/X-User-State), mas a mensagem fala em “Malformed location header (empty state)”, o que pode confundir diagnóstico de falha.

✏️ Sugestão de ajuste
-            // Assert - formato "City|" é malformado e deve ser bloqueado
+            // Assert - estado vazio no header deve ser bloqueado (sem fail-open)
             response.StatusCode.Should().Be(HttpStatusCode.UnavailableForLegalReasons,
-                "Malformed location header (empty state) should be rejected");
+                "Empty state header should be rejected (no fail-open)");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs`
around lines 271 - 273, Update the assert failure message in
GeographicRestrictionIntegrationTests where
response.StatusCode.Should().Be(HttpStatusCode.UnavailableForLegalReasons, ...)
is used: change the message to explicitly state the tested condition using
separate headers (e.g., "Malformed location headers (empty X-User-State) should
be rejected") so it accurately reflects that X-User-City/X-User-State were used
and the state header was empty; locate the assertion in the test method that
builds the request with X-User-City/X-User-State and replace the current
"Malformed location header (empty state) should be rejected" text with a clearer
message referencing the empty X-User-State header.
src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs (1)

20-40: Reduzir risco de drift: evitar strings mágicas de status.

Os valores "Created", "Confirmed", "Cancelled", "Rejected" e "Completed" estão duplicados no handler/testes/frontend. Centralizar em contrato compartilhado (ou constants únicas) evita divergência silenciosa.

Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs`
around lines 20 - 40, Substitua as strings mágicas de status usadas nas
overloads HandleAsync (ex.: BookingCreatedDomainEvent,
BookingConfirmedDomainEvent, BookingCancelledDomainEvent,
BookingRejectedDomainEvent, BookingCompletedDomainEvent) e no call PublishUpdate
por constantes/enums centralizadas em Shared.Contracts (por exemplo um
BookingStatus enum ou Status constants): importe o contrato compartilhado no
topo da classe BookingRealtimeEventsHandler e use os membros tipados (ex.:
BookingStatus.Created) em vez de
"Created"/"Confirmed"/"Cancelled"/"Rejected"/"Completed"; aplique a mesma
mudança em quaisquer testes ou código frontend que referenciem essas strings
para evitar drift.
src/Modules/Bookings/Tests/Unit/Application/Events/BookingRealtimeEventsHandlerTests.cs (1)

35-103: Blindar i18n/UX: validar também Message nos asserts do DTO.

Atualmente os testes garantem Status, mas não garantem o texto exibível no cliente. Vale incluir Message (e BookingId nos demais casos) para evitar regressão silenciosa.

✅ Exemplo de ajuste (padrão para os 5 testes)
         _sseHubMock.Verify(h => h.PublishAsync(
             SseTopic.ForBooking(bookingId),
-            It.Is<BookingStatusSseDto>(d => d.Status == "Confirmed"),
+            It.Is<BookingStatusSseDto>(d =>
+                d.BookingId == bookingId &&
+                d.Status == "Confirmed" &&
+                d.Message == "Reserva confirmada pelo prestador"),
             It.IsAny<CancellationToken>()), Times.Once);

Based on learnings: user-facing validation messages and API responses shown to the frontend must be in Portuguese.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Bookings/Tests/Unit/Application/Events/BookingRealtimeEventsHandlerTests.cs`
around lines 35 - 103, Update each test that calls _sseHubMock.Verify for
SseTopic.ForBooking to also assert the DTO's BookingId and Message (in addition
to Status) so the published BookingStatusSseDto contains the correct booking
identifier and Portuguese user-facing text; specifically modify the predicate in
the It.Is<BookingStatusSseDto>(...) used in tests
HandleAsync_BookingCreated_ShouldPublishCreatedStatus,
HandleAsync_BookingConfirmed_ShouldPublishConfirmedStatus,
HandleAsync_BookingCancelled_ShouldPublishCancelledStatus,
HandleAsync_BookingRejected_ShouldPublishRejectedStatus and
HandleAsync_BookingCompleted_ShouldPublishCompletedStatus to check d.BookingId
== bookingId and that d.Message equals the expected Portuguese message for each
status.
src/Modules/Providers/Tests/Unit/Application/Events/ProviderVerificationStatusUpdatedSseHandlerTests.cs (1)

13-65: Adicionar teste explícito de propagação de CancellationToken.

Hoje os testes validam o conteúdo publicado, mas não blindam regressão de cancelamento (chamadas em Line 32 e Line 59 sem token explícito).

✅ Sugestão de teste adicional
+    [Fact]
+    public async Task HandleAsync_ShouldForwardCancellationTokenToPublishAsync()
+    {
+        var sseHubMock = new Mock<ISseHub<ProviderVerificationSseDto>>();
+        sseHubMock
+            .Setup(h => h.PublishAsync(It.IsAny<string>(), It.IsAny<ProviderVerificationSseDto>(), It.IsAny<CancellationToken>()))
+            .Returns(Task.CompletedTask);
+
+        var logger = Mock.Of<ILogger<ProviderVerificationStatusUpdatedSseHandler>>();
+        var sut = new ProviderVerificationStatusUpdatedSseHandler(sseHubMock.Object, logger);
+
+        var providerId = Guid.NewGuid();
+        var `@event` = new ProviderVerificationStatusUpdatedDomainEvent(
+            providerId, 1, EVerificationStatus.Pending, EVerificationStatus.Verified, "admin");
+        using var cts = new CancellationTokenSource();
+
+        await sut.HandleAsync(`@event`, cts.Token);
+
+        sseHubMock.Verify(h => h.PublishAsync(
+            SseTopic.ForProviderVerification(providerId),
+            It.IsAny<ProviderVerificationSseDto>(),
+            cts.Token), Times.Once);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Providers/Tests/Unit/Application/Events/ProviderVerificationStatusUpdatedSseHandlerTests.cs`
around lines 13 - 65, The tests currently assert the published DTO but don't
ensure the CancellationToken passed to
ProviderVerificationStatusUpdatedSseHandler.HandleAsync is forwarded to
ISseHub.PublishAsync; update both tests
(HandleAsync_ShouldPublishVerificationStatusToHub and
HandleAsync_ShouldPublishRejectedStatus) to create an explicit CancellationToken
(e.g., via new CancellationTokenSource().Token), call sut.HandleAsync(`@event`,
cancellationToken) and change the Verify to assert the PublishAsync call
received that exact token (use It.Is<CancellationToken>(t => t ==
cancellationToken)); also update the Setup on the
ISseHub<ProviderVerificationSseDto> mock to accept that token so the test still
returns Task.CompletedTask. This ensures
ProviderVerificationStatusUpdatedSseHandler.HandleAsync correctly propagates the
cancellation token to ISseHub.PublishAsync.
src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs (1)

91-95: Sugestão: tornar o split de nome robusto para qualquer whitespace.

Atualmente o corte usa apenas ' '. Em entradas com tab/line-break entre nome e sobrenome, pode cair em validação incorreta de sobrenome ausente.

💡 Diff sugerido
-        var nameSpan = command.Name.AsSpan().Trim();
-        var firstSpace = nameSpan.IndexOf(' ');
-        
-        var firstName = firstSpace >= 0 ? nameSpan[..firstSpace].ToString() : nameSpan.ToString();
-        var lastName = firstSpace >= 0 ? nameSpan[(firstSpace + 1)..].TrimStart().ToString() : string.Empty;
+        var nameSpan = command.Name.AsSpan().Trim();
+        var splitIndex = -1;
+        for (var i = 0; i < nameSpan.Length; i++)
+        {
+            if (char.IsWhiteSpace(nameSpan[i]))
+            {
+                splitIndex = i;
+                break;
+            }
+        }
+
+        var firstName = splitIndex >= 0 ? nameSpan[..splitIndex].ToString() : nameSpan.ToString();
+        var lastName = splitIndex >= 0 ? nameSpan[(splitIndex + 1)..].Trim().ToString() : string.Empty;

Also applies to: 102-105

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs`
around lines 91 - 95, The current name-splitting logic in
RegisterCustomerCommandHandler uses a literal space character which fails on
tabs/newlines; update the parsing of command.Name (the
nameSpan/firstSpace/firstName/lastName logic) to split on any whitespace (e.g.,
use command.Name.Split((char[])null, StringSplitOptions.RemoveEmptyEntries) or
iterate using char.IsWhiteSpace) and then set firstName to the first token and
lastName to the remainder joined with a single space (apply the same change to
the second occurrence of this logic later in the file).
src/Shared/Monitoring/BusinessMetricsMiddleware.cs (1)

56-60: Simplifique o predicado de rota de usuários para reduzir complexidade no hot path.

Aqui há checks redundantes: o Regex.IsMatch("^/api/v\\d+/users$") já garante versão válida + match exato do endpoint. Manter StartsWith + Slice + EndsWith no mesmo bloco aumenta custo e manutenção sem ganho funcional.

♻️ Proposta de simplificação
- var isUserPath = pathSpan.Equals("/api/users", StringComparison.OrdinalIgnoreCase) || 
-                  (pathSpan.StartsWith("/api/v", StringComparison.OrdinalIgnoreCase) && 
-                   pathSpan.Slice(5).IndexOf('/') > 0 && 
-                   pathValue.EndsWith("/users", StringComparison.OrdinalIgnoreCase) &&
-                   Regex.IsMatch(pathValue, @"^/api/v\d+/users$", RegexOptions.IgnoreCase));
+ var isUserPath = pathSpan.Equals("/api/users", StringComparison.OrdinalIgnoreCase) ||
+                  Regex.IsMatch(pathValue, @"^/api/v\d+/users$", RegexOptions.IgnoreCase);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Monitoring/BusinessMetricsMiddleware.cs` around lines 56 - 60, O
predicado que define isUserPath mistura checagens redundantes
(StartsWith/Slice/EndsWith) com Regex.IsMatch; simplifique para reduzir custo no
hot path mantendo o comportamento: substituir a expressão atual por uma
verificação simples que aceita exatamente "/api/users" (comparação com
pathSpan/pathValue usando StringComparison.OrdinalIgnoreCase) ou aplica apenas o
Regex.IsMatch para o padrão de versão (por exemplo @"^/api/v\d+/users$") contra
pathValue; ajuste o uso de pathSpan/pathValue conforme necessário para evitar
chamadas extras e preserve o nome isUserPath para clareza.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Shared/Messaging/Options/MessageBusOptions.cs`:
- Around line 27-35: The current topic naming logic only takes the final
namespace segment (lastPart) causing many different events to map to
"events.events"; change the topic key to incorporate the event type name to make
it unique: replace the use of only nsSpan/lastPart in the return with a
combination that includes type.Name (for example, use
type.Name.ToLowerInvariant() + "." + lastPart.ToString().ToLowerInvariant() or
simply type.Name.ToLowerInvariant() + ".events"); update the return expression
where lastPart is used so topics are generated from the event type name plus the
suffix instead of only the namespace tail.

In
`@tests/MeAjudaAi.Shared.Tests/Unit/Monitoring/BusinessMetricsMiddlewareTests.cs`:
- Around line 170-189: The test promises "no business metrics" but only asserts
on measurements with tag source=api; change the assertion to verify that no
business metrics were recorded at all after invoking
BusinessMetricsMiddleware.InvokeAsync by asserting that the _longMeasurements
collection is empty (replace the filtered registrationMetrics check with a
direct _longMeasurements.Should().BeEmpty()). This ensures the test fails if any
metric (not only source=api) was emitted for the unversioned path.

---

Nitpick comments:
In `@src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs`:
- Around line 20-40: Substitua as strings mágicas de status usadas nas overloads
HandleAsync (ex.: BookingCreatedDomainEvent, BookingConfirmedDomainEvent,
BookingCancelledDomainEvent, BookingRejectedDomainEvent,
BookingCompletedDomainEvent) e no call PublishUpdate por constantes/enums
centralizadas em Shared.Contracts (por exemplo um BookingStatus enum ou Status
constants): importe o contrato compartilhado no topo da classe
BookingRealtimeEventsHandler e use os membros tipados (ex.:
BookingStatus.Created) em vez de
"Created"/"Confirmed"/"Cancelled"/"Rejected"/"Completed"; aplique a mesma
mudança em quaisquer testes ou código frontend que referenciem essas strings
para evitar drift.

In
`@src/Modules/Bookings/Tests/Unit/Application/Events/BookingRealtimeEventsHandlerTests.cs`:
- Around line 35-103: Update each test that calls _sseHubMock.Verify for
SseTopic.ForBooking to also assert the DTO's BookingId and Message (in addition
to Status) so the published BookingStatusSseDto contains the correct booking
identifier and Portuguese user-facing text; specifically modify the predicate in
the It.Is<BookingStatusSseDto>(...) used in tests
HandleAsync_BookingCreated_ShouldPublishCreatedStatus,
HandleAsync_BookingConfirmed_ShouldPublishConfirmedStatus,
HandleAsync_BookingCancelled_ShouldPublishCancelledStatus,
HandleAsync_BookingRejected_ShouldPublishRejectedStatus and
HandleAsync_BookingCompleted_ShouldPublishCompletedStatus to check d.BookingId
== bookingId and that d.Message equals the expected Portuguese message for each
status.

In
`@src/Modules/Providers/Tests/Unit/Application/Events/ProviderVerificationStatusUpdatedSseHandlerTests.cs`:
- Around line 13-65: The tests currently assert the published DTO but don't
ensure the CancellationToken passed to
ProviderVerificationStatusUpdatedSseHandler.HandleAsync is forwarded to
ISseHub.PublishAsync; update both tests
(HandleAsync_ShouldPublishVerificationStatusToHub and
HandleAsync_ShouldPublishRejectedStatus) to create an explicit CancellationToken
(e.g., via new CancellationTokenSource().Token), call sut.HandleAsync(`@event`,
cancellationToken) and change the Verify to assert the PublishAsync call
received that exact token (use It.Is<CancellationToken>(t => t ==
cancellationToken)); also update the Setup on the
ISseHub<ProviderVerificationSseDto> mock to accept that token so the test still
returns Task.CompletedTask. This ensures
ProviderVerificationStatusUpdatedSseHandler.HandleAsync correctly propagates the
cancellation token to ISseHub.PublishAsync.

In
`@src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs`:
- Around line 91-95: The current name-splitting logic in
RegisterCustomerCommandHandler uses a literal space character which fails on
tabs/newlines; update the parsing of command.Name (the
nameSpan/firstSpace/firstName/lastName logic) to split on any whitespace (e.g.,
use command.Name.Split((char[])null, StringSplitOptions.RemoveEmptyEntries) or
iterate using char.IsWhiteSpace) and then set firstName to the first token and
lastName to the remainder joined with a single space (apply the same change to
the second occurrence of this logic later in the file).

In `@src/Shared/Monitoring/BusinessMetricsMiddleware.cs`:
- Around line 56-60: O predicado que define isUserPath mistura checagens
redundantes (StartsWith/Slice/EndsWith) com Regex.IsMatch; simplifique para
reduzir custo no hot path mantendo o comportamento: substituir a expressão atual
por uma verificação simples que aceita exatamente "/api/users" (comparação com
pathSpan/pathValue usando StringComparison.OrdinalIgnoreCase) ou aplica apenas o
Regex.IsMatch para o padrão de versão (por exemplo @"^/api/v\d+/users$") contra
pathValue; ajuste o uso de pathSpan/pathValue conforme necessário para evitar
chamadas extras e preserve o nome isUserPath para clareza.

In
`@tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs`:
- Around line 271-273: Update the assert failure message in
GeographicRestrictionIntegrationTests where
response.StatusCode.Should().Be(HttpStatusCode.UnavailableForLegalReasons, ...)
is used: change the message to explicitly state the tested condition using
separate headers (e.g., "Malformed location headers (empty X-User-State) should
be rejected") so it accurately reflects that X-User-City/X-User-State were used
and the state header was empty; locate the assertion in the test method that
builds the request with X-User-City/X-User-State and replace the current
"Malformed location header (empty state) should be rejected" text with a clearer
message referencing the empty X-User-State header.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 707ea872-cb1a-4c26-bc20-e7c50ded0de1

📥 Commits

Reviewing files that changed from the base of the PR and between ba54dfc and 5ddbd7f.

📒 Files selected for processing (18)
  • .github/workflows/ci-backend.yml
  • src/Modules/Bookings/API/Endpoints/BookingEventsEndpoint.cs
  • src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs
  • src/Modules/Bookings/Tests/Unit/Application/Events/BookingRealtimeEventsHandlerTests.cs
  • src/Modules/Providers/API/Endpoints/ProviderVerificationEventsEndpoint.cs
  • src/Modules/Providers/Application/Events/ProviderVerificationStatusUpdatedSseHandler.cs
  • src/Modules/Providers/Tests/Unit/Application/Events/ProviderVerificationStatusUpdatedSseHandlerTests.cs
  • src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs
  • src/Shared/Messaging/Options/MessageBusOptions.cs
  • src/Shared/Monitoring/BusinessMetricsMiddleware.cs
  • src/Shared/Streaming/SseHub.cs
  • src/Shared/Streaming/SseTopic.cs
  • src/Web/MeAjudaAi.Web.Customer/__tests__/components/providers/dashboard-client.test.tsx
  • src/Web/MeAjudaAi.Web.Customer/__tests__/setup.ts
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-provider-verification.ts
  • tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Monitoring/BusinessMetricsMiddlewareTests.cs
✅ Files skipped from review due to trivial changes (3)
  • src/Web/MeAjudaAi.Web.Customer/tests/setup.ts
  • .github/workflows/ci-backend.yml
  • src/Shared/Streaming/SseTopic.cs
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/Modules/Bookings/API/Endpoints/BookingEventsEndpoint.cs
  • src/Modules/Providers/API/Endpoints/ProviderVerificationEventsEndpoint.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Messaging/Conventions/OptionsTopicNameConventionTests.cs
  • src/Web/MeAjudaAi.Web.Customer/hooks/use-provider-verification.ts
  • src/Shared/Streaming/SseHub.cs

Comment thread src/Shared/Messaging/Options/MessageBusOptions.cs
@frigini
Copy link
Copy Markdown
Owner Author

frigini commented Apr 29, 2026

@coderabbitai Uma próxima melhoria/otimização para considerar, o protocolo rpc é usado para comunicação inter modules, deveria ser considerado para uma arquitetura monolítica modular como deste projeto? Se sim, em que cenários?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== ModuleApi / IModuleApi patterns ==="
rg --type cs -n -i '(IModuleApi|ModuleApi|ICommunicationsApi|ISearchProvidersApi|IProvidersApi|IUsersApi|IBookingsApi|IPaymentsApi)' -A3 -B3 | head -400

echo ""
echo "=== gRPC / RPC usage ==="
rg --type cs -n -i '(grpc|\.proto|ServiceBase|GrpcClient|ChannelBase|Grpc\.Core|ProtoBuf|MessagePack)' -A3 -B3 | head -200

echo ""
echo "=== Current inter-module dependency files ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE '(ModuleApi|IModuleApi|ModuleApiClient)' | sort

echo ""
echo "=== List all cross-module interface files ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE '(ICommunications|ISearchProviders|IProviders|IUsers|IBookings|IPayments|IDocuments|ILocations|IServiceCatalog)' | grep -i 'Api\|Client\|Contract' | sort

echo ""
echo "=== Sample ModuleApi implementation ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'CommunicationsModuleApi|SearchProvidersModuleApi' | head -5 | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"' 2>/dev/null | head -300

echo ""
echo "=== IProvidersApi usage ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'IProvidersApi' | head -5 | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"' 2>/dev/null | head -200

echo ""
echo "=== .csproj cross-module references ==="
fd -e csproj | grep -v '/obj/' | grep -v '/bin/' | xargs grep -l 'ProjectReference' 2>/dev/null | xargs -I{} sh -c 'echo "=== {} ===" && grep "ProjectReference" "{}"' | head -100

echo ""
echo "=== Existing message bus / RabbitMQ integration ==="
rg --type cs -n -i '(IMessageBus|IRabbitMq|Rebus|IBus|IPublishEndpoint)' -A2 -B2 | head -200

echo ""
echo "=== IEndpoint / Minimal API structure ==="
fd -e cs | grep -v '/obj/' | grep -v '/bin/' | grep -iE 'IEndpoint' | grep -v Test | head -3 | xargs -I{} sh -c 'echo "=== {} ===" && cat -n "{}"' 2>/dev/null | head -100

echo ""
echo "=== NuGet / package references for gRPC ==="
fd -e csproj | grep -v '/obj/' | grep -v '/bin/' | xargs grep -i 'grpc\|protobuf\|messagepack' 2>/dev/null | head -30

Length of output: 108612


@frigini Curto e direto: para o monólito modular atual, não recomendo introduzir RPC (gRPC/HTTP) entre módulos. Hoje vocês já têm um boundary limpo via I{Module}ModuleApi em Contracts + implementações in‑process por módulo, e eventos assíncronos via RabbitMQ. Colocar RPC no meio do mesmo processo adicionaria overhead e complexidade sem ganho de isolamento.

Quando faz sentido considerar RPC aqui

  • Módulo fora do processo (split seletivo):
    • SearchProviders (geospatial pesado) escalando de forma independente.
    • Communications (envio de e-mail/SMS/outbox) rodando como worker dedicado.
    • Qualquer pipeline de documentos que envolva antivírus/OCR.
  • Interoperabilidade entre linguagens/equipes em repositórios distintos, exigindo contrato forte e codegen compartilhado.
  • Server‑to‑server streaming de alto throughput ou contratos binários estáveis (gRPC) entre serviços — não para browser (que exigiria gRPC‑Web).
  • Requisitos de segurança/rede que obriguem boundary de processo (com policies, rate limiting, WAF) entre módulos específicos.

Quando manter como está (sem RPC)

  • Dentro do mesmo processo (default do projeto).
  • Chamadas síncronas de baixo custo entre módulos (ex.: Bookings → Providers) já resolvidas via IProvidersModuleApi.
  • Realtime para web: já coberto por SSE (browser‑friendly), sem necessidade de gRPC streaming.

Decisão prática (como se preparar sem pagar o custo agora)

  • Padrão dual adapter por interface de módulo:
    • InProcessAdapter: implementação atual (chama Application/Queries/Handlers diretamente).
    • RemoteAdapter (opcional):
      • HTTP/JSON usando HttpClient/Refit, ou
      • gRPC (apenas se o módulo realmente for extraído).
    • Seleção por configuração (ex.: ModuleCommunication:Mode = InProcess | Http | Grpc).
  • Onde começar se quiser o “seam”:
    • ISearchProvidersModuleApi e ICommunicationsModuleApi são os melhores candidatos a ganhar um RemoteAdapter opcional.
    • Manter os DTOs de boundary em Contracts (como já estão); se planejar gRPC no futuro, pode introduzir .proto só para esses módulos candidatos, mas gerar e usar o adapter remoto apenas quando o módulo sair do processo.

Mini matriz de decisão

  • Mesmo processo, latência baixa, .NET em ambos os lados → Mantenha ModuleApi in‑proc.
  • Serviço dedicado, escalável separadamente, CPU/I/O intenso → RPC entre serviços (gRPC se .NET↔.NET/alto throughput; HTTP/JSON se houver consumo amplo).
  • Fluxos assíncronos, eventual consistency, fan‑out → Mensageria (RabbitMQ) como já está.

Se quiser, preparo um plano enxuto para habilitar o “dual adapter” em 1 módulo (ex.: SearchProviders): interfaces que já existem, registro DI com toggle por IOptions, e um esqueleto de RemoteAdapter Refit — totalmente aditivo e sem breaking change.


🧠 Learnings used
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-20T22:04:25.820Z
Learning: MeAjudaAi (PRs futuros): para cobertura global, NÃO excluir *Configuration e *Extensions (há smoke tests exercitando-os); também não excluir *.Monitoring.*, MeAjudaAi.Shared.Jobs.* e MeAjudaAi.Shared.Mediator.*. Manter excluídos apenas Request/Response/Dto/DTO/IntegrationEvent e *DbContextFactory; Endpoints podem ficar excluídos globalmente, a menos que sejam white-listados por módulo com E2E (ex.: +MeAjudaAi.Modules.Payments.API.*Endpoint; -*Endpoint).

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi ci-backend.yml (linha 272): Para tornar os endpoints de um módulo visíveis na cobertura do ReportGenerator, adicionar o assembly antes de `-*Endpoint` no classfilters, ex: `+MeAjudaAi.Modules.Bookings.API.*Endpoint` (após `+MeAjudaAi.Modules.Payments.API.*Endpoint`). Isso é necessário apenas para módulos com cobertura E2E. Módulos sem E2E mantêm os endpoints excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi Shared coverage: Classes de infraestrutura de messaging que dependem de broker RabbitMQ real (RabbitMqDeadLetterService, DeadLetterExtensions, FailedMessageInfo, FailureAttempt, EnvironmentMetadata) são candidatas a [ExcludeFromCodeCoverage] pois não são testáveis unitariamente. Utilitários puros (PhoneNumberValidator, PiiMaskingHelper, SlugHelper, UuidGenerator) devem ter testes unitários adicionados em vez de serem excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi Bookings.API: Classes não-endpoint dentro do assembly da API (Extensions, BookingsEndpoints, ProviderAuthorizationResult, ProviderAuthorizationResultExtensions) não são capturadas pelo classfilter `+MeAjudaAi.Modules.Bookings.API.*Endpoint` e devem receber [ExcludeFromCodeCoverage] individualmente (são glue/wiring). Já `ProviderAuthorizationResolver` contém lógica real de autorização e deve ter testes unitários próprios.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi coverlet.runsettings: `ExcludeByAttribute` deve incluir `ExcludeFromCodeCoverage` (além de Obsolete,GeneratedCode,CompilerGenerated). Sem isso, todas as classes decoradas com [ExcludeFromCodeCoverage] continuam sendo instrumentadas pelo coverlet e contam como linhas não cobertas no relatório, reduzindo a cobertura global. Correção: `<ExcludeByAttribute>Obsolete,GeneratedCode,CompilerGenerated,ExcludeFromCodeCoverage</ExcludeByAttribute>`.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-19T15:37:29.889Z
Learning: MeAjudaAi: objetivo de cobertura global é ≥90% (linhas), mantendo thresholds 90/80 no CI; quando a meta estiver ameaçada, preferir excluir “glue/DTO” (Endpoints/Extensions/Options/IntegrationEvent/DbContextFactory) e adicionar testes de alto impacto, em vez de reduzir o threshold.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-01-16T21:50:23.593Z
Learning: For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups; prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Providers/Tests/Unit/Application/Queries/GetProvidersByIdsQueryTests.cs:65-77
Timestamp: 2025-12-18T00:35:33.829Z
Learning: MeAjudaAi repo: Exceção à política de idioma para comentários — em testes, os cabeçalhos AAA (Arrange, Act, Assert) devem permanecer em inglês e não devem ser traduzidos. Comentários/XML continuam em português; logs em inglês; mensagens ao usuário (validações/APIs) em português.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:26:37.409Z
Learning: MeAjudaAi logging policy: logs must be in English, comments may remain in Portuguese. Portuguese logs were additionally found in Users (API/Extensions.cs:83), Providers (API/Extensions.cs:74), ServiceCatalogs (API/Extensions.cs:78,86,93), and Locations (multiple clients/services lines listed). SearchProviders module currently has no Portuguese logs.

… bus, monitoring, and real-time event infrastructure.
@frigini
Copy link
Copy Markdown
Owner Author

frigini commented Apr 29, 2026

@coderabbitai Então descartados cursor pagination e rpc no momento por adicionar complexidade e não serem necessários agora. Uma última atividade que tenho anotada no momento: revisar bru files e postman json. Todos os bru files estão criados e corretos? O poastman json está atualizado (e toda estrutura para isso está correta?) ?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== All .bru files ==="
fd -e bru | sort

echo ""
echo "=== All Postman JSON files ==="
fd -e json | grep -iE '(postman|collection)' | sort
fd -e json | grep -v '/obj/' | grep -v '/bin/' | grep -v 'node_modules' | grep -v 'package' | grep -v 'tsconfig' | grep -v 'jest' | grep -v 'vitest' | grep -v 'eslint' | grep -iE '(postman|collection|bruno)' | sort

echo ""
echo "=== Bruno environment/collection files ==="
fd | grep -iE '(bruno|\.bru)' | grep -v '/obj/' | grep -v '/bin/' | sort | head -60

echo ""
echo "=== List .bru contents ==="
fd -e bru | sort | xargs -I{} sh -c 'echo ""; echo "=== {} ==="; cat -n "{}"'

echo ""
echo "=== Postman JSON content ==="
fd -e json | grep -v '/obj/' | grep -v '/bin/' | grep -v 'node_modules' | grep -v 'package.json' | grep -v 'tsconfig' | grep -v 'jest' | grep -v 'vitest' | grep -v 'eslint' | grep -iE '(postman|collection)' | xargs -I{} sh -c 'echo ""; echo "=== {} ==="; cat -n "{}"' 2>/dev/null

echo ""
echo "=== Any bruno.json or collection.json ==="
fd -e json | grep -v '/obj/' | grep -v '/bin/' | grep -v 'node_modules' | grep -iE '(bruno\.json|collection\.json|environment\.json)' | xargs -I{} sh -c 'echo "=== {} ==="; cat -n "{}"' 2>/dev/null

Length of output: 123837


@frigini Seguem os achados e o plano objetivo.

Estado atual

  • Bruno (.bru): Estrutura está presente e bem coberta para os módulos. Identifiquei 3 lacunas/ajustes:

    1. Faltam requests para os novos endpoints SSE:
      • GET /api/v1/bookings/{id}/events
      • GET /api/v1/providers/{id}/verification-events
    2. Inconsistências de path/vars:
      • Locations/CreateAllowedCity.bru usa /api/v1/admin/allowed-cities, enquanto os demais AllowedCities usam /api/v1/locations/admin/allowed-cities. Sugerido: corrigir CreateAllowedCity.bru para incluir /locations.
      • Bookings/CancelBooking.bru e Bookings/ConfirmBooking.bru usam GUID 00000000-... fixo na URL; os docs citam variável bookingId. Sugerido: trocar para {{bookingId}} (e, se quiser, adicionar vars:pre-request com um placeholder).
    3. Variáveis duplicadas:
      • Shared/API.Collections/Common/EnvironmentVariables.bru e GlobalVariables.bru definem as mesmas chaves (baseUrl, keycloakUrl, realm, clientId, etc.). Recomendo centralizar defaults em GlobalVariables.bru e reservar EnvironmentVariables.bru apenas para valores via process.env, documentando a precedência (Environment → Global).
  • Postman (JSON): Não há nenhuma collection JSON no repositório. Apenas tools/api-collections/package.json foi encontrado. Portanto, o “Postman json” não está versionado/atualizado.

Correções sugeridas (Bruno)

  1. Adicionar SSE requests
  • Bookings/StreamBookingEvents.bru
    • GET {{baseUrl}}/api/{{apiVersion}}/bookings/{{bookingId}}/events
    • headers:
      • Accept: text/event-stream
      • Cache-Control: no-cache
    • auth: bearer (token: {{accessToken}})
    • docs: instruções para encerrar manualmente (stream não finaliza).
  • Providers/StreamProviderVerificationEvents.bru
    • GET {{baseUrl}}/api/{{apiVersion}}/providers/{{providerId}}/verification-events
    • headers e auth iguais ao acima.
  1. Corrigir paths/variáveis
  • Locations/CreateAllowedCity.bru: alterar url para {{baseUrl}}/api/{{apiVersion}}/locations/admin/allowed-cities.
  • Bookings/CancelBooking.bru e Bookings/ConfirmBooking.bru: trocar o GUID fixo por {{bookingId}}.
  • Opcional e recomendado: padronizar todos os URLs para usar /api/{{apiVersion}}/… (já existe apiVersion: v1 nas vars).
  1. Collections por módulo
  • Promover src/Modules/Providers/API/API.Client/collection.bru.example para collection.bru com:
    • vars (providerId, testType, testVerificationStatus)
    • include dos Common/StandardHeaders.bru (comentário de uso) e orientação sobre SetupGetKeycloakToken.bru
  • Replicar um collection.bru mínimo para Bookings, Users, ServiceCatalogs, etc., com as vars mais usadas (bookingId, providerId, categoryId, serviceId…).

Propostas de snippets (.bru)

Bookings/StreamBookingEvents.bru
meta {
name: Stream Booking Events (SSE)
type: http
seq: 11
}

get {
url: {{baseUrl}}/api/{{apiVersion}}/bookings/{{bookingId}}/events
auth: bearer
}

auth:bearer {
token: {{accessToken}}
}

headers {
Accept: text/event-stream
Cache-Control: no-cache
}

docs {

Stream Booking Events (SSE)

Mantém uma conexão aberta e recebe eventos de status do agendamento.

  • Requer Authorization (Bearer).
  • Para encerrar o stream, interrompa a execução manualmente.
    }

Providers/StreamProviderVerificationEvents.bru
meta {
name: Stream Provider Verification Events (SSE)
type: http
seq: 16
}

get {
url: {{baseUrl}}/api/{{apiVersion}}/providers/{{providerId}}/verification-events
auth: bearer
}

auth:bearer {
token: {{accessToken}}
}

headers {
Accept: text/event-stream
Cache-Control: no-cache
}

docs {

Stream Provider Verification Events (SSE)

Recebe atualizações de verificação do prestador em tempo real.
}

Ajuste CreateAllowedCity.bru (linha 8)

  • De:
    url: {{baseUrl}}/api/v1/admin/allowed-cities
  • Para:
    url: {{baseUrl}}/api/{{apiVersion}}/locations/admin/allowed-cities

Ajustes Cancel/Confirm Booking (linhas 8/9)

  • De:
    /bookings/00000000-0000-0000-0000-000000000000/confirm (ou cancel)
  • Para:
    /bookings/{{bookingId}}/confirm (ou cancel)

Postman
Opções possíveis:

  • Decidir pelo “Bruno como fonte de verdade” e remover a referência a Postman, mantendo apenas .bru versionados.
  • Ou adicionar geração de coleção Postman como artefato:
    • Se houver OpenAPI no projeto (não encontrei), podemos gerar Postman a partir do OpenAPI (ex.: via openapi-to-postman).
    • Alternativa: manter uma collection Postman manual em tools/api-collections/.postman_collection.json e atualizar nos releases. Hoje, esse arquivo não existe no repo.

Me diga se prefere:

  • Eu crio os dois .bru de SSE, corrijo os três pontos citados e preparo collection.bru para Providers/Bookings; ou
  • Configuramos a geração (ou inclusão) de uma collection Postman estática em tools/api-collections.

🧠 Learnings used
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-20T22:04:25.820Z
Learning: MeAjudaAi (PRs futuros): para cobertura global, NÃO excluir *Configuration e *Extensions (há smoke tests exercitando-os); também não excluir *.Monitoring.*, MeAjudaAi.Shared.Jobs.* e MeAjudaAi.Shared.Mediator.*. Manter excluídos apenas Request/Response/Dto/DTO/IntegrationEvent e *DbContextFactory; Endpoints podem ficar excluídos globalmente, a menos que sejam white-listados por módulo com E2E (ex.: +MeAjudaAi.Modules.Payments.API.*Endpoint; -*Endpoint).

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi ci-backend.yml (linha 272): Para tornar os endpoints de um módulo visíveis na cobertura do ReportGenerator, adicionar o assembly antes de `-*Endpoint` no classfilters, ex: `+MeAjudaAi.Modules.Bookings.API.*Endpoint` (após `+MeAjudaAi.Modules.Payments.API.*Endpoint`). Isso é necessário apenas para módulos com cobertura E2E. Módulos sem E2E mantêm os endpoints excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T20:02:36.451Z
Learning: MeAjudaAi Shared coverage: Classes de infraestrutura de messaging que dependem de broker RabbitMQ real (RabbitMqDeadLetterService, DeadLetterExtensions, FailedMessageInfo, FailureAttempt, EnvironmentMetadata) são candidatas a [ExcludeFromCodeCoverage] pois não são testáveis unitariamente. Utilitários puros (PhoneNumberValidator, PiiMaskingHelper, SlugHelper, UuidGenerator) devem ter testes unitários adicionados em vez de serem excluídos.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi Bookings.API: Classes não-endpoint dentro do assembly da API (Extensions, BookingsEndpoints, ProviderAuthorizationResult, ProviderAuthorizationResultExtensions) não são capturadas pelo classfilter `+MeAjudaAi.Modules.Bookings.API.*Endpoint` e devem receber [ExcludeFromCodeCoverage] individualmente (são glue/wiring). Já `ProviderAuthorizationResolver` contém lógica real de autorização e deve ter testes unitários próprios.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-24T21:04:28.437Z
Learning: MeAjudaAi coverlet.runsettings: `ExcludeByAttribute` deve incluir `ExcludeFromCodeCoverage` (além de Obsolete,GeneratedCode,CompilerGenerated). Sem isso, todas as classes decoradas com [ExcludeFromCodeCoverage] continuam sendo instrumentadas pelo coverlet e contam como linhas não cobertas no relatório, reduzindo a cobertura global. Correção: `<ExcludeByAttribute>Obsolete,GeneratedCode,CompilerGenerated,ExcludeFromCodeCoverage</ExcludeByAttribute>`.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-04-19T15:37:29.889Z
Learning: MeAjudaAi: objetivo de cobertura global é ≥90% (linhas), mantendo thresholds 90/80 no CI; quando a meta estiver ameaçada, preferir excluir “glue/DTO” (Endpoints/Extensions/Options/IntegrationEvent/DbContextFactory) e adicionar testes de alto impacto, em vez de reduzir o threshold.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 0
File: :0-0
Timestamp: 2026-01-16T21:50:23.593Z
Learning: For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups; prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Providers/Tests/Unit/Application/Queries/GetProvidersByIdsQueryTests.cs:65-77
Timestamp: 2025-12-18T00:35:33.829Z
Learning: MeAjudaAi repo: Exceção à política de idioma para comentários — em testes, os cabeçalhos AAA (Arrange, Act, Assert) devem permanecer em inglês e não devem ser traduzidos. Comentários/XML continuam em português; logs em inglês; mensagens ao usuário (validações/APIs) em português.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs:133-143
Timestamp: 2025-12-17T18:26:37.409Z
Learning: MeAjudaAi logging policy: logs must be in English, comments may remain in Portuguese. Portuguese logs were additionally found in Users (API/Extensions.cs:83), Providers (API/Extensions.cs:74), ServiceCatalogs (API/Extensions.cs:78,86,93), and Locations (multiple clients/services lines listed). SearchProviders module currently has no Portuguese logs.

Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 77
File: src/Modules/Providers/Tests/Unit/Application/Queries/GetProvidersByIdsQueryTests.cs:48-63
Timestamp: 2025-12-18T00:36:23.299Z
Learning: Nos testes do repositório MeAjudaAi, os rótulos do padrão AAA (Arrange/Act/Assert) devem permanecer em inglês e não devem ser traduzidos para português, mesmo que comentários e XML docs sejam em português.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs (1)

21-41: Centralize as mensagens de evento SSE em constantes compartilhadas

As mensagens estão corretas, mas hoje ficam espalhadas no handler. Vale extrair para constantes compartilhadas (ex.: Contracts/Application constants) para evitar drift entre backend/web e facilitar evolução.

Based on learnings: prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs`
around lines 21 - 41, Extract all SSE message literals used in
BookingRealtimeEventsHandler's HandleAsync methods (e.g., the messages passed to
PublishUpdate for
BookingCreated/BookingConfirmed/BookingCancelled/BookingRejected/BookingCompleted)
into shared constants in the Shared.Contracts (or Contracts/Application
constants) assembly, add descriptive constant names (e.g.,
BookingMessage_Created, BookingMessage_Confirmed, etc.), update the
PublishUpdate invocations in BookingRealtimeEventsHandler to use these constants
instead of raw strings, and ensure the handler imports the new shared constants
so backend and web can reuse the same values.
tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs (1)

205-211: Cobertura incompleta para X-User-Location malformado no teste de integração.

Os casos atuais cobrem city/state vazios, mas faltam formatos também tratados como malformados pelo middleware (sem | e com separador extra). Recomendo incluir ambos para reduzir risco de regressão.

Diff sugerido
     [InlineData("Muriaé|")] // City without state
     [InlineData("|MG")] // State without city
+    [InlineData("Muriaé")] // Sem separador
+    [InlineData("Muriaé|MG|Extra")] // Separador extra
     [InlineData("Muriaé| ")] // City with empty state (spaces)
     [InlineData(" |MG")] // Empty city (spaces) with state
     [InlineData("|")] // Both empty
     [InlineData("  |  ")] // Both empty with spaces

Based on learnings: MeAjudaAi: objetivo de cobertura global é ≥90% (linhas), mantendo thresholds 90/80 no CI; quando a meta estiver ameaçada, preferir excluir “glue/DTO” e adicionar testes de alto impacto.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs`
around lines 205 - 211, The test
GetProviders_WithMalformedLocationHeader_ShouldBeRejected is missing malformed
X-User-Location formats (no '|' separator and extra separators); update the
InlineData for that test in GeographicRestrictionIntegrationTests.cs (method
GetProviders_WithMalformedLocationHeader_ShouldBeRejected) to include examples
such as "Muriaé" (no separator), "MG" (no separator), "Muriaé||MG" (empty
between separators), "Muriaé|MG|Extra" (too many separators) and an empty string
"" so the middleware's parsing failure modes are covered and the assertions for
rejection remain the same.
src/Shared/Monitoring/BusinessMetricsMiddleware.cs (1)

56-57: Considere compilar os padrões Regex para evitar alocações no hot path.

O PR visa reduzir alocações em middlewares (P0), mas Regex.IsMatch(pathValue, pattern) recompila o padrão a cada requisição. Dado que este middleware executa em toda request, vale extrair padrões estáticos compilados como já feito com IdPattern na linha 18.

♻️ Proposta de refatoração com padrões compilados
 public class BusinessMetricsMiddleware(
     RequestDelegate next,
     BusinessMetrics businessMetrics,
     ILogger<BusinessMetricsMiddleware> logger)
 {
     private static readonly Regex IdPattern = new(@"/\d+", RegexOptions.Compiled);
+    private static readonly Regex VersionedUsersPattern = new(@"^/api/v\d+/users$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+    private static readonly Regex VersionedLoginPattern = new(@"^/api/v\d+/auth/login$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+    private static readonly Regex VersionedHelpRequestsPattern = new(@"^/api/v\d+/help-requests$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+    private static readonly Regex VersionedCompleteHelpPattern = new(@"^/api/v\d+/help-requests/[^/]+/complete$", RegexOptions.Compiled | RegexOptions.IgnoreCase);

     // ...

     private void LogBusinessEvents(HttpContext context, TimeSpan elapsed)
     {
         // ...
         
-            var isUserPath = pathSpan.Equals("/api/users", StringComparison.OrdinalIgnoreCase) || 
-                             Regex.IsMatch(pathValue, @"^/api/v\d+/users$", RegexOptions.IgnoreCase);
+            var isUserPath = pathSpan.Equals("/api/users", StringComparison.OrdinalIgnoreCase) || 
+                             VersionedUsersPattern.IsMatch(pathValue);

-            var isLoginPath = pathSpan.Equals("/api/auth/login", StringComparison.OrdinalIgnoreCase) || 
-                              (pathSpan.StartsWith("/api/v", StringComparison.OrdinalIgnoreCase) && 
-                               Regex.IsMatch(pathValue, @"^/api/v\d+/auth/login$", RegexOptions.IgnoreCase));
+            var isLoginPath = pathSpan.Equals("/api/auth/login", StringComparison.OrdinalIgnoreCase) || 
+                              (pathSpan.StartsWith("/api/v", StringComparison.OrdinalIgnoreCase) && 
+                               VersionedLoginPattern.IsMatch(pathValue));

-            if ((pathSpan.Equals("/api/help-requests", StringComparison.OrdinalIgnoreCase) || 
-                 Regex.IsMatch(pathValue, @"^/api/v\d+/help-requests$", RegexOptions.IgnoreCase)) && 
+            if ((pathSpan.Equals("/api/help-requests", StringComparison.OrdinalIgnoreCase) || 
+                 VersionedHelpRequestsPattern.IsMatch(pathValue)) && 

-            var isCompleteHelpPath = Regex.IsMatch(pathValue, @"^/api/v\d+/help-requests/[^/]+/complete$", RegexOptions.IgnoreCase);
+            var isCompleteHelpPath = VersionedCompleteHelpPattern.IsMatch(pathValue);
     }
 }

Also applies to: 66-68, 77-78, 87-87

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Shared/Monitoring/BusinessMetricsMiddleware.cs` around lines 56 - 57,
Extraia os padrões Regex usados no hot path para campos estáticos pré-compilados
na classe BusinessMetricsMiddleware (seguindo o exemplo de IdPattern) e
substitua as chamadas inline Regex.IsMatch(...) por essas instâncias
pré-compiladas; por exemplo, crie static readonly Regex UsersPattern = new
Regex(@"^/api/v\d+/users$", RegexOptions.Compiled | RegexOptions.IgnoreCase) e
use UsersPattern.IsMatch(pathValue) onde atualmente é usado isUserPath, e faça o
mesmo para os outros padrões referenciados nas ocorrências atuais (linhas
aproximadas onde arestas: 66-68, 77-78, 87) para evitar recompilação por
requisição.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/api-automation.md`:
- Around line 145-158: The section titled "Opção 2: Node.js apenas (spec +
collections)" is inconsistent because its content shows .NET commands (`dotnet
run`) and the original Node.js steps (`npm install`, `node
generate-postman-collections.js`) were removed; either update the section title
to reflect the current .NET/OpenAPI workflow or remove the section entirely and
consolidate into the main option; locate the header "Opção 2: Node.js apenas
(spec + collections)" in docs/api-automation.md and either rename it to
accurately describe the .NET/OpenAPI process or delete the block so only the
correct workflow (as described elsewhere in the docs) remains.

In
`@src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru`:
- Line 8: The request URL in CreateAllowedCity.bru contains an extra "locations"
segment ("{{baseUrl}}/api/{{apiVersion}}/locations/admin/allowed-cities") which
disagrees with the module endpoints; update the route to
"{{baseUrl}}/api/{{apiVersion}}/admin/allowed-cities" wherever it appears
(including the other occurrence noted at lines ~61) so the client matches
LocationsModuleEndpoints; search for the URL template in CreateAllowedCity.bru
and replace the path to "admin/allowed-cities".

In `@src/Shared/Monitoring/BusinessMetricsMiddleware.cs`:
- Around line 86-89: The isCompleteHelpPath check currently only matches
versioned routes; add the non-versioned fallback (similar to
isUserPath/isLoginPath) by checking
pathSpan.Equals("/api/help-requests/{id}/complete",
StringComparison.OrdinalIgnoreCase) OR the existing Regex.IsMatch on pathValue
for ^/api/v\d+/help-requests/[^/]+/complete$ so both
/api/help-requests/{id}/complete and /api/v{n}/help-requests/{id}/complete are
accepted; update the isCompleteHelpPath variable accordingly and keep the
existing POST + successful statusCode branch untouched.

In `@src/Web/e2e/support/mocks.ts`:
- Around line 163-184: The mock response in src/Web/e2e/support/mocks.ts
currently wraps the provider payload in an extra "data" causing a data.data
shape and placing success/error inside that nested object; fix it by flattening
the envelope so the mock returns an object with top-level keys { data: { id,
name, ... }, success: true, error: null } (i.e., remove the outer "data"
wrapper), updating the mocked provider response object where the nested "data"
property is defined to eliminate the double nesting and keep "success" and
"error" as siblings of "data".

---

Nitpick comments:
In `@src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs`:
- Around line 21-41: Extract all SSE message literals used in
BookingRealtimeEventsHandler's HandleAsync methods (e.g., the messages passed to
PublishUpdate for
BookingCreated/BookingConfirmed/BookingCancelled/BookingRejected/BookingCompleted)
into shared constants in the Shared.Contracts (or Contracts/Application
constants) assembly, add descriptive constant names (e.g.,
BookingMessage_Created, BookingMessage_Confirmed, etc.), update the
PublishUpdate invocations in BookingRealtimeEventsHandler to use these constants
instead of raw strings, and ensure the handler imports the new shared constants
so backend and web can reuse the same values.

In `@src/Shared/Monitoring/BusinessMetricsMiddleware.cs`:
- Around line 56-57: Extraia os padrões Regex usados no hot path para campos
estáticos pré-compilados na classe BusinessMetricsMiddleware (seguindo o exemplo
de IdPattern) e substitua as chamadas inline Regex.IsMatch(...) por essas
instâncias pré-compiladas; por exemplo, crie static readonly Regex UsersPattern
= new Regex(@"^/api/v\d+/users$", RegexOptions.Compiled |
RegexOptions.IgnoreCase) e use UsersPattern.IsMatch(pathValue) onde atualmente é
usado isUserPath, e faça o mesmo para os outros padrões referenciados nas
ocorrências atuais (linhas aproximadas onde arestas: 66-68, 77-78, 87) para
evitar recompilação por requisição.

In
`@tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs`:
- Around line 205-211: The test
GetProviders_WithMalformedLocationHeader_ShouldBeRejected is missing malformed
X-User-Location formats (no '|' separator and extra separators); update the
InlineData for that test in GeographicRestrictionIntegrationTests.cs (method
GetProviders_WithMalformedLocationHeader_ShouldBeRejected) to include examples
such as "Muriaé" (no separator), "MG" (no separator), "Muriaé||MG" (empty
between separators), "Muriaé|MG|Extra" (too many separators) and an empty string
"" so the middleware's parsing failure modes are covered and the assertions for
rejection remain the same.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0649313e-12c6-49d3-a6d1-45e5b0864601

📥 Commits

Reviewing files that changed from the base of the PR and between 5ddbd7f and 61e5b78.

⛔ Files ignored due to path filters (1)
  • tools/api-collections/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
📒 Files selected for processing (20)
  • docs/api-automation.md
  • docs/architecture.md
  • src/Modules/Bookings/API/API.Client/Bookings/CancelBooking.bru
  • src/Modules/Bookings/API/API.Client/Bookings/ConfirmBooking.bru
  • src/Modules/Bookings/API/API.Client/Bookings/StreamBookingEvents.bru
  • src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs
  • src/Modules/Bookings/Tests/Unit/Application/Events/BookingRealtimeEventsHandlerTests.cs
  • src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru
  • src/Modules/Providers/API/API.Client/ProviderAdmin/StreamProviderVerificationEvents.bru
  • src/Modules/Providers/Tests/Unit/Application/Events/ProviderVerificationStatusUpdatedSseHandlerTests.cs
  • src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs
  • src/Shared/Messaging/Options/MessageBusOptions.cs
  • src/Shared/Monitoring/BusinessMetricsMiddleware.cs
  • src/Web/e2e/support/mocks.ts
  • tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs
  • tests/MeAjudaAi.Shared.Tests/Unit/Monitoring/BusinessMetricsMiddlewareTests.cs
  • tools/api-collections/README.md
  • tools/api-collections/generate-all-collections.bat
  • tools/api-collections/generate-postman-collections.js
  • tools/api-collections/package.json
💤 Files with no reviewable changes (4)
  • tools/api-collections/package.json
  • tools/api-collections/generate-all-collections.bat
  • tools/api-collections/generate-postman-collections.js
  • tools/api-collections/README.md
✅ Files skipped from review due to trivial changes (4)
  • src/Modules/Bookings/API/API.Client/Bookings/CancelBooking.bru
  • src/Modules/Bookings/API/API.Client/Bookings/StreamBookingEvents.bru
  • src/Modules/Providers/API/API.Client/ProviderAdmin/StreamProviderVerificationEvents.bru
  • src/Modules/Users/Application/Handlers/Commands/RegisterCustomerCommandHandler.cs
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/MeAjudaAi.Shared.Tests/Unit/Monitoring/BusinessMetricsMiddlewareTests.cs
  • src/Modules/Bookings/Tests/Unit/Application/Events/BookingRealtimeEventsHandlerTests.cs
  • src/Modules/Providers/Tests/Unit/Application/Events/ProviderVerificationStatusUpdatedSseHandlerTests.cs

Comment thread docs/api-automation.md Outdated
Comment thread src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru Outdated
Comment thread src/Shared/Monitoring/BusinessMetricsMiddleware.cs Outdated
Comment thread src/Web/e2e/support/mocks.ts Outdated
Filipe Frigini added 2 commits April 29, 2026 16:00
@frigini frigini force-pushed the feature/extra-implementations-minimize-tech-debts branch from 35fa6aa to 776c2d1 Compare April 29, 2026 19:23
Filipe Frigini added 2 commits April 29, 2026 16:44
- Add mock for port 7002 (default Provider API port)
- Add console debug logging to auth.ts
- Skip provider dashboard test due to React hooks ordering issue in useProviderVerificationEvents
- Replace empty string test case with special NOTSET marker
- HttpClient removes empty headers, so proper test is not possible
- Different test logic for missing header (fail-open) vs malformed header (reject)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/GetAllAllowedCities.bru (1)

8-15: ⚠️ Potential issue | 🟡 Minor

Evite duplicar o onlyActive na URL e no bloco de query params.

Hoje o request define onlyActive em Line 8 e novamente nas Lines 13-15. Deixe em um único lugar para evitar ambiguidade.

🔧 Ajuste sugerido
-get {
-  url: {{baseUrl}}/api/{{apiVersion}}/admin/allowed-cities?onlyActive=true
+get {
+  url: {{baseUrl}}/api/{{apiVersion}}/admin/allowed-cities
   body: none
   auth: bearer
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/GetAllAllowedCities.bru`
around lines 8 - 15, A requisição está duplicando o parâmetro onlyActive tanto
na linha do url quanto no bloco params:query; remova a ocorrência no url (linha
com "url: {{baseUrl}}/api/{{apiVersion}}/admin/allowed-cities?onlyActive=true")
e deixe apenas o parâmetro declarado em params:query { onlyActive: true } para
evitar ambiguidade e garantir que o valor seja controlado pelo bloco de query
params.
♻️ Duplicate comments (1)
src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru (1)

61-61: ⚠️ Potential issue | 🟡 Minor

Atualize o Location documentado para não ficar preso em v1.

Em Line 61, a documentação ainda mostra /api/v1/admin/allowed-cities/{id}, divergindo da rota versionada usada em Line 8.

🔧 Ajuste sugerido
-  Header `Location`: `/api/v1/admin/allowed-cities/{id}`
+  Header `Location`: `/api/{{apiVersion}}/admin/allowed-cities/{id}`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru`
at line 61, The documented Header `Location` currently hardcodes
`/api/v1/admin/allowed-cities/{id}`; update it to match the versioned route used
in the file's route declaration (the same version segment used in the route at
the top of the file) so the Location header is not stuck on `v1`—replace the
hardcoded path with the same versioned path/token used by the route declaration
to ensure consistency for the allowed-cities endpoint.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Web/e2e/provider/portal.spec.ts`:
- Around line 17-27: The test was disabled with test.skip which hides
regressions; replace test.skip with test.fixme (include the tracking issue ID in
the test title) if it truly must remain disabled, otherwise re-enable the test
and remove the fixed sleep (the waitForTimeout) by switching to locator-based
waits and assertions: after page.waitForLoadState(...) use page.locator(...)
(e.g., the element that would show "Provider Test") and call locator.waitFor or
Playwright expects (toHaveText/toBeVisible) to assert presence; also check for
hooks-related failures by mounting the component that uses
useProviderVerificationEvents/useSse/useSession in a test harness or mocking
those hooks so the E2E flow can run reliably.

In `@src/Web/e2e/support/auth.ts`:
- Around line 17-27: The current setup reads page.url() into testBaseUrl (in
setupPageForE2E) before navigation which yields "about:blank" and then injects
that into process.env.NEXT_PUBLIC_API_URL via page.addInitScript; change the
addInitScript callback to compute the API URL at runtime using
window.location.origin (and assign that to (window as
any).process.env.NEXT_PUBLIC_API_URL) instead of passing testBaseUrl, so the env
is set correctly after navigation and SSE/API requests use the real origin.

---

Outside diff comments:
In
`@src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/GetAllAllowedCities.bru`:
- Around line 8-15: A requisição está duplicando o parâmetro onlyActive tanto na
linha do url quanto no bloco params:query; remova a ocorrência no url (linha com
"url: {{baseUrl}}/api/{{apiVersion}}/admin/allowed-cities?onlyActive=true") e
deixe apenas o parâmetro declarado em params:query { onlyActive: true } para
evitar ambiguidade e garantir que o valor seja controlado pelo bloco de query
params.

---

Duplicate comments:
In
`@src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru`:
- Line 61: The documented Header `Location` currently hardcodes
`/api/v1/admin/allowed-cities/{id}`; update it to match the versioned route used
in the file's route declaration (the same version segment used in the route at
the top of the file) so the Location header is not stuck on `v1`—replace the
hardcoded path with the same versioned path/token used by the route declaration
to ensure consistency for the allowed-cities endpoint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a8aeb4ba-7cf5-4484-896a-42e1e4fd1898

📥 Commits

Reviewing files that changed from the base of the PR and between 61e5b78 and 2087e61.

📒 Files selected for processing (14)
  • docs/api-automation.md
  • docs/roadmap.md
  • src/Modules/Bookings/Application/Constants/BookingSseMessages.cs
  • src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs
  • src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru
  • src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/DeleteAllowedCity.bru
  • src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/GetAllAllowedCities.bru
  • src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/GetAllowedCityById.bru
  • src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/UpdateAllowedCity.bru
  • src/Shared/Monitoring/BusinessMetricsMiddleware.cs
  • src/Web/e2e/provider/portal.spec.ts
  • src/Web/e2e/support/auth.ts
  • src/Web/e2e/support/mocks.ts
  • tests/MeAjudaAi.Integration.Tests/Middleware/GeographicRestrictionIntegrationTests.cs
💤 Files with no reviewable changes (1)
  • docs/api-automation.md
✅ Files skipped from review due to trivial changes (1)
  • src/Modules/Bookings/Application/Events/BookingRealtimeEventsHandler.cs
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Web/e2e/support/mocks.ts
  • docs/roadmap.md
  • src/Shared/Monitoring/BusinessMetricsMiddleware.cs

Comment thread src/Web/e2e/provider/portal.spec.ts
Comment thread src/Web/e2e/support/auth.ts Outdated
- Replace test.skip with test.fixme with tracking note for provider dashboard
- Use window.location.origin in auth.ts for runtime API URL
- Remove duplicate onlyActive param in GetAllAllowedCities.bru URL
- Use {{apiVersion}} variable in CreateAllowedCity.bru Location header
@github-actions
Copy link
Copy Markdown

✅ Contrato da API Validado

Nenhuma breaking change detectada pelo oasdiff em relação à branch base.

@github-actions
Copy link
Copy Markdown

Code Coverage Report

Code Coverage

Project Package Line Rate Branch Rate Health
MeAjudaAi.Web.Admin components/layout 73% 64%
MeAjudaAi.Web.Admin components/providers 93% 92%
MeAjudaAi.Web.Admin components/ui 100% 86%
MeAjudaAi.Web.Admin hooks/admin 95% 79%
MeAjudaAi.Web.Admin lib 100% 100%
MeAjudaAi.Web.Admin Summary 94% (256 / 272) 80% (118 / 147) -
MeAjudaAi.Web.Customer components/auth 62% 57%
MeAjudaAi.Web.Customer components/bookings 72% 61%
MeAjudaAi.Web.Customer components/home 100% 100%
MeAjudaAi.Web.Customer components/layout 63% 36%
MeAjudaAi.Web.Customer components/profile 93% 94%
MeAjudaAi.Web.Customer components/providers 62% 63%
MeAjudaAi.Web.Customer components/reviews 64% 70%
MeAjudaAi.Web.Customer components/search 88% 70%
MeAjudaAi.Web.Customer components/service 100% 100%
MeAjudaAi.Web.Customer components/ui 97% 97%
MeAjudaAi.Web.Customer hooks 70% 52%
MeAjudaAi.Web.Customer lib 100% 100%
MeAjudaAi.Web.Customer lib/api 83% 80%
MeAjudaAi.Web.Customer lib/constants 100% 100%
MeAjudaAi.Web.Customer lib/schemas 92% 80%
MeAjudaAi.Web.Customer lib/services 94% 86%
MeAjudaAi.Web.Customer lib/utils 77% 90%
MeAjudaAi.Web.Customer Summary 77% (731 / 945) 71% (631 / 878) -
MeAjudaAi.Web.Provider root 53% 36%
MeAjudaAi.Web.Provider components/dashboard 73% 52%
MeAjudaAi.Web.Provider components/layout 100% 100%
MeAjudaAi.Web.Provider components/profile 100% 100%
MeAjudaAi.Web.Provider components/providers 100% 100%
MeAjudaAi.Web.Provider components/ui 100% 90%
MeAjudaAi.Web.Provider hooks 0% 0%
MeAjudaAi.Web.Provider lib 100% 100%
MeAjudaAi.Web.Provider lib/api 100% 93%
MeAjudaAi.Web.Provider lib/constants 100% 100%
MeAjudaAi.Web.Provider lib/schemas 87% 75%
MeAjudaAi.Web.Provider lib/services 100% 100%
MeAjudaAi.Web.Provider lib/utils 96% 98%
MeAjudaAi.Web.Provider Summary 75% (314 / 416) 73% (366 / 499) -
Overall Summary 79% (1301 / 1633) 73% (1115 / 1524) -

@github-actions
Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Complexity Health
MeAjudaAi.ApiService 96% 80% 393
MeAjudaAi.Modules.Bookings.Application 91% 83% 217
MeAjudaAi.Modules.Bookings.Domain 97% 98% 68
MeAjudaAi.Modules.Bookings.Infrastructure 96% 39% 37
MeAjudaAi.Modules.Communications.API 100% 100% 4
MeAjudaAi.Modules.Communications.Application 89% 69% 206
MeAjudaAi.Modules.Communications.Domain 100% 100% 17
MeAjudaAi.Modules.Communications.Infrastructure 92% 57% 59
MeAjudaAi.Modules.Documents.API 100% 100% 3
MeAjudaAi.Modules.Documents.Application 86% 72% 191
MeAjudaAi.Modules.Documents.Domain 99% 100% 29
MeAjudaAi.Modules.Documents.Infrastructure 88% 76% 164
MeAjudaAi.Modules.Locations.API 100% 100% 11
MeAjudaAi.Modules.Locations.Application 94% 95% 70
MeAjudaAi.Modules.Locations.Domain 95% 82% 127
MeAjudaAi.Modules.Locations.Infrastructure 92% 75% 225
MeAjudaAi.Modules.Payments.API 100% 100% 3
MeAjudaAi.Modules.Payments.Application 97% 92% 55
MeAjudaAi.Modules.Payments.Domain 95% 86% 105
MeAjudaAi.Modules.Payments.Infrastructure 90% 71% 169
MeAjudaAi.Modules.Providers.API 100% 100% 17
MeAjudaAi.Modules.Providers.Application 92% 82% 277
MeAjudaAi.Modules.Providers.Domain 94% 87% 285
MeAjudaAi.Modules.Providers.Infrastructure 94% 75% 88
MeAjudaAi.Modules.Ratings.Application 91% 100% 14
MeAjudaAi.Modules.Ratings.Domain 98% 100% 24
MeAjudaAi.Modules.Ratings.Infrastructure 90% 75% 23
MeAjudaAi.Modules.SearchProviders.API 100% 100% 2
MeAjudaAi.Modules.SearchProviders.Application 94% 73% 36
MeAjudaAi.Modules.SearchProviders.Domain 99% 82% 48
MeAjudaAi.Modules.SearchProviders.Infrastructure 92% 89% 59
MeAjudaAi.Modules.ServiceCatalogs.API 100% 100% 3
MeAjudaAi.Modules.ServiceCatalogs.Application 98% 97% 170
MeAjudaAi.Modules.ServiceCatalogs.Domain 98% 93% 60
MeAjudaAi.Modules.ServiceCatalogs.Infrastructure 100% 78% 56
MeAjudaAi.Modules.Users.API 98% 83% 13
MeAjudaAi.Modules.Users.Application 94% 91% 203
MeAjudaAi.Modules.Users.Domain 95% 95% 139
MeAjudaAi.Modules.Users.Infrastructure 82% 59% 196
MeAjudaAi.Shared 80% 78% 1383
Summary 90% (14051 / 15595) 80% (3429 / 4282) 5249

Minimum allowed line rate is 90%

@frigini frigini merged commit 59ce93a into master Apr 29, 2026
10 checks passed
@frigini frigini deleted the feature/extra-implementations-minimize-tech-debts branch April 29, 2026 20:47
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.

1 participant