Conversation
- ✅ Marcar Sprint 3 Parte 1 (GitHub Pages) como concluída (11 Dez) - 🔄 Iniciar Sprint 3 Parte 2 (Admin Endpoints & Tools) - 📅 Atualizar cronograma: Parte 1 (10-11 Dez) → Parte 2 (11-24 Dez) - 🎯 Próximo objetivo: Admin endpoint para gerenciar cidades permitidas Branch: sprint3-parte2-admin-endpoints criada
- ✅ Criar DTOs (AllowedCityDto) e requests
- ✅ Criar Commands (Create, Update, Delete)
- ✅ Criar Queries (GetAll, GetById)
- ✅ Implementar todos os handlers CQRS
- ✅ Criar 5 endpoints admin-only:
- POST /api/v1/admin/allowed-cities
- GET /api/v1/admin/allowed-cities
- GET /api/v1/admin/allowed-cities/{id}
- PUT /api/v1/admin/allowed-cities/{id}
- DELETE /api/v1/admin/allowed-cities/{id}
- ✅ Registrar endpoints em Extensions.cs
- ✅ Atualizar dotnet-ef tools para 10.0.1
Sprint 3 Parte 2 - Admin Endpoints (Task 4-6/12 completed)
Próximo: Criar testes (unitários, integração, E2E)
…eAjudaAi.Shared - ✅ Refatorar Commands (Create, Update, Delete) para herdar de Command/Command<T> - ✅ Refatorar Queries (GetAll, GetById) para herdar de Query<T> - ✅ Refatorar Handlers para usar ICommandHandler e IQueryHandler do Shared - ✅ Criar testes unitários para AllowedCity entity (18 testes) - ✅ Criar testes unitários para todos os 5 handlers (mocks) -⚠️ PENDENTE: Corrigir endpoints para usar ICommandDispatcher.SendAsync e IQueryDispatcher.QueryAsync -⚠️ PENDENTE: Testes ainda não executados (endpoints precisam ser corrigidos primeiro) NOTA: Removida dependência do MediatR, usando implementação própria conforme padrão do projeto
…wedCity - 55 testes unitários (18 entidade + 22 handlers + 4 queries + 4 gets) - 18 testes de integração para AllowedCityRepository com TestContainers - Corrigido AllowedCity: IbgeCode de string para int?, trim antes de validação - Corrigido UpdateAllowedCityHandler: adicionar chamada UpdateAsync - Corrigido AllowedCityRepository: alterado de internal para public - Adicionado suporte para TestContainers.PostgreSql, Respawn, Bogus e AutoFixture no csproj - Criado GlobalTestConfiguration.cs para collection fixture - Testes de integração cobrem: CRUD, ordenação, case-insensitive, constraints únicos
- Criar 15 testes E2E abrangendo todos os 5 endpoints (CRUD completo) - Configurar LocationsDbContext no TestContainerFixture e TestContainerTestBase - Suprimir warning de PendingModelChangesWarning em ambiente de testes - Testar criação, leitura, atualização e exclusão com validações - Testar filtros (onlyActive), ordenação e fluxo completo - Testar autenticação admin e cenários de erro (404, 400, 403) - Testes compilam mas apresentam erro 500 (necessita debugging)
…ros de endpoint - Adicionar registro automático de Command e Query Handlers via reflection em Extensions.cs - Escanear assembly Application para ICommandHandler<> e IQueryHandler<,> - Registrar todos os handlers como Scoped services - Corrigir GetAllAllowedCitiesEndpoint: adicionar valores padrões nos parâmetros (onlyActive=false) - Resolver erro 500 nos testes E2E (handlers não estavam disponíveis no DI) - Resolver erro 400 nos testes E2E (parâmetro obrigatório sem valor padrão)
… lista hardcoded - Remover parâmetro allowedCities de IIbgeService.ValidateCityInAllowedRegionsAsync - Injetar IAllowedCityRepository no IbgeService - Usar repository.IsCityAllowedAsync para validar cidades permitidas - Atualizar GeographicValidationService para ignorar parâmetro allowedCities (agora usa banco) - Atualizar testes unitários do IbgeService para incluir mock do repositório - Sistema agora database-driven: cidades permitidas vêm do banco (tabela AllowedCities) BREAKING CHANGE: Interface IIbgeService não recebe mais allowedCities como parâmetro
… corretos - Criar hierarquia de exceções de domínio (NotFoundException, BadRequestException) - Criar exceções específicas (AllowedCityNotFoundException, DuplicateAllowedCityException) - Atualizar handlers para lançar exceções de domínio em vez de InvalidOperationException - Adicionar LocationsExceptionHandler implementando IExceptionHandler - Registrar exception handler e ProblemDetails no pipeline DI - Adicionar UseExceptionHandler() no início do pipeline de middleware - Criar testes de integração rápidos para validação (21s vs 9min dos E2E) - Adicionar LocationsDbContext ao ApiTestBase para suporte de testes de integração Resolves: 4 testes E2E que falhavam com 500 agora devem retornar 404/400 corretos Performance: Testes de integração 25x mais rápidos que E2E
Corrigidos:
- S2139: Passar exceções para logger.LogError em 3 catch blocks (IbgeClient)
- S927: Renomear parâmetros cityName->city, stateSigla->state para match interface (IbgeClient.ValidateCityInStateAsync)
- S1066: Merge nested if statement validação de estado (IbgeService)
- CS8604: Add null-coalescing para ufSigla em IsCityAllowedAsync call (IbgeService)
- S6610: Use char overload para EndsWith('/') em vez de string (Extensions)
- S1006: Add default parameter value = default em 5 handlers (CancellationToken)
- S6667: Pass exception to logger em OperationCanceledException (LocationsModuleApi)
- S1481: Remove unused variable 'result' usando discard _ (LocationsModuleApi)
Resultado: 0 warnings no módulo Locations (antes: 15)
Adicionado detalhamento completo do trabalho realizado: - ✅ AllowedCities CRUD endpoints implementados - ✅ Database: LocationsDbContext + migrations - ✅ Domain: Entity + Repository pattern - ✅ Handlers: 5 handlers (Create, Update, Delete, GetById, GetAll) - ✅ Exception Handling: Domain exceptions + IExceptionHandler - ✅ Testes: 4 integration tests (21s) + 15 unit tests - ✅ Quality: 0 warnings (15 removidos) - ⏳ E2E validation pending Status Sprint 3 Parte 2: ~60% concluído (Admin endpoints core done)
Correções de compilação: - MetricsCollectorService: remover parâmetros extras de métodos - SerilogConfigurator: corrigir chamada ConfigureApplicationInsights - DeadLetterServices: remover parâmetro cancellationToken de NotifyAdministratorsAsync - IbgeClient: corrigir nomes de variáveis (stateSigla → state, cityName → city) - GeographicValidationServiceTests: ajustar mocks para nova assinatura ValidateCityInAllowedRegionsAsync Correções de Exception Handling: - Program.cs: registrar módulos ANTES de AddSharedServices * LocationsExceptionHandler precisa ser executado antes do GlobalExceptionHandler * Exception handlers são executados na ordem de registro - GlobalExceptionHandler: adicionar tratamento para ArgumentException * AllowedCity valida inputs no construtor lançando ArgumentException * Retorna 400 Bad Request com detalhes do parâmetro inválido Testes: - Integration: 4/4 passing - E2E AllowedCities: 15/15 passing ✅ Infraestrutura: - Bruno Collections: 6 arquivos criados para AllowedCities Admin API - dotnet format executado: 71 arquivos formatados
- Health Checks UI Dashboard (/health-ui) - core já implementado, falta UI - Design Patterns Documentation (docs/design-patterns branch) - Rate Limiting: avaliar migração para AspNetCoreRateLimit vs manter custom - Logging: validar completude (Seq já configurado, verificar domain events e performance) Sprint 3 Parte 4 expandido de 3-4 dias para 5-8 dias com novas tarefas
- Trocar 'using NSubstitute' por 'using Moq' em 4 arquivos de teste - Converter Substitute.For<T>() para new Mock<T>().Object - Remover PackageReference NSubstitute dos .csproj - Adicionar PackageReference Moq no ServiceDefaults.Tests.csproj Arquivos modificados: - tests/MeAjudaAi.ServiceDefaults.Tests/ExtensionsTests.cs - tests/MeAjudaAi.ApiService.Tests/Extensions/SecurityExtensionsTests.cs - tests/MeAjudaAi.ApiService.Tests/Extensions/PerformanceExtensionsTests.cs - tests/MeAjudaAi.ApiService.Tests/Filters/ModuleTagsDocumentFilterTests.cs Razão: Padronizar com resto do projeto (todos outros testes usam Moq)
Padronizar geração de UUIDs v7 usando UuidGenerator centralizado. Arquivos atualizados (9): - src/Modules/SearchProviders/Tests/Unit/Infrastructure/Repositories/SearchableProviderRepositoryTests.cs - src/Modules/Providers/Tests/Unit/Application/Queries/GetProviderByDocumentQueryHandlerTests.cs - src/Modules/Users/Infrastructure/Services/LocalDevelopment/LocalDevelopmentUserDomainService.cs - src/Modules/Documents/Tests/Integration/DocumentsInfrastructureIntegrationTests.cs - tests/MeAjudaAi.Shared.Tests/Auth/ConfigurableTestAuthenticationHandler.cs - tests/MeAjudaAi.Integration.Tests/Modules/Users/UserRepositoryIntegrationTests.cs - tests/MeAjudaAi.Integration.Tests/Modules/Providers/ProviderRepositoryIntegrationTests.cs - tests/MeAjudaAi.Integration.Tests/Modules/Documents/DocumentRepositoryIntegrationTests.cs - tests/MeAjudaAi.E2E.Tests/Integration/UsersModuleTests.cs Benefícios: - Centraliza lógica de geração de UUIDs - Facilita futuras customizações (ex: timestamp override para testes) - Padrão único em todo código-base Nota: Alguns arquivos de teste podem precisar de 'using MeAjudaAi.Shared.Time;' adicional
Migração do formato .sln legado para .slnx (XML Solution format). Benefícios: - ✅ Formato legível e versionável (XML vs binário) - ✅ Melhor performance de load/save (até 5x mais rápido) - ✅ Reduz conflitos em merges git - ✅ Suporte nativo no .NET 9+ SDK e VS 2022 17.12+ Mudanças: - Criado MeAjudaAi.slnx via 'dotnet sln migrate' - Removido MeAjudaAi.sln (legacy) - Validado: 40 projetos listados corretamente - Build validado: Build succeeded - Workflows CI/CD atualizados (.sln → .slnx): - aspire-ci-cd.yml (7 substituições) - ci-cd.yml (2 substituições) - pr-validation.yml (3 substituições) Correções adicionais: - Adicionado 'using MeAjudaAi.Shared.Time;' em 6 arquivos de teste que usam UuidGenerator.NewId() mas faltava o using Arquivos de teste corrigidos: - GetProviderByDocumentQueryHandlerTests.cs - DocumentsInfrastructureIntegrationTests.cs - UsersModuleTests.cs (E2E) - UserRepositoryIntegrationTests.cs - ProviderRepositoryIntegrationTests.cs - DocumentRepositoryIntegrationTests.cs
- Workflow GitHub Actions para atualização automática de api-spec.json - Detecta mudanças em endpoints, DTOs, controllers automaticamente (src/**/API/**) - Gera OpenAPI spec via Swashbuckle CLI (sem rodar API) - Valida JSON e conta endpoints - Commita automaticamente api-spec.json atualizado - Deploy automático para GitHub Pages com ReDoc - Modificado generate-postman-collections.js para salvar api-spec.json - Documentação completa em docs/api-automation.md - Atualizado api/README.md com instruções de automação - Usa scripts existentes (generate-all-collections.bat/sh) Sprint 3 Parte 4: Task 8 - Automação OpenAPI concluída
- seed-dev-data.ps1 (Windows/PowerShell 7+) - seed-dev-data.sh (Linux/macOS/Bash) - Popula ServiceCatalogs: 6 categorias + 4 serviços - Popula Locations: 10 cidades permitidas (capitais) - Idempotente: detecta duplicatas via HTTP 409 - Autenticação automática via Keycloak - Output colorido e informativo - Validação de pré-requisitos (API + Keycloak) - Documentação completa em scripts/README.md Sprint 3 Parte 2: Tasks 6 e 7 - Seeding e Documentação
- IDevelopmentDataSeeder interface para abstração - DevelopmentDataSeeder implementação com: - Seed de ServiceCatalogs (6 categorias + 4 serviços) - Seed de Locations (10 cidades permitidas) - Verificação se banco está vazio (HasDataAsync) - Idempotente via ON CONFLICT DO NOTHING - Logging detalhado de progresso - SeedingExtensions para integração com aplicação - Integrado no Program.cs da API: - Executa automaticamente após migrations - Apenas em ambiente Development - Apenas se banco estiver vazio - Registrado em AddSharedServices (DI) - Funciona com Aspire AppHost - Scripts manuais (PowerShell/Bash) continuam disponíveis Benefícios: - Setup automático para novos desenvolvedores - Consistência de dados entre ambientes - Zero configuração manual - Scripts manuais para controle fino quando necessário Sprint 3 Parte 2: Seeding automático implementado
…onTool - MigrationExtensions.cs no AppHost/Extensions: - WithMigrations() extension method para PostgreSQL resources - MigrationHostedService roda migrations na inicialização - Descobre todos os DbContexts automaticamente via reflection - Lê connection string de variáveis de ambiente Aspire - Logging detalhado de progresso - Retry automático em caso de falha - Integrado em Program.cs: - Development: postgresql.MainDatabase.WithMigrations() - Testing: postgresql.MainDatabase.WithMigrations() - Removida pasta tools/MigrationTool (obsoleta) Vantagens sobre MigrationTool: - Integração nativa com Aspire (WaitFor, recursos) - Connection strings do Aspire (sem hardcoding) - Logs no Aspire Dashboard - Execução automática na ordem correta - Sem necessidade de script separado Migration agora é parte da orquestração Aspire
- Criar comando e handler AddServiceToProviderCommand
- Validação via IServiceCatalogsModuleApi.ValidateServicesAsync
- Verifica existência e status ativo do serviço
- Previne duplicação
- Criar comando e handler RemoveServiceFromProviderCommand
- Remove associação provider-service
- Valida que serviço está sendo oferecido
- Criar endpoints POST e DELETE /api/v1/providers/{providerId}/services/{serviceId}
- Autorização SelfOrAdmin
- Documentação OpenAPI completa
- Códigos HTTP apropriados (204, 400, 404)
- Adicionar Bruno collections
- AddServiceToProvider.bru
- RemoveServiceFromProvider.bru
- Adicionar project reference ServiceCatalogs.Application ao Providers.Application
- Corrigir warning S1144 em ProviderService.Provider (remover setter privado)
Migrations e entities já existiam (criados previamente)
Tabela provider_services com chave composta (provider_id, service_id)
Eventos de domínio: ProviderServiceAddedDomainEvent, ProviderServiceRemovedDomainEvent
WalkthroughAdds a Locations module (domain, application, API, persistence, migrations, and tests); replaces Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant API as Locations API
participant Dispatcher as ICommand/IQuery Dispatcher
participant Handler as CreateAllowedCityHandler
participant Ibge as IbgeService
participant Repo as IAllowedCityRepository
participant DB as PostgreSQL
Client->>API: POST /api/v1/admin/allowed-cities
API->>Dispatcher: Dispatch(CreateAllowedCityCommand)
Dispatcher->>Handler: HandleAsync(command)
alt geographic validation enabled
Handler->>Ibge: ValidateCityInAllowedRegionsAsync(city, state)
Ibge-->>Handler: validationResult
end
Handler->>Repo: ExistsAsync(city, state)
Repo->>DB: SELECT ... (ILIKE)
DB-->>Repo: exists?
alt not exists
Handler->>Repo: AddAsync(new AllowedCity)
Repo->>DB: INSERT ... RETURNING id
DB-->>Repo: persisted id
end
Repo-->>Handler: return id
Handler-->>Dispatcher: result (Guid)
Dispatcher-->>API: result
API-->>Client: 201 Created (Location + Guid)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Areas needing extra attention:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧬 Code graph analysis (1)src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
🔇 Additional comments (7)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (13)
docs/roadmap.md (1)
77-90: Align sprint table heading/year references with the actual sprint periods.The heading says “Cronograma de Sprints (Janeiro-Março 2025)” (Line 77), but the table entries are Nov/Dec 2025 and 2026 (Lines 81-89). Consider updating the heading to match.
Also, Line 93 says “Abril 2025+” while other places indicate post‑MVP as “Abril 2026+” (Line 24). Likely a year typo—worth normalizing to avoid roadmap confusion.
Also applies to: 91-94
src/Shared/Messaging/ServiceBus/ServiceBusMessageBus.cs (1)
112-127: Critical bug: Legitimate default values for value types will be incorrectly dead-lettered.The change from
message != nullto!object.Equals(message, default(T))introduces a critical bug for value types. WhenTis a value type (e.g.,int,bool,DateTime, or any struct),default(T)is the zero value (0, false,DateTime.MinValue, etc.), not null.Example failure scenario:
// Message contains valid JSON: "0" var message = JsonSerializer.Deserialize<int>(...); // message = 0 if (!object.Equals(0, default(int))) // default(int) = 0 // if (!object.Equals(0, 0)) = if (false) // → Message with legitimate value 0 is dead-lettered as "DeserializationFailed"This will cause data loss for any message containing default values when
Tis a value type.Recommended fix: Use a type-constrained check:
var message = JsonSerializer.Deserialize<T>(args.Message.Body.ToString(), _jsonOptions); -if (!object.Equals(message, default(T))) +// Only validate nullability for reference types; value types are always valid post-deserialization +if (message is not null || typeof(T).IsValueType) { await handler(message, args.CancellationToken);Alternatively, if null/default messages should never occur in your system, consider removing this validation entirely and relying on the outer exception handler to catch actual deserialization failures (malformed JSON throws
JsonException).src/Shared/Monitoring/MetricsCollectorService.cs (1)
61-98: Restore scope and cancellation token parameters for future implementation.Both helper methods have been refactored to remove the
IServiceScopeparameter and useCancellationToken.None:
- Removed scope parameter: When implementing the TODOs (lines 71, 90), you'll need access to scoped services like
DbContextor repositories to query real user/request counts. Without a scope parameter, the methods cannot resolve scoped dependencies.- Lines 69, 88: Using
CancellationToken.Noneprevents graceful cancellation during service shutdown.Apply this diff to both methods:
- private async Task<long> GetActiveUsersCount() + private async Task<long> GetActiveUsersCount(CancellationToken cancellationToken) { try { // Aqui você implementaria a lógica real para contar usuários ativos // Por exemplo, usuários que fizeram login nas últimas 24 horas // Placeholder - implementar com o serviço real de usuários - await Task.Delay(1, CancellationToken.None); // Simular operação async + await Task.Delay(1, cancellationToken); // Simular operação async // TODO: Implementar lógica real - por ora retorna valor fixo para evitar Random inseguro return 125; // Valor simulado fixo } catch (Exception ex) { logger.LogWarning(ex, "Failed to get active users count"); return 0; } } - private async Task<long> GetPendingHelpRequestsCount() + private async Task<long> GetPendingHelpRequestsCount(CancellationToken cancellationToken) { try { // Aqui você implementaria a lógica real para contar solicitações pendentes // Placeholder - implementar com o serviço real de help requests - await Task.Delay(1, CancellationToken.None); // Simular operação async + await Task.Delay(1, cancellationToken); // Simular operação async // TODO: Implementar lógica real - por ora retorna valor fixo para evitar Random inseguro return 25; // Valor simulado fixoAlternatively, if scoped services are needed for the real implementation, restore the scope parameter and resolve services from it:
- private async Task<long> GetActiveUsersCount(CancellationToken cancellationToken) + private async Task<long> GetActiveUsersCount(IServiceScope scope, CancellationToken cancellationToken)Then update
CollectMetricsto pass the scope to both helpers.src/Shared/Behaviors/CachingBehavior.cs (2)
36-40: Critical: Legitimate default values are incorrectly treated as cache misses.This change prevents caching of legitimate default values for value types. For example:
- A count query returning
0- A boolean query returning
false- A numeric calculation returning
0.0These are valid results that should be cached to avoid redundant query execution. The previous null-check was correct for reference types, but this change breaks value-type caching semantics.
Consider using a wrapper type or sentinel value to distinguish "no cached value" from "cached default value":
-var cachedResult = await cacheService.GetAsync<TResponse>(cacheKey, cancellationToken); -if (!object.Equals(cachedResult, default(TResponse))) +var cacheEntry = await cacheService.GetAsync<CacheEntry<TResponse>>(cacheKey, cancellationToken); +if (cacheEntry is not null) { logger.LogDebug("Cache hit for key: {CacheKey}", cacheKey); - return cachedResult; + return cacheEntry.Value; }Alternatively, update
ICacheServiceto return(bool found, T value)tuple to explicitly distinguish cache misses from cached default values.
48-60: Critical: Legitimate default values will not be cached.This condition prevents caching valid default values, forcing every subsequent request to re-execute the query. This defeats the purpose of caching and can cause significant performance degradation.
Example scenarios broken by this change:
GetItemCount()returning0itemsIsFeatureEnabled()returningfalseCalculateDiscount()returning0.0mApply the same wrapper approach suggested for the cache retrieval to ensure all valid query results are cached:
var result = await next(); -// Armazena no cache se o resultado não for nulo -if (!object.Equals(result, default(TResponse))) -{ - var options = new HybridCacheEntryOptions - { - Expiration = cacheExpiration, - LocalCacheExpiration = TimeSpan.FromMinutes(5) - }; - - await cacheService.SetAsync(cacheKey, result, cacheExpiration, options, cacheTags, cancellationToken); - - logger.LogDebug("Cached result for key: {CacheKey} with expiration: {Expiration}", - cacheKey, cacheExpiration); -} +// Always cache the result (including default values) +var options = new HybridCacheEntryOptions +{ + Expiration = cacheExpiration, + LocalCacheExpiration = TimeSpan.FromMinutes(5) +}; + +var cacheEntry = new CacheEntry<TResponse> { Value = result }; +await cacheService.SetAsync(cacheKey, cacheEntry, cacheExpiration, options, cacheTags, cancellationToken); + +logger.LogDebug("Cached result for key: {CacheKey} with expiration: {Expiration}", + cacheKey, cacheExpiration);src/Shared/Logging/SerilogConfigurator.cs (1)
69-83: Remove deadConfigureSerilogreturn value and consolidate duplicated logging configuration.
ConfigureSerilog(...)returns aLoggerConfiguration(line 119 inAddStructuredLogging) that is never used, and all its configuration is redundantly re-specified directly onloggerConfig. Additionally,ConfigureApplicationInsights(configuration)is an unfulfilled placeholder (no-op at lines 88–95, noApplicationInsights:ConnectionStringin appsettings, noSerilog.Sinks.ApplicationInsightsdependency) that will never execute and misleads readers into thinking Application Insights is configured in production.Refactor
ConfigureSerilogto accept and modify the providedLoggerConfigurationdirectly (not create and return a new one), then call it fromAddStructuredLogging:- public static LoggerConfiguration ConfigureSerilog(IConfiguration configuration, IWebHostEnvironment environment) + public static void ConfigureSerilog(LoggerConfiguration loggerConfig, IConfiguration configuration, IWebHostEnvironment environment) { - var loggerConfig = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) + loggerConfig + .ReadFrom.Configuration(configuration) .Enrich.FromLogContext() .Enrich.WithProperty("Application", "MeAjudaAi") .Enrich.WithProperty("Environment", environment.EnvironmentName) .Enrich.WithProperty("MachineName", Environment.MachineName) .Enrich.WithProperty("ProcessId", Environment.ProcessId) .Enrich.WithProperty("Version", GetApplicationVersion()) .Destructure.ByTransforming<KeycloakPermissionOptions>(o => new { o.BaseUrl, o.Realm, o.ClientId, o.AdminUsername, ClientSecret = "***REDACTED***", AdminPassword = "***REDACTED***" }); ApplyEnvironmentSpecificConfiguration(loggerConfig, configuration, environment); - - return loggerConfig; }services.AddSerilog((serviceProvider, loggerConfig) => { - var configuredLogger = SerilogConfigurator.ConfigureSerilog(configuration, environment); - - loggerConfig.ReadFrom.Configuration(configuration) - .Enrich.FromLogContext() - .Enrich.WithProperty("Application", "MeAjudaAi") - ... + SerilogConfigurator.ConfigureSerilog(loggerConfig, configuration, environment); + + // Sinks configured here (or move into ConfigureSerilog if preferred) });Remove or complete the
ConfigureApplicationInsightsmethod—if Application Insights integration is not planned, delete the call and method; if it is planned, wire the sink and specify the dependency.src/Modules/Locations/Infrastructure/packages.lock.json (1)
439-445: Add MediatR to Directory.Packages.props with a concrete version. The dependency currently appears in the lock file with an unbounded range"(, )", which breaks deterministic restores and allows unexpected upgrades. Since Central Package Management is enabled (ManagePackageVersionsCentrally=true), MediatR must be pinned to a specific version in Directory.Packages.props alongside other managed packages like Microsoft.AspNetCore.Mvc.Testing or FluentValidation.src/Modules/Locations/Infrastructure/Services/GeographicValidationService.cs (1)
7-35: Avoid “unused parameter” analyzer noise + make ignore explicit in code, not just comment.Consider explicitly consuming
allowedCitiesto silence CA1801-style rules and prevent future “oops we forgot it’s ignored” regressions:public async Task<bool> ValidateCityAsync( string cityName, string? stateSigla, IReadOnlyCollection<string> allowedCities, // IGNORADO: usar banco de dados CancellationToken cancellationToken = default) { + _ = allowedCities; // intentionally ignored: DB-backed validation + logger.LogDebug( "GeographicValidationService: Validando cidade {CityName} (UF: {State}) usando banco de dados", cityName, stateSigla ?? "N/A");api/README.md (1)
120-127: Docs contradict themselves about whetherapi-spec.jsonis versioned.This section says specs are not version controlled, but earlier you instruct committing
api/api-spec.jsonand you also describe an automation that auto-commits it. Pick one approach and make the README consistent (otherwise contributors won’t know whether changes should be committed or ignored).src/Shared/Extensions/ServiceCollectionExtensions.cs (1)
29-79: GuardAddDevelopmentSeeding()registration to non-test environments for cleaner design.
AddDevelopmentSeeding()only registers theIDevelopmentDataSeederservice and does not execute seeding logic directly. The actual seeding is executed later viaSeedDevelopmentDataIfNeededAsync()inProgram.cs(line 48), which includes an internalIHostEnvironment.IsDevelopment()guard that prevents seeding in non-Development environments.While the execution-time gating provides protection, gating the registration itself would be cleaner:
- // Adicionar seeding de dados de desenvolvimento - services.AddDevelopmentSeeding(); + // Adicionar seeding de dados de desenvolvimento + if (!isTestingEnvironment && envName == EnvironmentNames.Development) + { + services.AddDevelopmentSeeding(); + }This avoids registering unused services in test/production environments and makes the intent explicit at the registration layer.
src/Modules/Locations/Tests/Unit/Infrastructure/Services/IbgeServiceTests.cs (3)
65-83: MissingIsCityAllowedAsyncmock setup will cause test failure.With
MockBehavior.Stricton_allowedCityRepositoryMock, this test will fail becauseIsCityAllowedAsyncis called byValidateCityInAllowedRegionsAsyncafter the state validation passes, but no setup is provided for it.Add the missing mock setup:
SetupCacheGetOrCreate(cityName, municipio); + _allowedCityRepositoryMock + .Setup(x => x.IsCityAllowedAsync("São Paulo", "SP", It.IsAny<CancellationToken>())) + .ReturnsAsync(false); + // Act var result = await _sut.ValidateCityInAllowedRegionsAsync(cityName, stateSigla);
85-103: MissingIsCityAllowedAsyncmock setup for case-insensitive test.Similar to the previous test, this will fail with
MockBehavior.StrictbecauseIsCityAllowedAsyncis not set up. The state comparison passes (lowercase "mg" matches returned "MG" case-insensitively), so the repository method gets called.SetupCacheGetOrCreate(cityName, municipio); + _allowedCityRepositoryMock + .Setup(x => x.IsCityAllowedAsync("Muriaé", "MG", It.IsAny<CancellationToken>())) + .ReturnsAsync(true); + // Act var result = await _sut.ValidateCityInAllowedRegionsAsync(cityName, stateSigla);
144-162: MissingIsCityAllowedAsyncmock setup when no state is provided.When
stateSiglais null, the state validation is skipped andIsCityAllowedAsyncis still called. Without a mock setup, this test will fail withMockBehavior.Strict.SetupCacheGetOrCreate(cityName, municipio); + _allowedCityRepositoryMock + .Setup(x => x.IsCityAllowedAsync("Muriaé", "MG", It.IsAny<CancellationToken>())) + .ReturnsAsync(true); + // Act var result = await _sut.ValidateCityInAllowedRegionsAsync(cityName, stateSigla);
🧹 Nitpick comments (28)
src/Shared/Messaging/DeadLetter/RabbitMqDeadLetterService.cs (2)
71-74: Clarify cancellation semantics for admin notifications (avoid unbounded awaits).
Removing theCancellationTokenmakes future notification implementations (email/Slack/HTTP) potentially non-cancellable and able to stallSendToDeadLetterAsynclonger than intended. If the goal is “notify even when the request is canceled”, consider keeping the token but explicitly passingCancellationToken.None(or dispatching to a background queue) and adding a timeout.
402-418: AddCancellationTokenparameter before implementing real admin notifications.Once the TODO is implemented with real async work (e.g., sending emails/webhooks), this method will need cancellation support to prevent blocking on shutdown and to protect request-thread latency. Update the signature to
private async Task NotifyAdministratorsAsync(FailedMessageInfo failedMessageInfo, CancellationToken cancellationToken = default)and pass the token fromSendToDeadLetterAsync. This follows RabbitMQ.Client best practices for bounded async operations.src/Shared/Messaging/DeadLetter/ServiceBusDeadLetterService.cs (1)
53-56: Silent error handling in notification dispatch; consider CancellationToken when admin notifications are implemented.The current implementation (line 245–262) is a no-op (TODO +
Task.CompletedTask), but the exception handler silently logs and continues without propagating failures. When notifications are implemented (email, Slack, Teams, etc.), ensure:
- Either pass
CancellationTokenfromSendToDeadLetterAsyncto prevent unbounded hangs during shutdown, or- Enforce a short timeout and/or fire-and-forget dispatch to avoid blocking DLQ processing.
The same pattern appears in the RabbitMQ implementation (402–417).
src/Modules/Locations/Infrastructure/ExternalApis/Clients/IbgeClient.cs (1)
88-93:when (ex != null)is a no-op; distinguish timeout vs caller cancellation instead.
Right now you’ll log “Timeout…” even when the caller cancels viacancellationToken(common with HTTP request aborts). Prefer filtering oncancellationToken.IsCancellationRequestedand avoid mislabeling / noisy error logs.- catch (TaskCanceledException ex) when (ex != null) + // HttpClient timeouts often surface as TaskCanceledException when the token was NOT canceled. + catch (TaskCanceledException ex) when (!cancellationToken.IsCancellationRequested) { // Re-throw timeout exceptions to enable middleware fallback logger.LogError(ex, "Timeout querying IBGE for municipality {CityName}", cityName); throw; } + // Propagate caller cancellation without logging as an error/timeout. + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + }src/Shared/Logging/SerilogConfigurator.cs (1)
85-95: Make the “future AI” intent explicit (or implement it).
Right now the method silently no-ops even whenApplicationInsights:ConnectionStringis present; consider at least a clear TODO (with issue link) or implementing the sink so production behavior matches expectations.private static void ConfigureApplicationInsights(IConfiguration configuration) { var connectionString = configuration["ApplicationInsights:ConnectionString"]; if (!string.IsNullOrEmpty(connectionString)) { - // Application Insights será implementado no futuro quando necessário + // TODO(#<issue>): Wire Application Insights sink/telemetry (Serilog sink or OTEL) when adopted. } }Please verify which package/API you want here (e.g., Serilog sink vs OTEL), since the correct wiring depends on that choice.
src/Bootstrapper/MeAjudaAi.ApiService/Extensions/ServiceCollectionExtensions.cs (1)
76-86: ProblemDetails enablement is good; consider environment-gating exception details. Line 80 is a solid step, but you may want an explicit configuration to ensure Production never includes exception details even if defaults/config change.- services.AddProblemDetails(); + services.AddProblemDetails(options => + { + options.IncludeExceptionDetails = (_, _) => environment.IsDevelopment() || environment.IsEnvironment("Testing"); + });src/Modules/Providers/Application/Handlers/Commands/RemoveServiceFromProviderCommandHandler.cs (1)
57-66: Consider more granular exception handling.The current catch-all exception handler returns raw exception messages to callers, which may leak implementation details. Domain exceptions (e.g.,
ProviderDomainExceptionwhen the service doesn't exist) represent business rule violations and should be handled separately from unexpected infrastructure exceptions.Apply this pattern to distinguish domain exceptions from unexpected errors:
- catch (Exception ex) + catch (ProviderDomainException ex) + { + logger.LogWarning( + ex, + "Business rule violation removing service {ServiceId} from provider {ProviderId}", + command.ServiceId, + command.ProviderId); + + return Result.Failure(ex.Message); + } + catch (Exception ex) { logger.LogError( ex, "Error removing service {ServiceId} from provider {ProviderId}", command.ServiceId, command.ProviderId); - return Result.Failure($"An error occurred while removing service from provider: {ex.Message}"); + return Result.Failure("An unexpected error occurred while removing service from provider"); }scripts/seed-dev-data.sh (1)
76-94: Fix shellcheck warnings: separate variable declaration from assignment.Lines 82 and 88 combine declaration and assignment, which masks the return value of the command substitution. If the
curlcommand fails, the variable will be set but the error won't propagate due to thelocalkeyword absorbing the exit status.Apply this fix to properly handle command failures:
create_category() { local name="$1" local description="$2" info "Criando categoria: $name" - local response=$(curl -sf -X POST "$API_BASE_URL/api/v1/catalogs/admin/categories" \ + local response + response=$(curl -sf -X POST "$API_BASE_URL/api/v1/catalogs/admin/categories" \ "${HEADERS[@]}" \ -d "{\"name\":\"$name\",\"description\":\"$description\"}" \ 2>/dev/null || echo "") if [ -n "$response" ]; then - local id=$(echo "$response" | jq -r '.id') + local id + id=$(echo "$response" | jq -r '.id') CATEGORY_IDS[$name]=$id success "Categoria '$name' criada (ID: $id)" else warning "Categoria '$name' já existe ou erro ao criar" fi }docs/api-automation.md (1)
115-120: Wrap bare URLs in angle brackets for proper markdown formatting.Markdown linters flag bare URLs as they should be enclosed in angle brackets for proper rendering and link detection.
Apply this formatting fix:
### GitHub Pages -- **ReDoc (navegável)**: https://frigini.github.io/MeAjudaAi/api/ -- **OpenAPI JSON**: https://frigini.github.io/MeAjudaAi/api/api-spec.json +- **ReDoc (navegável)**: <https://frigini.github.io/MeAjudaAi/api/> +- **OpenAPI JSON**: <https://frigini.github.io/MeAjudaAi/api/api-spec.json> ### Swagger UI (local) -- **Swagger UI**: http://localhost:5000/swagger -- **OpenAPI JSON**: http://localhost:5000/api-docs/v1/swagger.json +- **Swagger UI**: <http://localhost:5000/swagger> +- **OpenAPI JSON**: <http://localhost:5000/api-docs/v1/swagger.json>src/Aspire/MeAjudaAi.AppHost/Extensions/MigrationExtensions.cs (1)
123-181: Consider adding timeout protection for migration operations.Long-running migrations could cause startup delays or hangs. Consider adding a timeout or making the migration process optional via configuration.
Add a cancellation token timeout:
- private async Task MigrateDbContextAsync(Type contextType, string connectionString, CancellationToken cancellationToken) + private async Task MigrateDbContextAsync(Type contextType, string connectionString, CancellationToken cancellationToken) { var moduleName = ExtractModuleName(contextType); _logger.LogInformation("🔧 Aplicando migrations para {Module}...", moduleName); try { + // Add timeout protection (e.g., 5 minutes per module) + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + cts.CancelAfter(TimeSpan.FromMinutes(5)); + // Criar DbContextOptionsBuilder dinamicamente var optionsBuilderType = typeof(DbContextOptionsBuilder<>).MakeGenericType(contextType); // ... - await context.Database.MigrateAsync(cancellationToken); + await context.Database.MigrateAsync(cts.Token);src/Shared/Exceptions/GlobalExceptionHandler.cs (1)
98-107: ArgumentException → 400 is good; consider sanitizingdetailand omitting nullparameterName.Returning
argumentException.Messagecan unintentionally expose internal details if upstream throws with sensitive context; consider a genericdetail(keepingparameterName) or ensuring upstream messages are safe for clients.src/Modules/Providers/Application/MeAjudaAi.Modules.Providers.Application.csproj (1)
15-19: New ProjectReference: ensure no cyclic dependency and keep cross-module boundary clean.If Providers only needs validation/query operations, consider depending on a slimmer contract (e.g., an API/abstractions project) rather than the full
ServiceCatalogs.Application, unless that’s explicitly the intended architecture.src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/GetAllowedCityById.bru (1)
34-34: Path parameter naming inconsistency.The documentation refers to the path parameter as
id, but the actual URL template usesallowedCityId. For consistency, the documentation should match the URL template variable name.Apply this change to align the documentation:
- - `id` (guid): ID da cidade permitida + - `allowedCityId` (guid): ID da cidade permitidasrc/Modules/Locations/API/API.Client/AllowedCitiesAdmin/UpdateAllowedCity.bru (1)
43-43: Path parameter naming inconsistency.The documentation refers to the path parameter as
id, but the actual URL template usesallowedCityId. For consistency, the documentation should match the URL template variable name.Apply this change:
- - `id` (guid): ID da cidade permitida a ser atualizada + - `allowedCityId` (guid): ID da cidade permitida a ser atualizadasrc/Modules/Locations/Application/DTOs/AllowedCityDto.cs (1)
1-15: ConsiderDateTimeOffset(or ensure UTC) + confirmCreatedBy/UpdatedByaren’t leaking PII.If
CreatedByis an email/username, that may be PII for admin endpoints—worth confirming intended exposure.src/Modules/Locations/Application/Handlers/CreateAllowedCityHandler.cs (1)
27-36: Consider adding command-level validation.Currently, input validation relies on the
AllowedCityconstructor throwingArgumentExceptionfor invalid inputs. While functional, CQRS patterns typically benefit from explicit command validation (e.g., FluentValidation) to provide clearer error messages and separate concerns.src/Modules/Locations/Infrastructure/Filters/LocationsExceptionFilter.cs (1)
15-41: Consider removing this filter in favor of the exception handler.Both
LocationsExceptionFilter(IExceptionFilter) andLocationsExceptionHandler(IExceptionHandler) implement identical exception-to-HTTP mapping logic forNotFoundExceptionandBadRequestException.In modern ASP.NET Core,
IExceptionHandler(middleware-based) is the preferred approach and runs before MVC filters. Unless MVC controller support requires the filter specifically, having both creates unnecessary duplication.src/Modules/Locations/Infrastructure/Extensions.cs (2)
32-47: DbContext configuration looks solid.The PostgreSQL configuration with explicit migrations history table and assembly is well done. One consideration:
Line 46:
EnableSensitiveDataLoggingis controlled by configuration, which is good. However, ensure theLogging:EnableSensitiveDataLoggingsetting defaults tofalsein production environments to avoid inadvertently logging sensitive query data (including potential PII).- options.EnableSensitiveDataLogging(configuration.GetValue<bool>("Logging:EnableSensitiveDataLogging")); + // Only enable sensitive data logging in development to avoid PII exposure + options.EnableSensitiveDataLogging( + configuration.GetValue<bool>("Logging:EnableSensitiveDataLogging", defaultValue: false));
49-54: Consider removing unnecessary design-time factory registration.The
Func<LocationsDbContext>registration appears unnecessary. Design-time migrations use theLocationsDbContextFactoryclass (IDesignTimeDbContextFactory), which creates its own context independently of DI. This factory delegate adds complexity without apparent benefit at runtime.If this was intended for a specific use case (e.g., lazy context resolution in migrations), please clarify; otherwise consider removing:
- // Registrar Func<LocationsDbContext> para uso em migrations (design-time) - services.AddScoped<Func<LocationsDbContext>>(provider => () => - { - var context = provider.GetRequiredService<LocationsDbContext>(); - return context; - }); -src/Modules/Locations/Application/Handlers/GetAllowedCityByIdHandler.cs (1)
14-34: Handler logic is correct; consider extracting shared mapping logic.The handler correctly fetches the city and returns null for the endpoint to handle as 404. However, the DTO mapping (lines 23-33) is duplicated with
GetAllAllowedCitiesHandler(seesrc/Modules/Locations/Application/Handlers/GetAllAllowedCitiesHandler.cslines 19-28).Consider extracting the mapping to a shared extension method or static helper to avoid duplication:
// In AllowedCityDto.cs or a separate mapper class public static AllowedCityDto FromEntity(AllowedCity city) => new(city.Id, city.CityName, city.StateSigla, city.IbgeCode, city.IsActive, city.CreatedAt, city.UpdatedAt, city.CreatedBy, city.UpdatedBy);Then both handlers can use
AllowedCityDto.FromEntity(city).scripts/seed-dev-data.ps1 (1)
38-41: Function names shadow built-in PowerShell cmdlets.
Write-Warning(line 40) andWrite-Error(line 41) shadow built-in PowerShell cmdlets. This could cause unexpected behavior if the built-in functionality is needed elsewhere in the script.Consider renaming to avoid shadowing:
-function Write-Warning { param($Message) Write-Host "⚠️ $Message" -ForegroundColor Yellow } -function Write-Error { param($Message) Write-Host "❌ $Message" -ForegroundColor Red } +function Write-Warn { param($Message) Write-Host "⚠️ $Message" -ForegroundColor Yellow } +function Write-Err { param($Message) Write-Host "❌ $Message" -ForegroundColor Red }src/Modules/Locations/Tests/Unit/Application/Handlers/CreateAllowedCityHandlerTests.cs (1)
26-43: Consider capturing and verifying entity properties.While the test validates that
AddAsyncis called, you could optionally enhance coverage by capturing theAllowedCityargument and verifying that properties likeCityNameandStateSiglamatch the command values, similar to the pattern in other tests (lines 73-83).Example enhancement:
+ AllowedCity? capturedCity = null; _repositoryMock.Setup(x => x.ExistsAsync(command.CityName, command.StateSigla, It.IsAny<CancellationToken>())) .ReturnsAsync(false); + _repositoryMock.Setup(x => x.AddAsync(It.IsAny<AllowedCity>(), It.IsAny<CancellationToken>())) + .Callback<AllowedCity, CancellationToken>((city, _) => capturedCity = city) + .Returns(Task.CompletedTask); // Act var result = await _handler.HandleAsync(command, CancellationToken.None); // Assert result.Should().NotBeEmpty(); + capturedCity.Should().NotBeNull(); + capturedCity!.CityName.Should().Be("Muriaé"); + capturedCity.StateSigla.Should().Be("MG"); + capturedCity.CreatedBy.Should().Be(userEmail); _repositoryMock.Verify(x => x.AddAsync(It.IsAny<AllowedCity>(), It.IsAny<CancellationToken>()), Times.Once);.github/workflows/update-api-docs.yml (1)
99-116: Consider consolidating redirects for cleaner shell script.The script works correctly, but static analysis suggests consolidating multiple echo redirects into a single block for better style.
Apply this diff for cleaner shell syntax:
- - name: 📊 Generate API Stats - run: | - echo "# 📊 API Statistics" > api-stats.md - echo "" >> api-stats.md - echo "- **Total Paths**: $(jq '.paths | length' api/api-spec.json)" >> api-stats.md - echo "- **Total Operations**: $(jq '[.paths[][] | select(type == "object")] | length' api/api-spec.json)" >> api-stats.md - echo "- **API Version**: $(jq -r '.info.version' api/api-spec.json)" >> api-stats.md - echo "- **Generated**: $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> api-stats.md - cat api-stats.md + - name: 📊 Generate API Stats + run: | + { + echo "# 📊 API Statistics" + echo "" + echo "- **Total Paths**: $(jq '.paths | length' api/api-spec.json)" + echo "- **Total Operations**: $(jq '[.paths[][] | select(type == "object")] | length' api/api-spec.json)" + echo "- **API Version**: $(jq -r '.info.version' api/api-spec.json)" + echo "- **Generated**: $(date -u +"%Y-%m-%d %H:%M:%S UTC")" + } > api-stats.md + cat api-stats.mdOtherwise, the commit step is properly configured with
[skip ci]to prevent infinite loops.src/Modules/Locations/Tests/Integration/AllowedCityRepositoryIntegrationTests.cs (2)
354-358: Redundant SaveChangesAsync call in helper method.
AllowedCityRepository.AddAsyncalready callsSaveChangesAsyncinternally (as shown in the relevant code snippets). The additionalawait _context.SaveChangesAsync();inAddCityAndSaveAsyncis redundant.private async Task AddCityAndSaveAsync(AllowedCity city) { await _repository.AddAsync(city); - await _context.SaveChangesAsync(); }Based on learnings, the repository follows an auto-save pattern.
360-364: Same redundancy in UpdateCityAndSaveAsync.
AllowedCityRepository.UpdateAsyncalso callsSaveChangesAsyncinternally.private async Task UpdateCityAndSaveAsync(AllowedCity city) { await _repository.UpdateAsync(city); - await _context.SaveChangesAsync(); }src/Modules/Locations/Domain/Entities/AllowedCity.cs (2)
81-81: Consider usingUuidGenerator.NewId()for consistency.The PR summary mentions migrating from
Guid.CreateVersion7()toUuidGenerator.NewId()across the codebase. This entity usesGuid.NewGuid()which generates v4 UUIDs. For consistency with the rest of the codebase and to enable time-ordered IDs, consider using the project'sUuidGenerator.NewId()if available.
63-88: Validation logic is duplicated between constructor and Update method.Consider extracting the common validation into a private method to reduce duplication:
private static (string cityName, string stateSigla, string actor) ValidateAndNormalize( string cityName, string stateSigla, string actor, string actorParamName) { cityName = cityName?.Trim() ?? string.Empty; stateSigla = stateSigla?.Trim().ToUpperInvariant() ?? string.Empty; actor = actor?.Trim() ?? string.Empty; if (string.IsNullOrWhiteSpace(cityName)) throw new ArgumentException("Nome da cidade não pode ser vazio", nameof(cityName)); // ... rest of validation return (cityName, stateSigla, actor); }src/Modules/Locations/Infrastructure/Services/IbgeService.cs (1)
49-51: Null-coalescing on non-nullable value adds noise.The comment states
ufSigla never null because GetEstadoSigla returns non-nullable string, but line 51 usesufSigla ?? string.Empty. If the comment is accurate, the null-coalescing is unnecessary:-var isAllowed = await allowedCityRepository.IsCityAllowedAsync(municipio.Nome, ufSigla ?? string.Empty, cancellationToken); +var isAllowed = await allowedCityRepository.IsCityAllowedAsync(municipio.Nome, ufSigla, cancellationToken);Or remove the comment if null is actually possible.
src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru
Outdated
Show resolved
Hide resolved
src/Modules/Locations/Tests/Unit/Application/Handlers/UpdateAllowedCityHandlerTests.cs
Show resolved
Hide resolved
src/Modules/Locations/Tests/Unit/Application/Handlers/UpdateAllowedCityHandlerTests.cs
Show resolved
Hide resolved
src/Modules/Locations/Tests/Unit/Infrastructure/Services/IbgeServiceTests.cs
Outdated
Show resolved
Hide resolved
- Add missing using directive for IHostedService in MigrationExtensions.cs - Fix badly formed XML comments (replace & with 'and', escape arrows) - Add null-forgiving operators to fix CS8603, CS8602, CS8604 warnings - Remove hardcoded DB credentials, require explicit config in production - Add null-safe normalization in AllowedCityRepository methods - Fix test assertions to match actual exception types thrown - Fix integration test ordering assertions (StateSigla first, then CityName) - Remove stray comment in IbgeServiceTests - Fix undefined variables in seed-dev-data.ps1 - Fix bare URLs in documentation (api/README.md, docs/roadmap.md) - Fix Bruno API client documentation (parameter names, URL paths) - Refactor SerilogConfigurator to avoid dead return value - Remove unused scope in MetricsCollectorService, add cancellation token - Update CachingBehavior to cache all values including defaults - Update HybridCacheService to properly track cache hits with flag
Sprint 3 (11-30 Dez 2025) - Todas as 4 partes concluídas: Parte 1 - Documentation Migration to GitHub Pages ✅ - Audit completo: 43 arquivos .md consolidados - mkdocs.yml configurado com navegação hierárquica - GitHub Actions workflow funcionando - Deploy validado e publicado Parte 2 - Admin Endpoints + Tools ✅ - Admin endpoints AllowedCities (5 endpoints CRUD) - Bruno Collections (6 arquivos) - 4 integration + 15 E2E tests (100% passando) - Exception handling completo - Code quality & security fixes (2 commits) Parte 3 - Module Integrations ✅ - Providers ↔ ServiceCatalogs integration - Aspire Migrations (MigrationHostedService) - Data seeding automático + scripts Parte 4 - Code Quality & Standardization ✅ - NSubstitute → Moq (4 arquivos) - UuidGenerator unification (9 arquivos) - Migração .slnx (40 projetos, 3 workflows) - OpenAPI automation (GitHub Actions + ReDoc) Build Status: ✅ 0 erros, 100% testes passando Próximos passos: Sprint 4 identificado com tarefas pendentes
- Ajustar assertions de testes de handlers (exception types) - Corrigir tracking de cache hits no HybridCacheService - Remover comentário solto no IbgeServiceTests
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/Shared/Monitoring/MetricsCollectorService.cs (2)
17-36: Avoid logging cancellations as errors/warnings (handleOperationCanceledExceptionexplicitly).
Task.Delay(_interval, stoppingToken)will throw on shutdown; also the placeholderTask.Delay(1, cancellationToken)will throw. Right now these can get logged as warnings/errors during normal service stop.Consider this adjustment:
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { logger.LogInformation("Metrics collector service started"); while (!stoppingToken.IsCancellationRequested) { try { await CollectMetrics(stoppingToken); } + catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) + { + break; + } catch (Exception ex) { logger.LogError(ex, "Error collecting metrics"); } - await Task.Delay(_interval, stoppingToken); + try + { + await Task.Delay(_interval, stoppingToken); + } + catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) + { + break; + } } logger.LogInformation("Metrics collector service stopped"); }
38-57: Don’t swallow/log cancellations inside metric collection; reserve logs for real failures.
InCollectMetrics/GetActiveUsersCount/GetPendingHelpRequestsCount, catchingExceptionwill also catchOperationCanceledException, causing noisy “Failed to …” logs on shutdown and turning cancellation into “0” metrics.A minimal pattern:
private async Task<long> GetActiveUsersCount(CancellationToken cancellationToken) { try { ... await Task.Delay(1, cancellationToken); return 125; } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } catch (Exception ex) { logger.LogWarning(ex, "Failed to get active users count"); return 0; } }Apply similarly to
GetPendingHelpRequestsCount, and inCollectMetricseither rethrow OCE or skip warning log for it.Also applies to: 59-98
src/Modules/Locations/Tests/Unit/Infrastructure/Services/IbgeServiceTests.cs (3)
66-83: Missing mock setup will cause test failure.This test calls
ValidateCityInAllowedRegionsAsync, which will invokeIsCityAllowedAsyncon the repository mock. Since the mock usesMockBehavior.Strict(line 31) and no setup is provided, the test will throw aMockExceptionat runtime.Apply this diff to add the missing setup:
var municipio = CreateMunicipio(3550308, "São Paulo", "SP"); SetupCacheGetOrCreate(cityName, municipio); + _allowedCityRepositoryMock + .Setup(x => x.IsCityAllowedAsync("São Paulo", "SP", It.IsAny<CancellationToken>())) + .ReturnsAsync(false); // Act var result = await _sut.ValidateCityInAllowedRegionsAsync(cityName, stateSigla); // Assert result.Should().BeFalse(); _cacheServiceMock.Verify(); + _allowedCityRepositoryMock.Verify();
86-102: Missing mock setup will cause test failure.This test reaches the repository check but lacks a setup for
IsCityAllowedAsync. WithMockBehavior.Strict, the test will fail with aMockException.Apply this diff:
var municipio = CreateMunicipio(3129707, "Muriaé", "MG"); SetupCacheGetOrCreate(cityName, municipio); + _allowedCityRepositoryMock + .Setup(x => x.IsCityAllowedAsync("Muriaé", "MG", It.IsAny<CancellationToken>())) + .ReturnsAsync(true); // Act var result = await _sut.ValidateCityInAllowedRegionsAsync(cityName, stateSigla); // Assert result.Should().BeTrue(); _cacheServiceMock.Verify(); + _allowedCityRepositoryMock.Verify();
144-161: Missing mock setup will cause test failure.Even when
stateSiglais null, the production code still callsIsCityAllowedAsyncafter successfully retrieving the municipality. The strict mock will throw without a setup.Apply this diff:
var municipio = CreateMunicipio(3129707, "Muriaé", "MG"); SetupCacheGetOrCreate(cityName, municipio); + _allowedCityRepositoryMock + .Setup(x => x.IsCityAllowedAsync("Muriaé", "MG", It.IsAny<CancellationToken>())) + .ReturnsAsync(true); // Act var result = await _sut.ValidateCityInAllowedRegionsAsync(cityName, stateSigla); // Assert result.Should().BeTrue(); _cacheServiceMock.Verify(); + _allowedCityRepositoryMock.Verify();
♻️ Duplicate comments (2)
src/Shared/Caching/HybridCacheService.cs (1)
12-37:GetAsyncstill risks polluting the cache withdefault(T)(read path performs writes).
Right now, a cache miss will run the factory (Line 21-25) and may cause HybridCache to storedefault(T)for that key; after that, future calls can become “hit returning default” even though no real value was ever set. This reintroduces the same “missing vs cached default” ambiguity and can skew both behavior and metrics.Suggested direction (pending HybridCache API confirmation): make
GetAsynca true read-only operation (e.g.,TryGet/GetAsyncif available), or remove/avoidGetAsyncand force callers ontoGetOrCreateAsyncwhere a real factory is provided.Microsoft.Extensions.Caching.Hybrid (HybridCache): is there a read-only GetAsync/TryGet API (no write on miss), or an option/flag to prevent caching the factory result (especially when null/default)?api/README.md (1)
72-74: Fix branch name reference (main→master).Line 74 references "branch
main" but the PR targets themasterbranch as default. Update to match the actual branch name for accuracy.-- 🔄 **Atualização**: Automática a cada push na branch `main` +- 🔄 **Atualização**: Automática a cada push na branch `master`
🧹 Nitpick comments (11)
src/Shared/Caching/HybridCacheService.cs (1)
15-36: Consider simplifying the return path and making the “miss returns default” behavior explicit at the interface boundary.
return factoryCalled ? default : result;(Line 36) bakes in a semantic change:GetAsyncnow guaranteesdefaulton miss, not “no value” as a distinct concept. If theICacheServicecontract expects “missing” to be distinguishable, prefer aTryGetAsyncshape (bool found, T? value) or similar.src/Modules/Locations/Tests/Unit/Application/Handlers/CreateAllowedCityHandlerTests.cs (1)
27-44: Consider asserting the persisted entity fields in the “valid command” test
You already captureAllowedCityin other tests; doing the same here would better validateCreatedBy,IbgeCode, andIsActivemapping.src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/DeleteAllowedCity.bru (1)
17-19:Content-Type: application/jsonis unnecessary forbody: none
Not blocking, but removing it avoids implying a request body.src/Modules/Locations/Tests/Unit/Application/Handlers/UpdateAllowedCityHandlerTests.cs (1)
115-139: Tighten “UpdatingSameCityWithSameName” to actually cover the intended branch
Right nowGetByCityAndStateAsync(...)isn’t configured, so it returns null and never exercises theexisting is not null && existing.Id == command.Idpath. Consider setting it up to returnexistingCity.src/Modules/Locations/Tests/Integration/AllowedCityRepositoryIntegrationTests.cs (2)
225-232: Remove redundantSaveChangesAsync()calls (repo already persists)
AllowedCityRepository.AddAsync/UpdateAsync/DeleteAsynccallSaveChangesAsyncinternally; the extra_context.SaveChangesAsync()in helpers/tests adds noise and can make failure points less clear. Based on learnings, you can drop the explicit saves inAddCityAndSaveAsync,UpdateCityAndSaveAsync, and the delete test.Also applies to: 358-368
302-320: Duplicate city/state test also duplicates IBGE, so it’s not isolating the constraint
If the goal is “duplicate (CityName, StateSigla)”, set the second city’sIbgeCodetonullor a different value so you’re only tripping the composite unique index.src/Bootstrapper/MeAjudaAi.ApiService/Program.cs (1)
47-49: Seeding call placement is sensible; consider passing a shutdown token.
If you have access toapp.Lifetime.ApplicationStopping, thread it through to allow graceful cancellation on shutdown.src/Shared/Seeding/SeedingExtensions.cs (1)
20-37: Environment gating is correct; consider logging when seeder isn’t registered.
Right now it silently no-ops ifIDevelopmentDataSeederisn’t registered; a Debug/Information log could reduce confusion.src/Shared/Seeding/DevelopmentDataSeeder.cs (2)
128-135: Propagate CancellationToken into the raw SQL inserts.
Right now the seeding work can’t be cancelled cleanly (app shutdown, ctrl+c, etc.).- await context.Database.ExecuteSqlRawAsync( + await context.Database.ExecuteSqlRawAsync( @"INSERT INTO service_catalogs.categories (id, name, description, created_at, updated_at) VALUES ({0}, {1}, {2}, {3}, {4}) ON CONFLICT (name) DO NOTHING", - cat.Id, cat.Name, cat.Description, DateTime.UtcNow, DateTime.UtcNow); + new object?[] { cat.Id, cat.Name, cat.Description, DateTime.UtcNow, DateTime.UtcNow }, + cancellationToken); ... - await context.Database.ExecuteSqlRawAsync( + await context.Database.ExecuteSqlRawAsync( @"INSERT INTO service_catalogs.services (id, name, description, category_id, eligibility_criteria, required_documents, created_at, updated_at, is_active) VALUES ({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, true) ON CONFLICT (name) DO NOTHING", - svc.Id, svc.Name, svc.Description, svc.CategoryId, svc.Criteria, svc.Documents, DateTime.UtcNow, DateTime.UtcNow); + new object?[] { svc.Id, svc.Name, svc.Description, svc.CategoryId, svc.Criteria, svc.Documents, DateTime.UtcNow, DateTime.UtcNow }, + cancellationToken); ... - await context.Database.ExecuteSqlRawAsync( + await context.Database.ExecuteSqlRawAsync( @"INSERT INTO locations.allowed_cities (id, ibge_code, city_name, state, is_active, created_at, updated_at) VALUES ({0}, {1}, {2}, {3}, true, {4}, {5}) ON CONFLICT (ibge_code) DO NOTHING", - city.Id, city.IbgeCode, city.CityName, city.State, DateTime.UtcNow, DateTime.UtcNow); + new object?[] { city.Id, city.IbgeCode, city.CityName, city.State, DateTime.UtcNow, DateTime.UtcNow }, + cancellationToken);Also applies to: 185-192, 222-229
234-258: Cache DbContext type lookup to avoid repeated assembly scans.
GetDbContextscans all loaded assemblies on every call; caching{moduleName -> Type?}would reduce startup overhead and log noise.src/Aspire/MeAjudaAi.AppHost/Extensions/MigrationExtensions.cs (1)
184-204: Consider modern using declaration.The current
using (context)pattern works correctly, but modern C# prefersusing var context = ...for cleaner scope management.Apply this diff to use modern syntax:
- using (context) + using var dbContext = context; + + // Aplicar migrations + var pendingMigrations = (await dbContext.Database.GetPendingMigrationsAsync(cancellationToken)).ToList(); + + if (pendingMigrations.Any()) { - // Aplicar migrations - var pendingMigrations = (await context.Database.GetPendingMigrationsAsync(cancellationToken)).ToList(); - - if (pendingMigrations.Any()) + _logger.LogInformation("📦 {Module}: {Count} migrations pendentes", moduleName, pendingMigrations.Count); + foreach (var migration in pendingMigrations) { - _logger.LogInformation("📦 {Module}: {Count} migrations pendentes", moduleName, pendingMigrations.Count); - foreach (var migration in pendingMigrations) - { - _logger.LogDebug(" - {Migration}", migration); - } - - await context.Database.MigrateAsync(cancellationToken); - _logger.LogInformation("✅ {Module}: Migrations aplicadas com sucesso", moduleName); - } - else - { - _logger.LogInformation("✓ {Module}: Nenhuma migration pendente", moduleName); + _logger.LogDebug(" - {Migration}", migration); } + + await dbContext.Database.MigrateAsync(cancellationToken); + _logger.LogInformation("✅ {Module}: Migrations aplicadas com sucesso", moduleName); } + else + { + _logger.LogInformation("✓ {Module}: Nenhuma migration pendente", moduleName); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (27)
.github/workflows/pr-validation.yml(2 hunks)api/README.md(1 hunks)docs/roadmap.md(7 hunks)scripts/seed-dev-data.ps1(1 hunks)src/Aspire/MeAjudaAi.AppHost/Extensions/MigrationExtensions.cs(1 hunks)src/Bootstrapper/MeAjudaAi.ApiService/Program.cs(2 hunks)src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru(1 hunks)src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/DeleteAllowedCity.bru(1 hunks)src/Modules/Locations/Infrastructure/Persistence/LocationsDbContextFactory.cs(1 hunks)src/Modules/Locations/Infrastructure/Repositories/AllowedCityRepository.cs(1 hunks)src/Modules/Locations/Tests/Integration/AllowedCityRepositoryIntegrationTests.cs(1 hunks)src/Modules/Locations/Tests/Unit/Application/Handlers/CreateAllowedCityHandlerTests.cs(1 hunks)src/Modules/Locations/Tests/Unit/Application/Handlers/DeleteAllowedCityHandlerTests.cs(1 hunks)src/Modules/Locations/Tests/Unit/Application/Handlers/UpdateAllowedCityHandlerTests.cs(1 hunks)src/Modules/Locations/Tests/Unit/Infrastructure/Services/IbgeServiceTests.cs(10 hunks)src/Modules/SearchProviders/Infrastructure/Persistence/Repositories/SearchableProviderRepository.cs(2 hunks)src/Modules/Users/API/Authorization/UsersPermissions.cs(0 hunks)src/Shared/Behaviors/CachingBehavior.cs(1 hunks)src/Shared/Caching/HybridCacheService.cs(1 hunks)src/Shared/Contracts/Modules/SearchProviders/ISearchProvidersModuleApi.cs(1 hunks)src/Shared/Logging/SerilogConfigurator.cs(4 hunks)src/Shared/Messaging/ServiceBus/ServiceBusMessageBus.cs(1 hunks)src/Shared/Monitoring/MetricsCollectorService.cs(3 hunks)src/Shared/Seeding/DevelopmentDataSeeder.cs(1 hunks)src/Shared/Seeding/IDevelopmentDataSeeder.cs(1 hunks)src/Shared/Seeding/SeedingExtensions.cs(1 hunks)tests/MeAjudaAi.Integration.Tests/packages.lock.json(4 hunks)
💤 Files with no reviewable changes (1)
- src/Modules/Users/API/Authorization/UsersPermissions.cs
🚧 Files skipped from review as they are similar to previous changes (5)
- src/Modules/Locations/Infrastructure/Persistence/LocationsDbContextFactory.cs
- src/Modules/Locations/Infrastructure/Repositories/AllowedCityRepository.cs
- scripts/seed-dev-data.ps1
- src/Shared/Behaviors/CachingBehavior.cs
- .github/workflows/pr-validation.yml
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-30T14:29:49.897Z
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 31
File: tests/MeAjudaAi.Integration.Tests/Modules/ServiceCatalogs/ServiceCategoryRepositoryIntegrationTests.cs:22-38
Timestamp: 2025-11-30T14:29:49.897Z
Learning: In the ServiceCatalogs module, ServiceCategoryRepository and ServiceRepository follow an auto-save pattern where AddAsync, UpdateAsync, and DeleteAsync methods internally call SaveChangesAsync. Integration tests for these repositories do not need explicit SaveChangesAsync calls after Add/Update operations.
Applied to files:
src/Modules/Locations/Tests/Integration/AllowedCityRepositoryIntegrationTests.cs
📚 Learning: 2025-11-25T01:05:52.410Z
Learnt from: frigini
Repo: frigini/MeAjudaAi PR: 29
File: tests/MeAjudaAi.Shared.Tests/Middleware/GeographicRestrictionMiddlewareTests.cs:178-178
Timestamp: 2025-11-25T01:05:52.410Z
Learning: In the MeAjudaAi codebase, the standard for comments is Portuguese (due to the Brazilian team), and English is only required for logs. Do not suggest changing Portuguese comments to English.
Applied to files:
src/Modules/Locations/Tests/Unit/Infrastructure/Services/IbgeServiceTests.cs
🧬 Code graph analysis (12)
src/Shared/Seeding/IDevelopmentDataSeeder.cs (1)
tests/MeAjudaAi.Shared.Tests/Extensions/TestCancellationExtensions.cs (1)
CancellationToken(13-13)
src/Modules/Locations/Tests/Unit/Application/Handlers/DeleteAllowedCityHandlerTests.cs (3)
src/Modules/Locations/Application/Handlers/DeleteAllowedCityHandler.cs (1)
DeleteAllowedCityHandler(11-22)src/Modules/Locations/Domain/Entities/AllowedCity.cs (3)
AllowedCity(7-136)AllowedCity(55-55)AllowedCity(57-88)src/Modules/Locations/Domain/Exceptions/AllowedCityNotFoundException.cs (1)
AllowedCityNotFoundException(6-10)
src/Shared/Seeding/SeedingExtensions.cs (3)
src/Shared/Seeding/DevelopmentDataSeeder.cs (8)
DevelopmentDataSeeder(10-259)DevelopmentDataSeeder(15-21)Task(23-35)Task(37-41)Task(43-88)Task(90-104)Task(106-195)Task(197-232)src/Bootstrapper/MeAjudaAi.ApiService/Program.cs (2)
Task(18-63)Task(100-119)src/Shared/Seeding/IDevelopmentDataSeeder.cs (3)
Task(11-11)Task(16-16)Task(21-21)
src/Shared/Seeding/DevelopmentDataSeeder.cs (3)
src/Shared/Seeding/IDevelopmentDataSeeder.cs (3)
Task(11-11)Task(16-16)Task(21-21)src/Shared/Seeding/SeedingExtensions.cs (1)
Task(20-37)src/Shared/Time/UuidGenerator.cs (1)
UuidGenerator(8-33)
src/Modules/Locations/Tests/Unit/Application/Handlers/UpdateAllowedCityHandlerTests.cs (5)
src/Modules/Locations/Application/Handlers/UpdateAllowedCityHandler.cs (1)
UpdateAllowedCityHandler(13-44)src/Modules/Locations/Infrastructure/Repositories/AllowedCityRepository.cs (9)
Task(13-21)Task(23-30)Task(32-36)Task(38-48)Task(50-61)Task(63-67)Task(69-73)Task(75-79)Task(81-91)src/Modules/Locations/Domain/Entities/AllowedCity.cs (3)
AllowedCity(7-136)AllowedCity(55-55)AllowedCity(57-88)src/Modules/Locations/Domain/Exceptions/AllowedCityNotFoundException.cs (1)
AllowedCityNotFoundException(6-10)src/Modules/Locations/Domain/Exceptions/DuplicateAllowedCityException.cs (1)
DuplicateAllowedCityException(6-11)
src/Modules/Locations/Tests/Unit/Application/Handlers/CreateAllowedCityHandlerTests.cs (3)
src/Modules/Locations/Application/Handlers/CreateAllowedCityHandler.cs (1)
CreateAllowedCityHandler(14-43)src/Modules/Locations/Domain/Entities/AllowedCity.cs (3)
AllowedCity(7-136)AllowedCity(55-55)AllowedCity(57-88)src/Modules/Locations/Domain/Exceptions/DuplicateAllowedCityException.cs (1)
DuplicateAllowedCityException(6-11)
src/Modules/Locations/Tests/Integration/AllowedCityRepositoryIntegrationTests.cs (2)
src/Modules/Locations/Infrastructure/Repositories/AllowedCityRepository.cs (1)
AllowedCityRepository(11-92)src/Modules/Locations/Domain/Entities/AllowedCity.cs (4)
AllowedCity(7-136)AllowedCity(55-55)AllowedCity(57-88)Update(90-115)
src/Shared/Monitoring/MetricsCollectorService.cs (2)
src/Shared/Monitoring/BusinessMetrics.cs (1)
UpdateActiveUsers(77-78)tests/MeAjudaAi.Shared.Tests/Extensions/TestCancellationExtensions.cs (1)
CancellationToken(13-13)
src/Shared/Logging/SerilogConfigurator.cs (2)
src/Shared/Logging/CorrelationIdEnricher.cs (1)
LoggerConfiguration(85-88)tests/MeAjudaAi.Integration.Tests/Messaging/DeadLetter/DeadLetterIntegrationTests.cs (1)
IConfiguration(276-302)
src/Aspire/MeAjudaAi.AppHost/Extensions/MigrationExtensions.cs (2)
src/Shared/Seeding/DevelopmentDataSeeder.cs (1)
DbContext(234-258)tests/MeAjudaAi.Architecture.Tests/ModuleApiArchitectureTests.cs (1)
Assembly(312-318)
src/Shared/Caching/HybridCacheService.cs (1)
src/Shared/Caching/CacheMetrics.cs (1)
RecordOperation(73-81)
src/Modules/Locations/Tests/Unit/Infrastructure/Services/IbgeServiceTests.cs (2)
src/Modules/Locations/Infrastructure/Services/IbgeService.cs (1)
IbgeService(16-122)src/Modules/Locations/Domain/Exceptions/MunicipioNotFoundException.cs (3)
MunicipioNotFoundException(8-28)MunicipioNotFoundException(13-19)MunicipioNotFoundException(21-27)
🪛 GitHub Check: build-and-test
src/Shared/Seeding/DevelopmentDataSeeder.cs
[failure] 95-95:
No overload for method 'SeedLocationsAsync' takes 1 arguments
[failure] 94-94:
No overload for method 'SeedServiceCatalogsAsync' takes 1 arguments
[failure] 95-95:
No overload for method 'SeedLocationsAsync' takes 1 arguments
[failure] 94-94:
No overload for method 'SeedServiceCatalogsAsync' takes 1 arguments
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Security Scan
- GitHub Check: Code Quality Checks
- GitHub Check: code-analysis
🔇 Additional comments (24)
src/Shared/Monitoring/MetricsCollectorService.cs (1)
25-25: Cancellation token propagation + deterministic placeholders look good.
Nice improvement passingstoppingTokenthrough and avoidingRandomin a metrics background loop.Also applies to: 43-48, 59-92
tests/MeAjudaAi.Integration.Tests/packages.lock.json (2)
192-195: Platform runtime identifier switch is consistent and well-aligned with PR objectives.The transition from
linux-x64towin-x64for Aspire.Dashboard.Sdk and Aspire.Hosting.Orchestration is intentional and reflects the shift to Windows-centric development. ContentHashes have been updated appropriately for the new platform-specific packages. The meajudaai.apphost project correctly references the updated win-x64 versions.Verify that this lock file is not used by Linux-based CI pipelines. If integration tests are expected to run on Linux agents, this change may require splitting the lock file or conditionally restoring platform-specific packages.
Also applies to: 376-379, 2618-2618, 2624-2624
2727-2727: New ServiceCatalogs.Application dependency is architecturally sound.The addition of
"MeAjudaAi.Modules.ServiceCatalogs.Application": "[1.0.0, )"to meajudaai.modules.providers.application aligns with the PR objective for Providers ↔ ServiceCatalogs integration. The version constraint follows the project's internal dependency convention and maintains consistency with the module graph.src/Shared/Contracts/Modules/SearchProviders/ISearchProvidersModuleApi.cs (1)
7-7: LGTM! Documentation consistency improvement.Spelling out "and" instead of using "&" is more appropriate for formal XML documentation.
src/Modules/SearchProviders/Infrastructure/Persistence/Repositories/SearchableProviderRepository.cs (1)
33-45: Documentation clarifications look good.The updated wording for the
HasConversiondescription and the mapping note improves readability by removing the angle-bracket notation that could be confusing in XML doc comments.api/README.md (1)
72-74: Bare URLs fixed — previous MD034 violations resolved.The bare URLs that were flagged in the prior review (lines 72–73) have been correctly replaced with proper markdown link syntax. The documentation for API automation and GitHub Pages deployment is now well-structured with clear workflow steps.
docs/roadmap.md (3)
1207-1207: Bare URL fixed — previous MD034 violation resolved.Line 1207's bare URL has been properly wrapped in markdown link syntax:
[GitHub Pages](https://frigini.github.io/MeAjudaAi/). Documentation is now markdownlint-compliant.
10-94: Excellent sprint segmentation and status tracking.The roadmap clearly reflects the actual Sprint 3 partitioning (Part 1: ✅ completed, Part 2: 🔄 in progress) and maintains consistent emoji markers throughout for visual clarity. Status updates align well with the PR objectives (Admin endpoints, Bruno collections, integrations, seeding, code quality improvements).
1200-1237: Detailed completion notes with commit traceability — excellent audit trail.Lines 1200–1237 provide granular breakdown of Sprint 3 Parte 2 & 4 completions, including commit references (e.g., d1ce745, e334c4d) and specific file changes. This level of detail is valuable for historical tracking and post-mortems.
src/Modules/Locations/Tests/Unit/Infrastructure/Services/IbgeServiceTests.cs (1)
4-4: LGTM! Repository integration properly implemented.The IAllowedCityRepository has been correctly added to the test setup with proper mocking, constructor instantiation, and verification in the first test case.
Also applies to: 16-16, 23-23, 31-31, 33-37, 52-54, 57-57, 62-62
src/Shared/Logging/SerilogConfigurator.cs (2)
22-50: LGTM! Refactored to in-place configuration pattern.The signature change from returning
LoggerConfigurationtovoidand accepting the logger config as a parameter is a valid refactoring that follows a common configuration pattern. The method now mutates the provided instance in place, which is consistent with how it's called fromAddStructuredLogging.
116-130: Remove the redundant Console sink configuration from code to prevent duplicate logging.The configuration in
src/Bootstrapper/MeAjudaAi.ApiService/appsettings.jsonalready defines a Console sink in theSerilog:WriteTosection (line 12-18). WhenConfigureSerilogcallsReadFrom.Configuration(), it loads this Console sink. Then lines 122-126 add a Console sink again in code, resulting in duplicate console output.Remove the Console sink from lines 122-126 and keep only the File sink (lines 128-130), or consolidate all sink configuration in
appsettings.jsonand remove the explicit sink additions from code.src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru (1)
7-15: Bruno spec is clear and route/Location look consistent
Auth + error examples are well documented.Also applies to: 53-76
src/Modules/Locations/Tests/Unit/Application/Handlers/CreateAllowedCityHandlerTests.cs (1)
46-145: Test coverage and exception expectations align with handler behavior
Duplicate/system/null-IBGE/inactive scenarios look consistent withCreateAllowedCityHandler.src/Modules/Locations/Tests/Unit/Application/Handlers/UpdateAllowedCityHandlerTests.cs (1)
27-181: Unit tests match handler semantics (not-found/duplicate/system updater)
Exception types and message matchers are aligned with the shown domain exceptions/handler logic.src/Modules/Locations/Tests/Unit/Application/Handlers/DeleteAllowedCityHandlerTests.cs (1)
1-75: LGTM — aligns with handler behavior and exception typessrc/Bootstrapper/MeAjudaAi.ApiService/Program.cs (1)
30-42: Module registration order looks correct (specific handlers before global).
This ordering should help ensure module-specific exception handlers win over the fallback global handler.src/Shared/Seeding/IDevelopmentDataSeeder.cs (1)
1-22: Clean, minimal contract for dev seeding.
CancellationToken defaults are consistent and will compose well with test cancellation utilities.src/Shared/Seeding/SeedingExtensions.cs (1)
11-15: DI registration is straightforward.
Scoped lifetime is a good fit given DbContext usage.src/Aspire/MeAjudaAi.AppHost/Extensions/MigrationExtensions.cs (5)
19-24: LGTM! Clean extension method for migration registration.The fluent API design is clean and the hosted service registration is straightforward.
43-71: LGTM! Good fail-fast behavior on migration errors.The orchestration logic is clean with appropriate early returns and error handling. Throwing on migration failure (line 69) ensures the app doesn't start with an inconsistent database state, which is the correct behavior for production.
89-121: Excellent improvement! Security concern from previous review fully addressed.The environment-aware approach is well-implemented:
- Development defaults are clearly documented and logged
- Production requires explicit configuration or fails safely
- Warning messages guide proper deployment configuration
This resolves the previous security concern about hardcoded credentials.
126-151: LGTM! DbContext discovery is consistent with codebase patterns.The assembly scanning approach matches similar patterns used elsewhere in the codebase (e.g.,
DevelopmentDataSeeder.cs,ModuleApiArchitectureTests.cs). The try-catch provides good resilience against assembly loading issues.
213-225: LGTM! Module name extraction handles edge cases well.The logic correctly extracts module names from the expected namespace structure (
MeAjudaAi.Modules.{ModuleName}.Infrastructure.Persistence) and provides a sensible fallback for non-standard namespaces.
src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/CreateAllowedCity.bru
Show resolved
Hide resolved
src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/DeleteAllowedCity.bru
Show resolved
Hide resolved
src/Modules/Locations/API/API.Client/AllowedCitiesAdmin/DeleteAllowedCity.bru
Show resolved
Hide resolved
src/Modules/Locations/Tests/Integration/AllowedCityRepositoryIntegrationTests.cs
Show resolved
Hide resolved
- Remover par\u00e2metros CancellationToken não utilizados de SeedServiceCatalogsAsync e SeedLocationsAsync - M\u00e9todos privados não precisam do token já que ExecuteSeedAsync não o repassa
- Corrigir testes do SerilogConfigurator para usar método void ConfigureSerilog - Adicionar Microsoft.EntityFrameworkCore e Npgsql.EntityFrameworkCore.PostgreSQL ao AppHost - Formatar código com dotnet format Erros corrigidos: - 10 erros CS7036/CS0815 em SerilogConfiguratorTests (assinatura de método void) - 3 erros CS1061 em MigrationExtensions (missing EF Core packages) Build status: ✅ 0 erros, compilação bem-sucedida
…consertar todos os usos - Mudado ICacheService.GetAsync<T> para retornar Task<(T? value, bool isCached)> - Atualizado HybridCacheService para detectar cache hits via factory pattern - Corrigido CachingBehavior para usar tuple deconstruction e não cachear nulls - Atualizado todos os testes de cache (HybridCacheServiceTests, CachingBehaviorTests) - Corrigido TestCacheService em Shared.Tests e Providers.Tests - Adicionado stable category IDs no DevelopmentDataSeeder para evitar FK failures - Corrigido DevelopmentDataSeeder para usar LINQ .AnyAsync() ao invés de ExecuteSqlRawAsync SELECT COUNT - Mudado GlobalExceptionHandler para retornar 500 para ArgumentException - Adicionado mock setups para IsCityAllowedAsync nos testes do IbgeService - Todos os testes de cache agora passam (13/13) - Testes do IbgeService agora passam (3/3) Refs: PR code review feedback
…ation issues Security & Privacy: - KeycloakPermissionResolver: Remove exception object from LogDebug to prevent PII exposure * HTTP request URL in exception contains raw userId * Now logs only masked userId and status code Exception Handling Improvements: - UsersModuleApi: Rethrow OperationCanceledException instead of wrapping * Preserves .NET cancellation conventions * Allows callers to properly catch OperationCanceledException * Matches pattern used in CanExecuteBasicOperationsAsync - RequestLoggingMiddleware: Remove InvalidOperationException wrapper * Preserves original exception type and stack trace * Prevents masking of exception types for upstream handlers * Logging still captures method and path context Documentation: - .editorconfig: Add detailed rationale for xUnit1051 suppression * Explains 755+ violations from xUnit v3 migration * Documents planned refactoring timeline * Clarifies test infrastructure stability requirements
…es and cancellation semantics ModuleApi Files (Documents, Providers, SearchProviders, ServiceCatalogs): - Rethrow OperationCanceledException instead of wrapping in InvalidOperationException - Preserves .NET cancellation conventions - Allows callers to properly catch and handle cancellation - Consistent with CanExecuteBasicOperationsAsync pattern UploadDocumentCommandHandler: - Remove InvalidOperationException wrapper for UnauthorizedAccessException and ArgumentException - Preserves exception types for GlobalExceptionHandler status code mapping - UnauthorizedAccessException → 401 Unauthorized (not 500) - ArgumentException → 400 Bad Request (not 500) - Logging still captures provider context before rethrowing - Generic Exception handler still wraps with context for unexpected errors
There was a problem hiding this comment.
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)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
168-214: Double token request on cache miss.When the admin token is not cached,
RequestAdminTokenAsyncis called twice:
- At line 179 via
CreateTokenCacheOptionsAsyncto determine TTL- At line 176 inside the
GetOrCreateAsyncfactoryThis doubles latency and network calls on every cache miss.
Consider refactoring to fetch the token once and derive both the value and TTL:
private async Task<string> GetAdminTokenAsync(CancellationToken cancellationToken) { var cacheKey = "keycloak_admin_token"; + const int safetyMarginSeconds = 30; + const int minimumTtlSeconds = 10; + const int defaultExpiresInSeconds = 300; return await _cache.GetOrCreateAsync( cacheKey, async _ => { var tokenResponse = await RequestAdminTokenAsync(cancellationToken); return tokenResponse.AccessToken; }, - await CreateTokenCacheOptionsAsync(cancellationToken), + new HybridCacheEntryOptions + { + // Use a reasonable default; token expiry is typically consistent + Expiration = TimeSpan.FromSeconds(defaultExpiresInSeconds - safetyMarginSeconds), + LocalCacheExpiration = TimeSpan.FromSeconds(120) + }, cancellationToken: cancellationToken); }Alternatively, cache the entire
TokenResponseand compute TTL fromExpiresInwithin a single factory call.
♻️ Duplicate comments (1)
.editorconfig (1)
1-273: File still violates its owninsert_final_newline = truerule (line 9).The file ends at line 273 without a trailing newline character. This violates the global rule declared on line 9. Add a newline at the end of the file to maintain compliance.
dotnet_diagnostic.CA2100.severity = none +
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
.editorconfig(1 hunks)src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs(1 hunks)src/Modules/Documents/Application/ModuleApi/DocumentsModuleApi.cs(1 hunks)src/Modules/Providers/Application/ModuleApi/ProvidersModuleApi.cs(1 hunks)src/Modules/SearchProviders/Application/ModuleApi/SearchProvidersModuleApi.cs(1 hunks)src/Modules/ServiceCatalogs/Application/ModuleApi/ServiceCatalogsModuleApi.cs(1 hunks)src/Modules/Users/Application/ModuleApi/UsersModuleApi.cs(1 hunks)src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/Modules/ServiceCatalogs/Application/ModuleApi/ServiceCatalogsModuleApi.cs
🧰 Additional context used
🧬 Code graph analysis (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (3)
src/Modules/Users/Infrastructure/Identity/Keycloak/KeycloakService.cs (1)
IEnumerable(381-445)src/Shared/Authorization/AuthorizationExtensions.cs (1)
IEnumerable(163-173)src/Shared/Authorization/PermissionExtensions.cs (3)
IEnumerable(63-71)IEnumerable(77-81)EPermission(42-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: code-analysis
- GitHub Check: Security Scan
- GitHub Check: build-and-test
- GitHub Check: Code Quality Checks
🔇 Additional comments (9)
src/Modules/SearchProviders/Application/ModuleApi/SearchProvidersModuleApi.cs (1)
64-68: Richer cancellation logging while preserving cancellation semantics (good change).
LoggingOperationCanceledExceptionwith the exception instance retains stack/context, and rethrowing keeps the expected cancellation behavior.src/Modules/Providers/Application/ModuleApi/ProvidersModuleApi.cs (1)
81-85: Cancellation is rethrown and now logged with full exception context (LGTM).
This keeps the correct behavior (don’t convert cancellation into “module unavailable”) while making cancellations diagnosable when needed..editorconfig (1)
159-166: ✓ xUnit1051 suppression is now well-documented.The rationale for the xUnit1051 suppression (lines 161–165) is now clearly explained, addressing the prior review concern. The documentation succinctly covers the migration context (755+ violations from xUnit v3), the planned refactoring timeline, technical stability considerations, and debt tracking—all appropriate justifications for a temporary suppression.
src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs (2)
131-140: Good switch tothrow;to preserve stack trace.Keeping
UnauthorizedAccessException/ArgumentExceptionas-is (and only logging) preserves the original exception type and stack for upstream HTTP mapping.
141-145: VerifyInvalidOperationExceptionmaps to the intended HTTP response (and aligns with tests/other handlers).Wrapping the unexpected path is reasonable, but please confirm your global exception middleware/controller layer translates
InvalidOperationExceptionto a 500 with the desired (non-leaky) payload, and that no tests/consumers expectArgumentException/UnauthorizedAccessExceptionto also be wrapped in this handler.src/Modules/Users/Application/ModuleApi/UsersModuleApi.cs (1)
74-78: LGTM! Previous issue resolved.The exception handling now correctly preserves the original
OperationCanceledExceptionusingthrow;, addressing the previous review comment. This maintains .NET cancellation conventions and consistency withCanExecuteBasicOperationsAsync(lines 116-118).src/Modules/Documents/Application/ModuleApi/DocumentsModuleApi.cs (1)
78-82: Logging enhancement is good, but consider consistency across cancellation handlers.The change to capture and log exception details improves observability in the availability check. However, this file has 9 other
OperationCanceledExceptionhandlers (lines 106–109, 130–133, 153–156, 203–206, 273–276, 325–328, 367–370, 394–397, 421–424) that silently rethrow without logging.Since
IsAvailableAsyncis a health check operation where cancellation diagnostics may be valuable, this inconsistency could be intentional. If other methods should similarly log cancellations for observability, apply the same pattern consistently. If only availability checks need logging, document why to prevent future confusion.src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (2)
265-268: LGTM - PII concern addressed.The change from logging the full exception object to only logging
ex.StatusCodeaddresses the previously flagged PII leak concern. The unmasked user ID in the exception message is no longer exposed in logs.
320-324: LGTM!Clean parameter rename that better reflects the domain. The null check and switch expression correctly reference the new parameter name.
…caching, and fix test expectations KeycloakPermissionResolver Security & Performance: - Remove exception objects from logging to prevent PII exposure * HTTP request URLs in exceptions contain raw userId * Now logs only masked userId and status code/error message - Optimize GetAdminTokenAsync to eliminate double token request on cache miss * Previously called RequestAdminTokenAsync twice: once in factory, once in CreateTokenCacheOptionsAsync * Now uses fixed expiration (270s) with reasonable defaults * Reduces latency and network calls by 50% on cache misses - Remove unused CreateTokenCacheOptionsAsync method Test Fixes - UploadDocumentCommandHandlerTests: - Update tests to expect unwrapped exceptions after handler changes * ArgumentException tests now expect ArgumentException directly (not wrapped) * UnauthorizedAccessException test expects UnauthorizedAccessException directly * Aligns with fix that preserves exception types for HTTP status code mapping - Tests affected: * HandleAsync_WithInvalidContentType_ShouldThrowArgumentException * HandleAsync_WithOversizedFile_ShouldThrowArgumentException * HandleAsync_WithInvalidDocumentType_ShouldThrowArgumentException * HandleAsync_WithUnauthorizedUser_ShouldThrowUnauthorizedAccessException
…ions - Test was expecting InvalidOperationException wrapper with context message - After removing wrapper to preserve original exception types, test now expects original exception - Verifies exception is re-thrown as-is (same instance) without modification - Aligns with fix that preserves exception types for upstream handlers
…handling Warnings Fixed: - Remove unused minimumTtlSeconds variable in KeycloakPermissionResolver * CS0219 warning eliminated AppHost Migrations: - Add early return for Testing/Test environments * Skips migrations when ASPNETCORE_ENVIRONMENT is Testing or Test * Test infrastructure manages its own database setup * Prevents InvalidOperationException in CI/CD when DB env vars not set * Development still allows missing connection string (warning only) GlobalExceptionHandler: - Fix ArgumentException mapping from 500 to 400 Bad Request * ArgumentException indicates validation/input errors (400) * Was incorrectly returning 500 Internal Server Error * Now returns proper status code with actual exception message * Fixes E2E test: CreateAllowedCity_WithInvalidData_ShouldReturnBadRequest * Aligns with REST API best practices
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
src/Aspire/MeAjudaAi.AppHost/Extensions/MigrationExtensions.cs (3)
101-157: Good: no hardcoded password fallback; dev defaults gated and password still required.
This addresses the prior “test123” risk and makes non-dev behavior fail fast.
201-238: High risk: dynamic module assembly loading won’t work unless module DLLs are present in AppHost output.
Directory.GetFiles(AppContext.BaseDirectory, "MeAjudaAi.Modules.*.Infrastructure.dll", AllDirectories)only helps if those DLLs are actually copied alongside AppHost. If AppHost doesn’t reference (or otherwise copy) the module infrastructure assemblies, discovery will always be empty. Please verify the build output / project refs.#!/bin/bash set -euo pipefail echo "=== Find AppHost csproj ===" fd -a -t f 'MeAjudaAi.AppHost.csproj' src || true APPHOST_CSPROJ="$(fd -a -t f 'MeAjudaAi.AppHost.csproj' src | head -n1 || true)" if [ -z "${APPHOST_CSPROJ}" ]; then echo "AppHost csproj not found." exit 1 fi echo "Using: ${APPHOST_CSPROJ}" echo echo "=== AppHost ProjectReference to module infrastructure? ===" rg -n "ProjectReference" "${APPHOST_CSPROJ}" || true echo echo "=== Any explicit references to MeAjudaAi.Modules.*.Infrastructure? ===" rg -n "MeAjudaAi\.Modules\..*\.Infrastructure" "${APPHOST_CSPROJ}" || true echo echo "=== Where do module Infrastructure csprojs live? (sanity) ===" fd -a -t f -g '*Infrastructure.csproj' src/Modules | head -n 50 || true echo echo "=== Verify runtime expectation in code (pattern + load call) ===" rg -n "MeAjudaAi\.Modules\.\*\.Infrastructure\.dll|LoadFromAssemblyPath|LoadModuleAssemblies" -S src/Aspire/MeAjudaAi.AppHost/Extensions/MigrationExtensions.cs
42-58: DB readiness window may still be too short; consider an explicit wait or longer retries.
You rely onEnableRetryOnFailure(maxRetryCount: 3)plus connection timeout, but there’s no explicit “wait for postgres to accept connections” before enumerating/migrating all modules. If the container takes longer than the retry window, startup fails.Also applies to: 265-273
🧹 Nitpick comments (6)
src/Aspire/MeAjudaAi.AppHost/Extensions/MigrationExtensions.cs (5)
20-26:WithMigrations<T>()is fine, but note it ignores the resource connection string.
Right now migrations rely entirely on environment variables (GetConnectionString()), not theIResourceWithConnectionStringinstance you’re extending. If Aspire ever changes the env-var shape (or if you have multiple Postgres resources), this can get brittle—consider wiring the resource connection string explicitly into the hosted service (e.g., via options) if feasible.
42-98: Testing-environment detection is likely incomplete (ASPNETCORE_ENVIRONMENT-only).
You only checkASPNETCORE_ENVIRONMENTforTesting/Test. If your repo usesDOTNET_ENVIRONMENTand/or anINTEGRATION_TESTSflag (as hinted by existing shared helpers), migrations may run unexpectedly in CI/integration tests. Consider centralizing on the same detection logic used elsewhere.
159-199: Potential silent no-op: returning 0 DbContexts can hide real packaging issues.
Ifassemblies.Count == 0, you log a warning and proceed (meaning “migrations succeeded” even though nothing happened). In non-Development environments you might want to treat “no module assemblies found” as a startup failure (or at least escalate severity) to avoid drifting schemas.
240-341: Two pitfalls:MigrationsAssemblyvalue + overly strict ctor matching.
npgsqlOptions.MigrationsAssembly(assemblyName)usesFullNamefirst; EF typically expects the simple assembly name (e.g.,GetName().Name). Consider preferringGetName().Nameto avoid resolution weirdness.GetConstructor(new[] { options.GetType() })requires an exact single-parameter match. If any module DbContext ctor adds extra deps (e.g., interceptors, options wrappers), migrations will fail even though the app might run fine under DI.Suggested tweak (safer assembly name + more flexible ctor match):
- var assemblyName = contextType.Assembly.FullName - ?? contextType.Assembly.GetName().Name - ?? contextType.Assembly.ToString(); + var assemblyName = contextType.Assembly.GetName().Name + ?? contextType.Assembly.FullName + ?? contextType.Assembly.ToString(); - var constructor = contextType.GetConstructor(new[] { options.GetType() }); + var optionsType = options.GetType(); + var constructor = contextType.GetConstructors() + .FirstOrDefault(ctor => + { + var ps = ctor.GetParameters(); + return ps.Length == 1 && ps[0].ParameterType.IsAssignableFrom(optionsType); + }); if (constructor == null) { throw new InvalidOperationException( $"No suitable constructor found for {contextType.Name} that accepts {options.GetType().Name}. " + "Ensure the DbContext has a constructor that accepts DbContextOptions."); }
343-355:ExtractModuleNamefallback is OK, but namespace parsing can mislabel non-standard contexts.
If any DbContext doesn’t followMeAjudaAi.Modules.<Module>.*, the fallbackName.Replace("DbContext","")may produce odd module names and confusing logs/errors. Consider logging the namespace when falling back.src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
190-190: Consider removing the removal comment.While helpful for review, comments documenting removed code are typically unnecessary in version-controlled projects since the change history is captured by git. This is a minor style preference.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/Aspire/MeAjudaAi.AppHost/Extensions/MigrationExtensions.cs(1 hunks)src/Modules/Documents/Tests/Unit/Application/UploadDocumentCommandHandlerTests.cs(1 hunks)src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs(5 hunks)src/Shared/Exceptions/GlobalExceptionHandler.cs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/Shared/Exceptions/GlobalExceptionHandler.cs
- src/Modules/Documents/Tests/Unit/Application/UploadDocumentCommandHandlerTests.cs
🧰 Additional context used
🧬 Code graph analysis (2)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (2)
src/Shared/Caching/HybridCacheService.cs (1)
HybridCacheEntryOptions(155-162)src/Modules/Users/Infrastructure/Identity/Keycloak/KeycloakService.cs (1)
IEnumerable(381-445)
src/Aspire/MeAjudaAi.AppHost/Extensions/MigrationExtensions.cs (3)
src/Aspire/MeAjudaAi.ServiceDefaults/Extensions.cs (1)
Extensions(20-238)src/Shared/Database/DapperConnection.cs (1)
GetConnectionString(13-24)src/Shared/Seeding/DevelopmentDataSeeder.cs (1)
DbContext(313-337)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Code Quality Checks
- GitHub Check: Security Scan
- GitHub Check: build-and-test
- GitHub Check: code-analysis
🔇 Additional comments (5)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (5)
151-154: Good fix: PII leak resolved.The exception object has been correctly removed from the log call, addressing the previous concern about exposing the unmasked user ID from the HTTP request URL. Logging only the StatusCode is safe and provides sufficient diagnostic information.
171-172: LGTM: Constants improve maintainability.The token caching constants are well-defined with reasonable values (5-minute default expiration with 30-second safety margin).
181-186: LGTM: Simplified token caching.The inline
HybridCacheEntryOptionsconfiguration is clearer and eliminates the need for a separate method. The expiration calculation (270 seconds = 4.5 minutes) with a 2-minute local cache window is appropriate for admin token caching.
241-244: Good fix: PII leak resolved.The exception object has been correctly removed from the log call, addressing the previous concern. Logging only the StatusCode is safe and the Debug level is appropriate for this fallback scenario.
296-300: LGTM: Parameter rename improves clarity.Renaming
roleNametokeycloakRolebetter reflects the parameter's purpose. The null check and switch expression are correctly updated.
…n test KeycloakPermissionResolver Security: - Remove ex.Message from logging to prevent PII leakage * Exception messages can contain raw userId in HTTP request URLs * Now logs only exception type (ex.GetType().Name) and masked user ID * Preserves exception details for debugging via InnerException * Follows principle: log categories/types, not sensitive data GlobalExceptionHandlerTests: - Update test expectation for ArgumentException * Changed from expecting 500 (Internal Server Error) * Now expects 400 (Bad Request) - correct HTTP semantics * Aligns with fix that maps ArgumentException to 400 * Test name updated: ShouldReturn500 → ShouldReturn400
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
168-188: Hard-coded cache expiry will serve expired tokens if Keycloak'sexpires_indiffers from 300 seconds.The code discards
TokenResponse.ExpiresInand uses a fixed 270-second cache expiration. If Keycloak returns a token with shorter lifetime (common for client credentials), the cache will serve expired tokens until eviction. Set expiration based ontokenResponse.ExpiresIn(with safety margin clamping), or choose a much more conservative fixed value.Also remove the leftover comment "Removed CreateTokenCacheOptionsAsync - no longer needed" at line 190; it describes deleted code and provides no guidance for current maintainers.
♻️ Duplicate comments (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
241-271: PII risk still present: logging exception object may include raw userId in URL.
Even though Line 243 is now PII-safe, the latercatch (Exception ex)logsexat Line 269; for HTTP failures, the exception/inner exception commonly contains the request URI (including the unmaskeduserId).- catch (Exception ex) + catch (Exception ex) { - _logger.LogWarning(ex, "Failed to find user {MaskedUserId} by username in Keycloak", MaskUserId(userId)); + _logger.LogWarning( + "Failed to find user {MaskedUserId} by username in Keycloak ({ExceptionType})", + MaskUserId(userId), + ex.GetType().Name); return null; }
🧹 Nitpick comments (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
296-301: Role normalization: consider trimming / avoiding per-callToLowerInvariant()allocation.
If Keycloak ever sends roles with casing differences (handled) or incidental whitespace (not handled), you can normalize viaTrim(); and aDictionary<string, EPermission[]>(StringComparer.OrdinalIgnoreCase)avoids allocating a new lowercase string each call.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs(5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Security Scan
- GitHub Check: Code Quality Checks
- GitHub Check: code-analysis
- GitHub Check: build-and-test
🔇 Additional comments (2)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (2)
151-155: Good: avoids logging exception details (PII-safe) in NotFound path.
This addresses the earlier risk of leaking raw user IDs viaHttpRequestExceptioncontent.
156-162: Good: logs only exception type + throws with masked context.
Keepingexonly as theInnerException(not logged) is a solid balance for debugging vs. PII exposure.
…n tests KeycloakPermissionResolver Security & Performance: - Use actual TokenResponse.ExpiresIn for cache expiration * Previous hard-coded 270s could serve expired tokens * Now calculates: expiresIn - safetyMargin (30s) with minimum 10s * Prevents cache serving expired tokens when Keycloak returns shorter lifetimes * LocalCache expiration: min(safeCacheSeconds/2, 120s) for distributed cache coherence - Remove leftover comment about deleted code - Remove exception object from username search logging to prevent PII leakage * Exception may contain raw userId in HTTP request URL * Now logs only exception type and masked user ID GlobalExceptionHandlerTests (ApiService): - Update tests for ArgumentException → 400 Bad Request behavior * TryHandleAsync_WithArgumentException_ShouldReturnBadRequest (was: ShouldReturnInternalServerError) * TryHandleAsync_WithArgumentNullException_ShouldReturnBadRequest (was: ShouldReturnInternalServerError) * ArgumentNullException inherits from ArgumentException, maps to 400 * Aligns with REST API semantics: validation errors = 400, not 500
There was a problem hiding this comment.
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)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
241-274: Consider disposing HttpResponseMessage instancesIn
GetUserInfoAsync,directResponse(Line 229) andsearchResponse(Line 253) aren’t disposed. Even though this is “just” admin API traffic, leaking responses can accumulate under load. Wrap them inusing var ... = await ...;.
♻️ Duplicate comments (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
80-117: Avoid leaking raw userId via cache keys and exception logging
cacheKey = $"keycloak_user_roles_{userId}"(Line 83) embeds raw user identifiers into cache infrastructure (often ends up in metrics/diagnostics). Prefer a stable hash (or HMAC) ofuserIdfor the cache key.- The catch logs
ex(Line 115). If the inner exception originated from an HTTP call,ex.Messagemay include the request URL with the unmaskeduserId(same class of issue discussed in prior reviews).@@ - var cacheKey = $"keycloak_user_roles_{userId}"; + var cacheKey = $"keycloak_user_roles_{HashForCacheKey(userId)}"; @@ - _logger.LogError(ex, "Failed to resolve permissions from Keycloak for user {MaskedUserId}", MaskUserId(userId)); + _logger.LogError("Failed to resolve permissions from Keycloak for user {MaskedUserId} ({ExceptionType})", + MaskUserId(userId), ex.GetType().Name); return Array.Empty<EPermission>(); } + + private static string HashForCacheKey(string input) + { + using var sha = SHA256.Create(); + var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(input)); + return Convert.ToHexString(bytes); + }
🧹 Nitpick comments (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
299-304: Mapping rename looks fine; consider rejecting whitespace-only roles
ArgumentNullException.ThrowIfNull(keycloakRole);(Line 301) still allows""/whitespace; if that’s invalid input, switch toArgumentException.ThrowIfNullOrWhiteSpace(keycloakRole);.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (3)
src/Shared/Caching/HybridCacheService.cs (1)
HybridCacheEntryOptions(155-162)src/Shared/Authorization/Middleware/PermissionOptimizationMiddleware.cs (2)
TimeSpan(317-326)IEnumerable(295-303)src/Shared/Authorization/AuthorizationExtensions.cs (1)
IEnumerable(163-173)
🪛 GitHub Check: build-and-test
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs
[failure] 181-181:
The type arguments for method 'HybridCache.GetOrCreateAsync<TState, T>(string, TState, Func<TState, CancellationToken, ValueTask>, HybridCacheEntryOptions?, IEnumerable?, CancellationToken)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
[failure] 181-181:
The type arguments for method 'HybridCache.GetOrCreateAsync<TState, T>(string, TState, Func<TState, CancellationToken, ValueTask>, HybridCacheEntryOptions?, IEnumerable?, CancellationToken)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
🪛 GitHub Check: Code Quality Checks
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs
[failure] 181-181:
The type arguments for method 'HybridCache.GetOrCreateAsync<TState, T>(string, TState, Func<TState, CancellationToken, ValueTask>, HybridCacheEntryOptions?, IEnumerable?, CancellationToken)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
[failure] 181-181:
The type arguments for method 'HybridCache.GetOrCreateAsync<TState, T>(string, TState, Func<TState, CancellationToken, ValueTask>, HybridCacheEntryOptions?, IEnumerable?, CancellationToken)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Security Scan
- GitHub Check: code-analysis
🔇 Additional comments (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
151-162: Error wrapping + NotFound logging looks goodGood move to (1) avoid logging the exception object on 404 and (2) rethrow a wrapped
InvalidOperationExceptionwhile logging only{ExceptionType}(Lines 153, 158-161).
… PII leakage - Fix CS0411 compilation error in GetAdminTokenAsync - Move RequestAdminTokenAsync call inside cache factory - Use correct async lambda returning string (implicitly ValueTask) - Use conservative fixed expiration (270s) instead of dynamic - Hash userId in cache keys using SHA256 to prevent PII exposure - Remove exception object from logging to prevent PII in URLs - Dispose HttpResponseMessage instances to prevent resource leaks - Strengthen role validation using ArgumentException.ThrowIfNullOrWhiteSpace
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs(10 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
src/Shared/Caching/HybridCacheService.cs (1)
HybridCacheEntryOptions(155-162)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Code Quality Checks
- GitHub Check: Security Scan
- GitHub Check: code-analysis
- GitHub Check: build-and-test
🔇 Additional comments (8)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (8)
82-83: LGTM: Cache key hashing prevents PII exposure.Hashing the user ID before using it in the cache key is a solid defense-in-depth measure that prevents PII from appearing in cache infrastructure logs or metrics.
115-116: LGTM: Exception type logging avoids PII leakage.Logging only the exception type instead of the full exception object addresses the previous PII concern while retaining useful diagnostic information.
154-154: LGTM: Status code logging without exception object.This addresses the previous PII concern by logging the HTTP status code without including the exception object that might contain the unmasked user ID in its message.
159-162: LGTM: Exception handling avoids PII in logs.Logging the exception type instead of the message prevents PII leakage (e.g., user IDs embedded in HTTP URLs), while the wrapped
InvalidOperationExceptionproperly masks the user ID in its message.
227-227: LGTM: Explicit resource management for HTTP responses.Adding
usingstatements for HTTP responses ensures timely disposal and prevents potential resource leaks.Also applies to: 251-251
241-241: LGTM: User lookup logging prevents PII exposure.Both the ID lookup fallback (line 241) and username search failure (lines 267-270) now log diagnostics without including exception objects that could contain unmasked user IDs in HTTP URLs.
Also applies to: 267-270
297-301: LGTM: Parameter rename and validation.Renaming the parameter to
keycloakRolebetter reflects its source, and theArgumentException.ThrowIfNullOrWhiteSpaceguard is a good defensive practice.
372-379: LGTM: SHA256 hashing for cache keys.SHA256 is appropriate for this use case—deterministic for cache lookups and one-way to prevent PII reconstruction from cache keys.
… PII leakage - Fix CS0411 compilation error in GetAdminTokenAsync - Create local async Task<string> helper GetAccessTokenAsync - Wrap in ValueTask constructor to match HybridCache signature - Use conservative fixed expiration (270s) instead of dynamic - Hash userId in cache keys using SHA256 to prevent PII exposure - Remove exception object from logging to prevent PII in URLs - Dispose HttpResponseMessage instances to prevent resource leaks - Strengthen role validation using ArgumentException.ThrowIfNullOrWhiteSpace
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
169-186: Consider simplifying the factory lambda.The current approach (local async method + ValueTask wrapper) works correctly and fixes the caching bug from past reviews. However, the code could be more concise using an async ValueTask lambda:
- async Task<string> GetAccessTokenAsync(CancellationToken ct) => (await RequestAdminTokenAsync(ct)).AccessToken; - return await _cache.GetOrCreateAsync( cacheKey, - (ct) => new ValueTask<string>(GetAccessTokenAsync(ct)), + async ValueTask<string> (ct) => + { + var tokenResponse = await RequestAdminTokenAsync(ct); + return tokenResponse.AccessToken; + }, new HybridCacheEntryOptions { Expiration = TimeSpan.FromSeconds(defaultExpiresInSeconds - safetyMarginSeconds), LocalCacheExpiration = TimeSpan.FromSeconds(120) }, cancellationToken: cancellationToken);Note on hardcoded expiration: Using a fixed 270-second TTL (300s - 30s safety margin) instead of
TokenResponse.ExpiresInis a conservative trade-off. SinceHybridCacheEntryOptionsmust be set before the factory runs, using the actual token lifetime would require a different caching pattern. The current approach is safe and reasonable.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs(10 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
src/Modules/Users/Infrastructure/Identity/Keycloak/KeycloakService.cs (1)
IEnumerable(381-445)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Security Scan
- GitHub Check: Code Quality Checks
- GitHub Check: build-and-test
- GitHub Check: code-analysis
🔇 Additional comments (7)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (7)
82-83: Excellent: PII-safe cache keys.Hashing the user ID before using it in cache keys prevents PII from leaking into cache infrastructure logs and monitoring systems.
115-116: Good: PII-safe error logging.Logging only the exception type name instead of the full exception object prevents potential PII leaks from HTTP URLs in exception messages.
152-163: Improved error handling with PII protection.The updated logging avoids exposing raw user IDs or HTTP URLs from exceptions. Using HTTP status codes and exception type names provides sufficient diagnostic information without PII risk.
237-240: Good: Consistent PII-safe logging pattern.Following the same pattern as other error handlers - logs HTTP status code without exposing the full exception object.
265-268: Consistent PII-safe logging.The fallback username search path now follows the same safe logging pattern established throughout the file.
295-299: Improved parameter naming and validation.Renaming
roleNametokeycloakRolebetter conveys the source of the role, and addingArgumentException.ThrowIfNullOrWhiteSpacestrengthens input validation.
370-377: Solid utility for PII protection.The
HashForCacheKeymethod provides a consistent way to hash sensitive identifiers before using them in cache keys. SHA256 is appropriate for this non-cryptographic use case.
Use async ValueTask<string> lambda directly instead of local helper method More concise and clearer intent
There was a problem hiding this comment.
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)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)
82-96: FixGetOrCreateAsyncfactory signature—returnValueTask<T>and use the factoryCancellationTokenparameter (roles cache).The roles cache factory currently uses
async _ => await GetUserRolesFromKeycloakAsync(...), which returnsTask<IReadOnlyList<string>>instead of the requiredValueTask<IReadOnlyList<string>>, and ignores the factory'sCancellationTokenparameter in favor of the closure variable. This is inconsistent with the admin token cache implementation in the same file (which correctly usesasync ValueTask<string> (ct) => ...) and breaks the HybridCache contract.var userRoles = await _cache.GetOrCreateAsync( cacheKey, - async _ => await GetUserRolesFromKeycloakAsync(userId, cancellationToken), + async ValueTask<IReadOnlyList<string>> (ct) => await GetUserRolesFromKeycloakAsync(userId, ct), cacheOptions, cancellationToken: cancellationToken);
🧹 Nitpick comments (2)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (2)
154-163: Good: NotFound path avoids logging exception body; keep improving diagnostics with safe fields.
Consider also loggingex.StatusCode(already done in NotFound) for the generic error path whenex is HttpRequestException hre, without logginghre.Message/ full exception object (URL may contain userId).
372-379:HashForCacheKeyshould defensively reject null/empty input (tiny hardening).
Even though current callers guard, making the helper self-contained avoids future footguns.private static string HashForCacheKey(string input) { + ArgumentException.ThrowIfNullOrWhiteSpace(input); var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(input)); return Convert.ToHexString(bytes); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs(10 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (3)
src/Shared/Caching/HybridCacheService.cs (1)
HybridCacheEntryOptions(155-162)src/Modules/Users/Infrastructure/Identity/Keycloak/KeycloakService.cs (1)
IEnumerable(381-445)src/Shared/Authorization/PermissionExtensions.cs (3)
IEnumerable(63-71)IEnumerable(77-81)EPermission(42-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: build-and-test
- GitHub Check: Code Quality Checks
- GitHub Check: code-analysis
- GitHub Check: Security Scan
🔇 Additional comments (2)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (2)
115-117: Logging change avoids PII via exception/URL leakage (good).
Returning empty permissions on error is “fail closed” for authZ, so it’s a safe default.
297-301: No action required. The method signature in the interface matches the implementation exactly (string keycloakRolein both), and all call sites within the codebase are compatible with the current signature.
…natures - Use tokenResponse.ExpiresIn for dynamic cache TTL instead of hardcoded 300s - Fix roles cache factory to use ValueTask and factory's CancellationToken - Add defensive validation to HashForCacheKey helper - Improve error logging with HTTP status code without exposing PII - Translate comments to Portuguese (project standard)
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs(11 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (3)
src/Shared/Caching/HybridCacheService.cs (1)
HybridCacheEntryOptions(155-162)src/Modules/Users/Infrastructure/Identity/Keycloak/KeycloakService.cs (1)
IEnumerable(381-445)src/Shared/Authorization/PermissionExtensions.cs (3)
IEnumerable(63-71)IEnumerable(77-81)EPermission(42-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: code-analysis
- GitHub Check: build-and-test
- GitHub Check: Security Scan
- GitHub Check: Code Quality Checks
🔇 Additional comments (7)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (7)
82-83: Good security improvement: hashing user IDs in cache keys.Hashing the user ID before using it in the cache key prevents PII exposure in cache infrastructure (logs, metrics, distributed cache keys). This addresses the PII concerns from previous reviews.
93-93: LGTM: ValueTask signature now matches HybridCache requirements.The factory delegate signature is now correct, resolving the build issue flagged in previous reviews.
115-117: LGTM: Enhanced error logging without PII exposure.Extracting the status code and exception type instead of logging the full exception prevents PII leakage while maintaining diagnostic value.
153-164: LGTM: Error handling correctly avoids PII leakage.Lines 155 and 160 now log only the exception type instead of the full exception or
ex.Message, which could contain unmasked user IDs from HTTP request URLs. This addresses the PII concerns from previous reviews.
234-234: LGTM: Proper resource disposal and enhanced diagnostics.Good improvements across multiple areas:
- Lines 234, 258:
usingblocks ensureHttpResponseMessageobjects are properly disposed- Lines 248, 274-277: Enhanced logging with HTTP status codes and exception types provides better diagnostics without exposing PII
Also applies to: 248-248, 258-258, 274-277
304-308: LGTM: Improved parameter naming and validation.The changes improve code clarity:
- More descriptive parameter name (
keycloakRolevsroleName)- Explicit null/empty validation
- Case-insensitive matching with
ToLowerInvariant()
379-387: LGTM: Clean hashing implementation for cache keys.The implementation correctly hashes user IDs for PII-safe cache keys. SHA256 is appropriate here (though SHA1 or even a non-cryptographic hash like SHA1 would also work and be faster, SHA256 is a safe, widely-available choice).
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs
Outdated
Show resolved
Hide resolved
Fetching token before GetOrCreateAsync defeats caching optimization: - Cache hit: fetches token unnecessarily, discards it - Cache miss: fetches token twice (before + inside factory) Use conservative 4-minute expiration (5min token - 1min margin) instead of dynamic calculation. This avoids the circular dependency where HybridCacheEntryOptions needs expiration before factory runs, but expiration value comes from token fetched inside factory.
Pull Request: Sprint 3 Parte 2 - Admin Endpoints e Integrações
📊 Summary
Este PR implementa as tarefas do Sprint 3 Parte 2 conforme roadmap, incluindo CRUD admin completo para AllowedCities, automações de OpenAPI e seeding, migrações para Aspire, e integração entre módulos Providers e ServiceCatalogs.
🎯 Objetivos do Sprint 3 Parte 2
✅ Tarefas Completadas
AllowedCities Admin CRUD (Locations Module)
Automação OpenAPI (Task 8)
Sistema de Seeding (Tasks 6-7)
Migração Aspire (MigrationTool → Aspire)
Integração Providers ↔ ServiceCatalogs (Sprint 3 Parte 3)
Code Quality (Sprint 3 Parte 4)
🔧 Changes
Novos Arquivos (principais)
Automação:
Seeding C#:
Aspire:
AllowedCities (Locations Module):
Providers ↔ ServiceCatalogs:
Arquivos Removidos
Arquivos Modificados
📈 Impact
Estatísticas
Cobertura
🧪 Testing
Executados Localmente
Pipeline (Aguardando)
📋 Checklist
🔗 References
📝 Notes for Reviewers
🚀 Breaking Changes
Nenhuma. Todas as mudanças são aditivas ou refactorings internos.
Ready for review! ✅
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.