Skip to content

Refactor/fix blazor references#121

Merged
frigini merged 70 commits intomasterfrom
refactor/fix-blazor-references
Feb 5, 2026
Merged

Refactor/fix blazor references#121
frigini merged 70 commits intomasterfrom
refactor/fix-blazor-references

Conversation

@frigini
Copy link
Owner

@frigini frigini commented Jan 24, 2026

Summary by CodeRabbit

  • New Features

    • Geolocation for allowed cities (latitude/longitude/service radius), city search/autocomplete, PATCH for partial city updates, and OIDC/Keycloak diagnostics in the admin UI.
  • Improvements

    • Permission-based admin controls, in-grid editing for allowed cities, richer service metadata (description & display order), unified API result envelope, and safer/escaped search queries.
  • Bug Fixes

    • More consistent response shapes/status codes and updated Portuguese user-facing messages.
  • Documentation

    • New Admin Portal docs (overview, dashboard, features, architecture).
  • Tests

    • Expanded unit/integration/e2e coverage for permissions, locations, providers and response handling.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 24, 2026

📝 Walkthrough

Walkthrough

Adds geo fields and service-radius to AllowedCity (DB, DTOs, migration); integrates geocoding/Nominatim search and PATCH/search endpoints; converts location handlers to Result-based flows; standardizes Result response envelopes; centralizes Keycloak role/permission mapping and adds dev auth debug middleware; many endpoints shifted to permission-based auth.

Changes

Cohort / File(s) Summary
Locations — schema & domain
src/Modules/Locations/Domain/Entities/AllowedCity.cs, src/Modules/Locations/Infrastructure/Persistence/Migrations/...AddGeoFieldsToAllowedCity.*
Add Latitude, Longitude, ServiceRadiusKm; extend constructors and Update/UpdateRadius signatures; introduce InvalidLocationArgumentException; add EF migration.
Locations — commands/handlers/patch/search
src/Modules/Locations/Application/Commands/*, src/Modules/Locations/Application/Handlers/*, src/Modules/Locations/API/Endpoints/*
Create/Update commands now return Result; handlers depend on IGeocodingService and ILogger; add PatchAllowedCityCommand/Handler and SearchLocationsEndpoint; endpoints updated to use Result envelopes.
Locations — DTOs & mappers
src/Modules/Locations/Application/DTOs/*, src/Modules/Locations/API/Mappers/*, src/Contracts/Contracts/Modules/Locations/DTOs/*
Add DTO properties (Latitude, Longitude, ServiceRadiusKm), new PatchAllowedCityRequestDto and LocationCandidate, ResponseMapperExtensions with ToContract mappings.
Geocoding — client & service
src/Modules/Locations/Infrastructure/ExternalApis/NominatimClient.cs, src/Modules/Locations/Infrastructure/Services/GeocodingService.cs, src/Modules/Locations/Application/Services/IGeocodingService.cs
Add Nominatim search with dedupe and state→sigla mapping; GeocodingService.SearchAsync with caching; IGeocodingService exposes SearchAsync.
Endpoints / response envelope refactor
src/Shared/Endpoints/EndpointExtensions.cs, various src/*/API/Endpoints/*, tests/**
Endpoint helpers now return full Result/Result envelopes across success/error/no-content/created paths; CreateErrorResponse returns Result failure shape; tests updated to unwrap value/data.
Authorization & Keycloak
src/Shared/Authorization/Core/EPermission.cs, src/Shared/Authorization/Keycloak/*, src/Shared/Authorization/AuthorizationExtensions.cs, src/Shared/Utilities/Constants/RoleConstants.cs
Add ServiceCatalogs/Locations permissions; central RoleConstants; extend role→permission mapping; register KeycloakPermissionProvider/IPermissionProvider and wire configuration-based resolver.
Auth debug middleware & security
src/Bootstrapper/.../Middleware/InspectAuthMiddleware.cs, src/Bootstrapper/.../Extensions/SecurityExtensions.cs, src/Shared/Utilities/Constants/AuthConstants.cs
Add InspectAuthMiddleware (dev-only) emitting debug headers; JWT auth failure now emits sanitized debug header in non-prod; AddAuthorizationPolicies now accepts IConfiguration and registers Keycloak resolver/provider.
Admin UI & UX changes
src/Web/MeAjudaAi.Web.Admin/**, docs
Add OidcDebugService and startup diagnostics; AllowedCities moved to in-grid radius editing and autocomplete-based create dialog; Fluxor actions/effects added for patch/update flows; UI strings/localization updates.
Providers & ServiceCatalogs updates
src/Modules/Providers/**, src/Modules/ServiceCatalogs/**
Replace literal messages with centralized ValidationMessages constants; add DisplayOrder to DTOs and mappings; many endpoints switched to permission-based authorization; provider queries use AsSplitQuery and LIKE escaping.
Utilities & masking
src/Shared/Utilities/PiiMaskingHelper.cs, src/Shared/Utilities/Constants/RoleConstants.cs
Add PiiMaskingHelper.MaskUserId; centralize Keycloak role constant values.
Bootstrap & Keycloak infra
src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ConfigurationEndpoints.cs, src/Bootstrapper/.../ServiceCollectionExtensions.cs, infrastructure/keycloak/*, src/Web/MeAjudaAi.Web.Admin/wwwroot/index.html
Authority derivation supports BaseUrl+Realm fallback; AddAuthorizationPolicies now requires IConfiguration; register InspectAuthMiddleware in Development; update Keycloak realm JSON and add reimport script; remove inline CSP meta.
Tests & test infra
tests/**, tests/MeAjudaAi.E2E.Tests/Base/ResponseTypes.cs, tests/*/Base/BaseApiTest.cs
Introduce ApiResult/ApiError test types and GetResponseData helper; update many unit/integration/E2E tests for Result/value envelope shapes; add many new unit tests for handlers, mappers, middleware, Nominatim client, and patch handler.
Misc & error handling
src/Shared/Exceptions/GlobalExceptionHandler.cs, src/Modules/Documents/*, src/Shared/Serialization/*, src/Modules/Providers/Infrastructure/*
Broaden exception unwrapping to more wrapper types; standardize cancellation handling; consolidate catches; seal inner enum converter; add EscapeLikePattern helper and AsSplitQuery in EF queries.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Endpoint as Locations API Endpoint
    participant Handler as CreateAllowedCityHandler
    participant Geo as GeocodingService
    participant Repo as AllowedCityRepository

    Client->>Endpoint: POST /admin/allowed-cities {city,state,lat?,lon?,radius?}
    Endpoint->>Handler: Send CreateAllowedCityCommand
    Handler->>Repo: ExistsAsync(city,state)?
    alt exists
        Repo-->>Handler: true
        Handler-->>Endpoint: Result.Failure (Conflict)
        Endpoint-->>Client: 409 Conflict (Result failure)
    else
        Handler->>Geo: GetCoordinatesAsync(city,state) [if coords missing]
        Geo-->>Handler: GeoPoint or null
        Handler->>Repo: AddAsync(AllowedCity with coords,radius)
        Repo-->>Handler: created id
        Handler-->>Endpoint: Result.Success(id)
        Endpoint-->>Client: 200 OK (Result success)
    end
Loading
sequenceDiagram
    participant Client as Client
    participant API as API
    participant Inspect as InspectAuthMiddleware
    participant Auth as JWT/AuthSubsystem
    participant Logger as Logger

    Client->>API: HTTP request (with Authorization)
    API->>Inspect: InvokeAsync (dev-only)
    Inspect->>Auth: read claims, roles, permissions
    Auth-->>Inspect: claims data
    Inspect->>API: register OnStarting to add debug headers
    API->>Logger: optional diagnostic logs
    API-->>Client: Response + debug headers (X-Debug-User, X-Debug-Roles, X-Debug-Permissions...)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 I hopped through contracts, geo and Keycloak vines,

Result envelopes snug within tidy lines,
Debug headers peep in development's light,
Radius and maps now guided true and right,
A rabbit cheers — the orchard takes to flight.

🚥 Pre-merge checks | ❌ 3
❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is entirely missing (no description body was provided by the author), leaving reviewers without context about the changes, objectives, testing, or rationale for the extensive modifications across multiple modules. Provide a detailed PR description following the template, covering summary, problem statement, solution, list of changes, impact, and testing evidence; include links to related issues.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'Refactor/fix blazor references' is vague and does not clearly convey the primary changes, which encompass extensive backend refactoring including authorization, locations, providers, service catalogs, users, and documentation—far beyond just Blazor references. Clarify the title to accurately reflect the scope; consider a more comprehensive title such as 'Refactor authorization, add locations features, and improve test coverage' or break into multiple focused PRs.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/fix-blazor-references

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (3)
src/Modules/Providers/Application/Handlers/Queries/GetProvidersByCityQueryHandler.cs (1)

35-38: Localize the failure message to Portuguese (user-facing response).

The failure string is returned to the caller and should be in Portuguese per the language policy. Please translate it (e.g., “Ocorreu um erro ao recuperar os prestadores”).

src/Modules/Providers/Application/Handlers/Queries/GetProvidersByVerificationStatusQueryHandler.cs (1)

28-28: Localize user-facing error message to Portuguese.

Result.Failure returns a user-facing string; per repo language policy it should be Portuguese. Suggested update below. Based on learnings, logs stay English, user-facing messages should be Portuguese.

🔧 Proposed fix
-            return Result<IReadOnlyList<ProviderDto>>.Failure("An error occurred while retrieving providers");
+            return Result<IReadOnlyList<ProviderDto>>.Failure("Ocorreu um erro ao recuperar os prestadores");
src/Modules/ServiceCatalogs/API/Endpoints/Service/GetServicesByCategoryEndpoint.cs (1)

18-34: Add permission requirement for consistency with other read endpoints.

GetServicesByCategoryEndpoint lacks the ServiceCatalogsRead permission requirement that GetAllServicesEndpoint has. While GetServiceByIdEndpoint also lacks this permission, the unfiltered list endpoint (GetAllServicesEndpoint) does require it. Consider whether GetServicesByCategoryEndpoint should also require ServiceCatalogsRead for consistent permission enforcement across read operations, or if the difference is intentional based on your authorization design.

🤖 Fix all issues with AI agents
In `@src/Bootstrapper/MeAjudaAi.ApiService/Extensions/SecurityExtensions.cs`:
- Line 310: The code currently appends raw exception messages to a response
header (context.Response.Headers.Append(...)) which leaks sensitive info and
uses a hardcoded header name; add a constant in AuthConstants.Headers (e.g.,
public const string DebugAuthFailure = "X-Debug-Auth-Failure") and change the
header emission in SecurityExtensions (where context.Response.Headers.Append is
called) to only run in non-production environments (use IHostEnvironment or
env.IsDevelopment()/IsStaging() as appropriate) and avoid including the full
exception text — include a short sanitized message or exception type instead
(e.g., ex.GetType().Name or a truncated/sanitized message) when adding the
header, and use the new AuthConstants.Headers.DebugAuthFailure constant for the
header name.

In `@src/Modules/Users/Application/Handlers/Commands/DeleteUserCommandHandler.cs`:
- Around line 7-8: Remove the duplicate ValidationMessages import by deleting
the using directive for MeAjudaAi.Shared.Utilities.Constants and keeping only
MeAjudaAi.Contracts.Utilities.Constants in DeleteUserCommandHandler.cs so the
ValidationMessages symbol is unambiguous; ensure the file retains the using
MeAjudaAi.Contracts.Utilities.Constants line and compile to confirm no other
references to the Shared namespace remain.

In
`@src/Modules/Users/Application/Handlers/Queries/GetUserByUsernameQueryHandler.cs`:
- Around line 6-7: Remove the redundant using directive for
MeAjudaAi.Shared.Utilities.Constants at the top of GetUserByUsernameQueryHandler
(the file/class GetUserByUsernameQueryHandler.cs); keep the existing using
MeAjudaAi.Contracts.Utilities.Constants for ValidationMessages and ensure no
other symbols from the Shared namespace are referenced—simply delete the unused
"using MeAjudaAi.Shared.Utilities.Constants;" line.

In
`@src/Modules/Users/Tests/Integration/GetUserByUsernameQueryIntegrationTests.cs`:
- Around line 8-9: Remove the redundant using directive referencing
MeAjudaAi.Shared.Utilities.Constants in GetUserByUsernameQueryIntegrationTests
(the import on line with "using MeAjudaAi.Shared.Utilities.Constants"); keep the
existing using MeAjudaAi.Contracts.Utilities.Constants which defines
ValidationMessages, so delete the unused Shared import and run the build/tests
to ensure no other references to that namespace remain.

In
`@src/Modules/Users/Tests/Unit/Application/Queries/GetUserByUsernameQueryHandlerTests.cs`:
- Around line 7-8: The file imports the same ValidationMessages namespace from
two places which is redundant and can cause ambiguity; remove the duplicate by
deleting the using for MeAjudaAi.Shared.Utilities.Constants and keep only using
MeAjudaAi.Contracts.Utilities.Constants so references to ValidationMessages
resolve unambiguously.

In `@src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs`:
- Around line 370-382: Add unit tests in KeycloakPermissionResolverTests.cs to
assert that the new roles map to the correct permissions: create tests that call
KeycloakPermissionResolver.Resolve (or the relevant mapping method) with
"meajudaai-catalog-manager" and assert the returned permissions include
EPermission.ServiceCatalogsRead and EPermission.ServiceCatalogsManage, and
another test for "meajudaai-location-manager" asserting
EPermission.LocationsRead and EPermission.LocationsManage; also update the test
fixture/setup that seeds Keycloak roles (or the realm configuration used by
tests) to include these two roles so the resolver tests run against the expected
role set.

In `@src/Web/MeAjudaAi.Web.Admin/App.razor`:
- Around line 85-106: The log messages in DiagnoseOidcConfiguration are in
Portuguese; update all Logger calls in that method (Logger.LogInformation,
Logger.LogWarning, Logger.LogError) to English strings while preserving the same
semantics and structured parameters (e.g., keep "{Error}" placeholder and
include exception 'ex' in LogError). Specifically, translate the startup info,
the "detected problems" warning and the per-error warning entries, and the error
message in the catch block; leave method flow,
OidcDebugService.CheckKeycloakHealthAsync usage, and
healthCheck.GetDiagnosticSummary() unchanged.

In `@src/Web/MeAjudaAi.Web.Admin/Authorization/PolicyNames.cs`:
- Around line 32-36: Update the XML doc for the LocationsManagerPolicy constant
to reflect that the policy requires a role, not a permission: change the comment
text to indicate it "Requer role 'locations-manager'" (or similar phrasing
consistent with other policy docs) so the documentation for
PolicyNames.LocationsManagerPolicy matches the actual policy setup that calls
RequireRole("locations-manager") in Program.cs.

In `@src/Web/MeAjudaAi.Web.Admin/Services/OidcDebugService.cs`:
- Around line 6-9: This file contains mojibake in XML comments—save the source
file as UTF-8 and update the garbled Portuguese text in the OidcDebugService XML
docs and other commented strings (look for class/summary comments and comments
near methods referenced in this file such as OidcDebugService and any public
methods/properties) to proper Portuguese (e.g. replace "Servi�o para
diagnosticar problemas com OIDC e Keycloak. Fornece informa��es detalhadas sobre
a configura��o de autentica��o." with "Serviço para diagnosticar problemas com
OIDC e Keycloak. Fornece informações detalhadas sobre a configuração de
autenticação."). Ensure all occurrences at the regions flagged (around the
summary block and the other comment blocks) are corrected and encoded as UTF-8
so the accents render properly.
- Around line 45-114: Update all logger messages in OidcDebugService (calls to
_logger.LogInformation, _logger.LogDebug, _logger.LogWarning, _logger.LogError)
to English and remove emoji/malformed glyphs; e.g., replace "?? Iniciando
diagnóstico de Keycloak" with "Starting Keycloak diagnostics", "   Well-Known
Error" messages and "?? Timeout..." with plain English equivalents, and change
final messages ("? Diagnóstico de Keycloak concluído", " ? Erro durante
diagnóstico de Keycloak") to "Keycloak diagnostics completed" and "Error during
Keycloak diagnostics". Keep existing structured logging placeholders and
variables (wellKnownUrl, tokenUrl, authUrl, _clientConfig.Keycloak.*,
_navigationManager.Uri, result) intact and do not change exception logging or
the result assignments.
- Around line 141-177: Translate all user-facing strings in GetDiagnosticSummary
to Portuguese (pt-BR) and replace the corrupted "????????..." border with a
clean UTF-8 box-drawing or simple ASCII border; update the list entries that
reference properties (Timestamp, Authority, ClientId, Scopes,
PostLogoutRedirectUri, WellKnownEndpointStatus, TokenEndpointUrl,
AuthorizationEndpointUrl, CurrentUrl, AppOrigin, IsHealthy, Errors) to
Portuguese labels (e.g., "Relatório de Diagnóstico Keycloak", "Data/Hora",
"Configuração", "Endpoints", "Informações do Cliente", "Status", "Erros") and
format the healthy/issue status text accordingly (e.g., "Saudável" / "Problemas
detectados"); ensure the final string join and error enumeration
(Errors.Select(...)) remain intact and that the new border characters are valid
UTF-8 or plain ASCII so the admin UI displays correctly.
🧹 Nitpick comments (19)
src/Modules/Providers/Application/Handlers/Queries/GetProviderByUserIdQueryHandler.cs (1)

44-44: Consider localizing the error message to Portuguese.

If the Result.Failure message is surfaced to API consumers or the frontend, it should be in Portuguese per the established language policy. Consider changing "Error getting provider" to a Portuguese equivalent such as "Erro ao buscar prestador".

Based on learnings, user-facing API responses should be in Portuguese.

src/Bootstrapper/MeAjudaAi.ApiService/Endpoints/ConfigurationEndpoints.cs (1)

44-57: Harden Keycloak authority normalization/validation.
If Keycloak:Authority is provided with trailing slashes or Keycloak:BaseUrl is whitespace, the resulting authority can be malformed. Consider trimming/validating consistently.

♻️ Suggested hardening
-        var keycloakAuthority = configuration["Keycloak:Authority"];
+        var keycloakAuthority = configuration["Keycloak:Authority"]?.TrimEnd('/');
         
         if (string.IsNullOrWhiteSpace(keycloakAuthority))
         {
             // Construir Authority a partir de BaseUrl e Realm
-            var keycloakBaseUrl = configuration["Keycloak:BaseUrl"] 
-                ?? throw new InvalidOperationException("Keycloak:BaseUrl ou Keycloak:Authority deve estar configurado");
+            var keycloakBaseUrl = configuration["Keycloak:BaseUrl"];
+            if (string.IsNullOrWhiteSpace(keycloakBaseUrl))
+                throw new InvalidOperationException("Keycloak:BaseUrl ou Keycloak:Authority deve estar configurado");
+            keycloakBaseUrl = keycloakBaseUrl.TrimEnd('/');
             
-            var keycloakRealm = configuration["Keycloak:Realm"] 
-                ?? "meajudaai"; // Valor padrão
+            var keycloakRealm = configuration["Keycloak:Realm"];
+            if (string.IsNullOrWhiteSpace(keycloakRealm))
+                keycloakRealm = "meajudaai"; // Valor padrão
+            keycloakRealm = keycloakRealm.Trim('/');
 
-            keycloakAuthority = $"{keycloakBaseUrl.TrimEnd('/')}/realms/{keycloakRealm}";
+            keycloakAuthority = $"{keycloakBaseUrl}/realms/{keycloakRealm}";
         }
src/Modules/ServiceCatalogs/Application/Mappings/ServiceCatalogsMappingExtensions.cs (1)

3-4: Prefer a single constants namespace here.

With both MeAjudaAi.Shared.Utilities.Constants and MeAjudaAi.Contracts.Utilities.Constants in scope, ValidationMessages can become ambiguous or resolve to legacy values. If everything moved to Contracts, drop the Shared import (or fully qualify). Based on learnings, keep enums/constants aligned with Contracts.

♻️ Suggested cleanup
-using MeAjudaAi.Shared.Utilities.Constants;
using MeAjudaAi.Contracts.Utilities.Constants;
src/Modules/Users/Tests/Unit/Application/Services/UsersModuleApiTests.cs (1)

6-8: Prefer Contracts constants only to avoid ambiguity.

If ValidationMessages exists in both namespaces, references may become ambiguous or bind to legacy constants. Consider removing the Shared import once Contracts is the source of truth. Based on learnings, keep constants in Contracts.

♻️ Suggested cleanup
-using MeAjudaAi.Shared.Utilities.Constants;
using MeAjudaAi.Contracts.Utilities.Constants;
src/Modules/Users/Application/Validators/UpdateUserProfileRequestValidator.cs (1)

3-5: Prefer a single constants namespace.

Having both Shared and Contracts constants in scope can cause ambiguous or legacy resolution for ValidationMessages/ValidationConstants. If Contracts is the target, drop the Shared import (or fully qualify). Based on learnings, prefer Contracts for constants.

♻️ Suggested cleanup
-using MeAjudaAi.Shared.Utilities.Constants;
using MeAjudaAi.Contracts.Utilities.Constants;
src/Modules/ServiceCatalogs/Tests/Unit/Application/Mappings/ServiceCatalogsMappingExtensionsTests.cs (1)

5-6: Prefer Contracts constants only.

If ValidationMessages exists in both namespaces, the reference can become ambiguous or resolve to legacy values. Consider removing the Shared import once Contracts is authoritative. Based on learnings, keep constants aligned with Contracts.

♻️ Suggested cleanup
-using MeAjudaAi.Shared.Utilities.Constants;
using MeAjudaAi.Contracts.Utilities.Constants;
src/Modules/Users/Application/Validators/CreateUserCommandValidator.cs (1)

3-5: Prefer a single constants namespace.

Keeping both Shared and Contracts constants in scope can cause ambiguous or legacy resolution of ValidationMessages/ValidationConstants. If Contracts is the target, drop the Shared import (or fully qualify). Based on learnings, prefer Contracts.

♻️ Suggested cleanup
-using MeAjudaAi.Shared.Utilities.Constants;
using MeAjudaAi.Contracts.Utilities.Constants;
src/Modules/Users/Tests/Unit/Application/Validators/UpdateUserProfileRequestValidatorTests.cs (1)

4-6: Prefer Contracts constants only to avoid ambiguity.

If ValidationMessages exists in both namespaces, references may become ambiguous or bind to legacy values. Consider removing the Shared import once Contracts is the source of truth. Based on learnings, prefer Contracts constants.

♻️ Suggested cleanup
-using MeAjudaAi.Shared.Utilities.Constants;
using MeAjudaAi.Contracts.Utilities.Constants;
src/Web/MeAjudaAi.Web.Admin/Services/OidcDebugService.cs (1)

51-56: Prefer injected HttpClient (or factory) over per-call instantiation.
In Blazor, repeated new HttpClient can lead to suboptimal resource usage. Consider DI-injecting HttpClient (or IHttpClientFactory on server) and reuse it.

♻️ Suggested DI refactor
 public class OidcDebugService
 {
     private readonly ClientConfiguration _clientConfig;
     private readonly ILogger<OidcDebugService> _logger;
     private readonly NavigationManager _navigationManager;
+    private readonly HttpClient _httpClient;
@@
     public OidcDebugService(
         ClientConfiguration clientConfig,
         ILogger<OidcDebugService> logger,
-        NavigationManager navigationManager)
+        NavigationManager navigationManager,
+        HttpClient httpClient)
     {
         _clientConfig = clientConfig;
         _logger = logger;
         _navigationManager = navigationManager;
+        _httpClient = httpClient;
     }
@@
-            using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
+            _httpClient.Timeout = TimeSpan.FromSeconds(10);
@@
-                var response = await client.GetAsync(wellKnownUrl);
+                var response = await _httpClient.GetAsync(wellKnownUrl);
src/Modules/ServiceCatalogs/Tests/Unit/Application/ModuleApi/ServiceCatalogsModuleApiTests.cs (1)

7-9: Prefer Contracts constants only to avoid ambiguity.
With both namespaces imported, ValidationMessages can become ambiguous if it exists in both. Please remove the Shared constants import and rely on Contracts. Based on learnings, prioritize Contracts constants.

♻️ Suggested cleanup
-using MeAjudaAi.Shared.Utilities.Constants;
 using MeAjudaAi.Contracts.Utilities.Constants;
src/Modules/ServiceCatalogs/Application/ModuleApi/ServiceCatalogsModuleApi.cs (1)

5-7: Make ValidationMessages source explicit (prefer Contracts).
If ValidationMessages exists in both namespaces, this becomes ambiguous; even if not, keeping both imports obscures intent. Recommend removing the Shared import or aliasing Contracts explicitly. Based on learnings, prefer Contracts constants.

♻️ Suggested explicit alias
-using MeAjudaAi.Shared.Utilities.Constants;
-using MeAjudaAi.Contracts.Utilities.Constants;
+using ValidationMessages = MeAjudaAi.Contracts.Utilities.Constants.ValidationMessages;
src/Modules/Locations/API/Endpoints/LocationsAdmin/GetAllAllowedCitiesEndpoint.cs (1)

11-12: Missing blank line between using directive and namespace declaration.

The using directive on line 11 lacks a blank line separator before the namespace declaration on line 12, which differs from the file's existing style (lines 1-10 have proper spacing).

Suggested fix
 using MeAjudaAi.Contracts.Models;
+
 namespace MeAjudaAi.Modules.Locations.API.Endpoints.LocationsAdmin;
src/Modules/Locations/API/Endpoints/LocationsAdmin/CreateAllowedCityEndpoint.cs (1)

12-13: Missing blank line between using directive and namespace declaration.

Same formatting inconsistency as in GetAllAllowedCitiesEndpoint.cs. Add a blank line for consistency.

Suggested fix
 using MeAjudaAi.Contracts.Models;
+
 namespace MeAjudaAi.Modules.Locations.API.Endpoints.LocationsAdmin;
src/Modules/Locations/API/Endpoints/LocationsAdmin/GetAllowedCityByIdEndpoint.cs (1)

11-12: Missing blank line between using directive and namespace declaration.

Consistent with other endpoint files in this PR - add a blank line separator.

Suggested fix
 using MeAjudaAi.Contracts.Models;
+
 namespace MeAjudaAi.Modules.Locations.API.Endpoints.LocationsAdmin;
src/Web/MeAjudaAi.Web.Admin/Program.cs (3)

131-131: Remove commented-out code or add a TODO explaining why it's kept.

The commented line // builder.Services.AddPermissionBasedAuthorization(builder.Configuration); should either be removed if obsolete or accompanied by a TODO/FIXME explaining when it will be re-enabled.


154-157: Create RoleNames.LocationsManager constant and use it for consistency.

The LocationsManagerPolicy uses a hardcoded "locations-manager" string while all other role-based policies use RoleNames constants. Define public const string LocationsManager = "locations-manager"; in the RoleNames class and replace the hardcoded string in line 157.

Note: The same issue exists in CatalogManagerPolicy (line 149), which also uses a hardcoded string despite RoleNames.CatalogManager already being defined.


148-149: Replace hardcoded string with RoleNames.CatalogManager constant for consistency.

Line 149 uses a hardcoded "catalog-manager" string while other policies use RoleNames.* constants (RoleNames.Admin, RoleNames.ProviderManager, RoleNames.DocumentReviewer). The RoleNames.CatalogManager constant exists and should be used here.

Change:

policy.RequireRole("catalog-manager", RoleNames.Admin)

to:

policy.RequireRole(RoleNames.CatalogManager, RoleNames.Admin)
src/Modules/Locations/API/Endpoints/LocationsModuleEndpoints.cs (1)

38-41: Stale comment references removed authorization method.

Line 39 comment still mentions "RequireAdmin" but the code now uses RequirePermission. Update the comment to reflect the new permission-based authorization.

📝 Suggested comment update
         // Usa o sistema unificado de versionamento via BaseEndpoint
-        // RequireAdmin aplicado no grupo garante que todos endpoints são protegidos por padrão
+        // RequirePermission aplicado no grupo garante que todos endpoints são protegidos por padrão
         var endpoints = BaseEndpoint.CreateVersionedGroup(app, "admin/allowed-cities", "Allowed Cities")
             .RequirePermission(EPermission.LocationsManage);
src/Web/MeAjudaAi.Web.Admin/App.razor (1)

77-79: Consider limiting OIDC diagnostics to development environment.

Running OIDC health checks on every app initialization adds startup latency in production. Consider gating this behind an environment check or configuration flag.

♻️ Suggested conditional execution

Inject IWebAssemblyHostEnvironment and conditionally run diagnostics:

 `@inject` IJSRuntime JSRuntime
 `@inject` OidcDebugService OidcDebugService
+@inject IWebAssemblyHostEnvironment HostEnvironment
 `@implements` IDisposable
         // Realizar diagnóstico de OIDC/Keycloak
-        await DiagnoseOidcConfiguration();
+        if (HostEnvironment.IsDevelopment())
+        {
+            await DiagnoseOidcConfiguration();
+        }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/Bootstrapper/MeAjudaAi.ApiService/Extensions/SecurityExtensions.cs`:
- Line 321: The file references AuthConstants in SecurityExtensions.cs (used in
the context.Response.Headers.Append call) but is missing its namespace import;
add the using directive for MeAjudaAi.Shared.Utilities.Constants to the file's
using/import section so AuthConstants resolves correctly.

In
`@src/Bootstrapper/MeAjudaAi.ApiService/Extensions/ServiceCollectionExtensions.cs`:
- Around line 168-170: The InspectAuthMiddleware is currently added
unconditionally via app.UseMiddleware<InspectAuthMiddleware>(), which can leak
sensitive auth headers; change ServiceCollectionExtensions to gate the
middleware registration behind environment or configuration by checking
IHostEnvironment (e.g., env.IsDevelopment() or !env.IsProduction()) or a config
flag (e.g., IConfiguration.GetValue<bool>("EnableInspectAuthMiddleware")) and
only call app.UseMiddleware<InspectAuthMiddleware>() when the check passes;
update the method that contains the app.UseMiddleware call to accept or resolve
the environment/config and add the conditional guard around the
InspectAuthMiddleware registration.

In `@src/Bootstrapper/MeAjudaAi.ApiService/Middleware/InspectAuthMiddleware.cs`:
- Around line 21-38: No trecho do método InvokeAsync em InspectAuthMiddleware.cs
existem comentários em inglês; converta todos esses comentários inline para
Português mantendo o mesmo sentido e contexto (por exemplo: "Wait,
AuthenticationMiddleware runs before this..." -> equivalente em Português),
garantindo que as observações sobre ordem de execução, uso de OnStarting e
restrições de adicionar headers permaneçam claras; edite os comentários dentro
do método InvokeAsync e quaisquer comentários relacionados a _next, HttpContext
e fluxo de autenticação/autorização para Português sem alterar a lógica do
código.
🧹 Nitpick comments (6)
src/Modules/Providers/Application/Handlers/Queries/GetProvidersByStateQueryHandler.cs (1)

31-35: Correct localization, but inconsistent with line 23.

The Portuguese error message aligns with the language policy (user-facing API responses in Portuguese). However, line 23 still returns an English message "State parameter is required". Consider updating it for consistency.

Based on learnings, user-facing validation messages and API responses should be in Portuguese.

♻️ Suggested fix for consistency
 if (string.IsNullOrWhiteSpace(query.State))
 {
     logger.LogWarning("Invalid state parameter: state cannot be null or empty");
-    return Result<IReadOnlyList<ProviderDto>>.Failure("State parameter is required");
+    return Result<IReadOnlyList<ProviderDto>>.Failure("O parâmetro de estado é obrigatório");
 }
src/Modules/Users/Application/Validators/UpdateUserProfileRequestValidator.cs (1)

43-46: Optional: Consider translating inline comments to Portuguese.

Per the codebase language policy, inline comments should be written in Portuguese while logs remain in English. These comments are pre-existing, so this is not blocking, but you may want to address it in a follow-up for consistency.

Based on learnings about the language policy.

src/Web/MeAjudaAi.Web.Admin/Program.cs (2)

131-131: Consider removing commented-out code.

Commented-out code adds noise and can become stale. If AddPermissionBasedAuthorization is no longer needed, remove this line entirely. If it's temporarily disabled for debugging or a phased migration, add a TODO comment explaining when it should be restored or removed.

♻️ Suggested fix
-// builder.Services.AddPermissionBasedAuthorization(builder.Configuration);
 // Autorização com políticas baseadas em roles

154-158: Policy registration is correct; consider reordering for consistency.

The LocationsManagerPolicy is correctly implemented requiring either "locations-manager" or "admin" roles, matching the pattern of other manager policies.

Minor nit: Other manager policies (Provider, Document, Catalog) are grouped before ViewerPolicy. Consider moving LocationsManagerPolicy before ViewerPolicy (lines 151-153) for consistency.

♻️ Optional reorder for consistency
     // Política de Gerente de Catálogo - requer "catalog-manager" ou "admin"
     options.AddPolicy(PolicyNames.CatalogManagerPolicy, policy =>
         policy.RequireRole(RoleNames.CatalogManager, RoleNames.Admin));

+    // Política de Gerente de Localidades - requer "locations-manager" ou "admin"
+    options.AddPolicy(PolicyNames.LocationsManagerPolicy, policy =>
+        policy.RequireRole(RoleNames.LocationsManager, RoleNames.Admin));
+
     // Política de Visualizador - qualquer usuário autenticado
     options.AddPolicy(PolicyNames.ViewerPolicy, policy =>
         policy.RequireAuthenticatedUser());
-
-    // Política de Gerente de Localidades - requer "locations-manager" ou "admin"
-    options.AddPolicy(PolicyNames.LocationsManagerPolicy, policy =>
-        policy.RequireRole(RoleNames.LocationsManager, RoleNames.Admin));
 });
src/Modules/Providers/Application/Handlers/Queries/GetProviderByIdQueryHandler.cs (1)

43-46: Localize all user-facing errors consistently (and centralize messages).

The new Portuguese failure text aligns with the language policy, but the earlier Error.NotFound("Provider not found") remains English. Consider moving these to shared validation/error message constants (e.g., in shared contracts) to keep localization consistent across handlers. Based on learnings, ...

src/Bootstrapper/MeAjudaAi.ApiService/Extensions/SecurityExtensions.cs (1)

310-323: Comments should be in Portuguese per codebase language policy.

Based on learnings, all in-code comments should be written in Portuguese while log messages remain in English. The comments on lines 312 and 316 are in English.

♻️ Proposed fix
-                        // Only emit debug header in non-production environments
+                        // Emite header de debug apenas em ambientes não-produção
                         if (!env.IsProduction())
                         {
                             var sanitizedMessage = $"{context.Exception.GetType().Name}: {context.Exception.Message.Length} chars";
-                            // Or better: truncated message to avoid leaking too much internal state, but helpful for debugging
+                            // Mensagem completa apenas em desenvolvimento para facilitar debugging
                             if (env.IsDevelopment())

Filipe Frigini added 2 commits January 24, 2026 12:54
…cation, CORS policy, and configuration validation, alongside a new authentication inspection middleware and a query handler for providers.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Bootstrapper/MeAjudaAi.ApiService/Middleware/InspectAuthMiddleware.cs`:
- Around line 45-79: InspectAuthMiddleware's InvokeAsync currently writes
sensitive auth info into response headers unconditionally; restrict this to
non-production environments by injecting or retrieving the hosting environment
(IWebHostEnvironment or IHostEnvironment) inside
InspectAuthMiddleware/InvokeAsync and gate the context.Response.OnStarting
registration behind an environment check (e.g., only when !env.IsProduction()).
Ensure both the authenticated and unauthenticated branches check the environment
before adding headers so X-Debug-* headers are never emitted in production.
♻️ Duplicate comments (1)
src/Bootstrapper/MeAjudaAi.ApiService/Middleware/InspectAuthMiddleware.cs (1)

22-44: Translate inline comments to Portuguese per repo language policy.

Multiple English comments exist throughout the file (lines 25-30, 35-39, 43-44). Per the codebase language policy, comments and XML documentation should be in Portuguese while logs remain in English. Based on learnings, keep comments in Portuguese.

🧹 Nitpick comments (2)
src/Bootstrapper/MeAjudaAi.ApiService/Middleware/InspectAuthMiddleware.cs (2)

11-41: Remove or complete dead code: DebugAuthMiddleware is unused.

This class has injected dependencies (_logger) that are never used and the InvokeAsync method only calls _next(context) without performing any actual work. The extensive comments suggest this was exploratory code that was superseded by InspectAuthMiddleware.

Consider removing this class entirely to avoid confusion and dead code in the codebase.

♻️ Proposed removal
-/// <summary>
-/// Middleware temporário para diagnóstico de problemas de autorização.
-/// Adiciona headers com roles e permissões do usuário atual.
-/// </summary>
-public class DebugAuthMiddleware
-{
-    private readonly RequestDelegate _next;
-    private readonly ILogger<DebugAuthMiddleware> _logger;
-
-    public DebugAuthMiddleware(RequestDelegate next, ILogger<DebugAuthMiddleware> logger)
-    {
-        _next = next;
-        _logger = logger;
-    }
-
-    public async Task InvokeAsync(HttpContext context)
-    {
-        // Executa o pipeline primeiro (para garantir que autenticação aconteceu)
-        // Wait, AuthenticationMiddleware runs before this if we register it correctly.
-        // We want to run logic AFTER Authentication.
-        
-        // Pass to next middleware first? No, we want to set headers BEFORE response starts.
-        // But Identity is set BY AuthenticationMiddleware which must run BEFORE this.
-        // So: Authentication -> DebugAuthMiddleware -> Authorization -> Endpoint
-        
-        await _next(context);
-        
-        // Headers só podem ser adicionados se a resposta não começou
-        // Mas se a autorização falhar (403), o response já pode ter começado?
-        // Authorization middleware writes 403.
-        // So we need to use OnStarting.
-        
-        // BETTER: Logic inside Invoke, before Next, accessing User.
-    }
-}
-
-// Simplified approach: Middleware that inspects context.User immediately.
-// Assumption: registered after UseAuthentication.

55-75: Consider centralizing debug header names in AuthConstants.Headers.

For consistency with the pattern used in SecurityExtensions.cs (which references AuthConstants.Headers.DebugAuthFailure), consider adding these debug header names as constants in AuthConstants.Headers. This aligns with the learnings preference for reusing Shared.Contracts for enums/constants.

// In AuthConstants.Headers:
public const string DebugUser = "X-Debug-User";
public const string DebugRoles = "X-Debug-Roles";
public const string DebugPermissionsCount = "X-Debug-Permissions-Count";
public const string DebugPermissions = "X-Debug-Permissions";
public const string DebugAuthStatus = "X-Debug-Auth-Status";

…onProvider

- Register KeycloakPermissionResolver in SecurityExtensions.AddAuthorizationPolicies
- Add IConfiguration parameter to AddAuthorizationPolicies method
- Update Keycloak realm roles to match expected role names in KeycloakPermissionResolver
- Assign all admin roles to admin.portal user in realm configuration
- Fix unit tests to pass IConfiguration parameter
- Add reimport-realm scripts (.ps1 and .sh) for easy realm reconfiguration
- Restrict InspectAuthMiddleware debug headers to non-production environments
- Centralize debug header constants in AuthConstants.Headers
- Translate comments to Portuguese per repo language policy

This fixes the issue where users were getting 403 Forbidden errors because the KeycloakPermissionProvider was not being registered in the DI container, resulting in zero permissions being resolved.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (1)
infrastructure/keycloak/realms/meajudaai-realm.dev.json (1)

222-252: Avoid committing default user passwords.
Line 224–251 embeds known passwords in the realm export. Even with temporary: true, this is risky if reused or accidentally promoted. Prefer generating passwords at import time or setting them via a post-import step (from env/secret store), leaving credentials out of the committed JSON.

🤖 Fix all issues with AI agents
In `@infrastructure/keycloak/realms/meajudaai-realm.dev.json`:
- Around line 184-199: The exported realm contains a hardcoded confidential
client secret for the clientId "meajudaai-api-service" (the "MeAjudaAi API
Service Account") — remove the "secret": "meajudaai-api-secret-dev-2024" entry
from the client JSON so no secret is committed; instead generate or set the
client secret at import/runtime via Keycloak admin API or CI secrets. Update the
client block in the realm JSON to omit the "secret" field (leave
serviceAccountsEnabled and protocol unchanged) and document in deployment notes
to provision the secret securely (via admin-cli post-import or environment/CI)
referencing the clientId "meajudaai-api-service".
- Around line 39-88: Create a centralized set of role constants (e.g., a static
class RoleConstants or update RoleNames) that defines every meajudaai-* role
string present in the Keycloak JSON and replace all hardcoded string literals in
KeycloakPermissionResolver (method(s) around the role checks), UserRoles,
RoleNames, and Program authorization policy registrations to reference these
constants; remove legacy role definitions and any policy/usage referencing
"provider-manager" and "document-reviewer" from UserRoles, RoleNames, and
Program so code and Keycloak realm roles are consistent.

In `@infrastructure/keycloak/reimport-realm.ps1`:
- Around line 69-70: The script uses Get-Content to read $REALM_FILE into
$realmContent without verifying the file exists; add a Test-Path check for the
variable REALM_FILE before calling Get-Content and if it does not exist emit a
clear error and exit the script (so that the code path using $realmContent in
this script stops cleanly). Locate the Get-Content usage that assigns
$realmContent and wrap it with a guard using Test-Path $REALM_FILE, calling
Write-Error/Write-Host (or equivalent logger) with a descriptive message and
terminating the script (e.g., exit) when the file is missing.
- Around line 55-61: The catch block handling deletion errors currently logs
non-404 failures and continues; change it to fail fast by emitting an error and
terminating the script: in the catch for the deletion call (where
$_.Exception.Response.StatusCode is checked) replace the Write-Host for non-404
with a Write-Error (including the detailed exception message and status) and
then call exit 1 (or rethrow the exception) so the script stops instead of
proceeding to an import that will likely fail; update the catch around the
delete realm logic to perform this fail-fast behavior.
- Around line 81-88: The script currently prints secrets and passwords via
multiple Write-Host calls (the block printing "Client Secret" and user
passwords); remove those Write-Host lines or guard them behind an explicit
opt-in boolean parameter (e.g. a new $ShowSecrets / -ShowSecrets switch that
defaults to false) and only emit the secret/password output when that parameter
is true; update any call sites or help text to document the new switch so
secrets are never printed by default.
- Around line 17-22: The tokenBody currently hardcodes admin credentials
(username/password/client_id); change it to obtain credentials securely
instead—read username and client_id from environment variables (e.g.,
$env:KEYCLOAK_ADMIN_USER and $env:KEYCLOAK_CLIENT_ID) and read the password via
a secure prompt (Read-Host -AsSecureString converted to plain text only when
needed) or from a protected secret store, and fail early with a clear error if
required values are missing; update the tokenBody construction to use those
variables instead of literal strings.

In `@src/Shared/Authorization/Keycloak/KeycloakPermissionProvider.cs`:
- Around line 31-38: The code calls
keycloakResolver.ResolvePermissionsAsync(...) and then uses permissions.Count
which will NRE if null; update KeycloakPermissionProvider to guard against null
by assigning the result to a local (e.g., permissions = await
keycloakResolver.ResolvePermissionsAsync(...) ?? Enumerable.Empty<Permission>()
or a new List<Permission>() depending on the method's return type), use that
local in logger.LogDebug (MaskUserId(userId) remains), and return the non-null
collection so ResolvePermissionsAsync and logger.LogDebug never observe a null
permissions reference.
- Around line 29-47: The catch block in the method that calls
keycloakResolver.ResolvePermissionsAsync should not swallow cancellation: update
the exception handling in KeycloakPermissionProvider (the method using
ResolvePermissionsAsync and MaskUserId) so that OperationCanceledException (and
derived cancellation exceptions) are rethrown instead of being logged and
returning Array.Empty<EPermission>(); specifically, detect or catch
OperationCanceledException and throw it (or if catching Exception, rethrow when
ex is OperationCanceledException) so cancellation propagates to callers while
still logging or handling non-cancellation exceptions as before.
- Around line 15-16: The Keycloak provider's ModuleName property currently
returns ModuleNames.Users which prevents its cross-module permissions from being
used; change the KeycloakPermissionProvider.ModuleName to indicate it applies to
all modules (e.g., a new ModuleNames.All or empty/wildcard value), then update
the filtering in KeycloakPermissionResolver.GetUserPermissionsByModuleAsync so
it includes providers whose ModuleName equals the requested module OR whose
ModuleName is the all/wildcard value; verify
MapKeycloakRoleToPermissions_AdminRole still maps Keycloak roles to permissions
across Providers, ServiceCatalogs, Locations, etc.
🧹 Nitpick comments (1)
infrastructure/keycloak/reimport-realm.ps1 (1)

4-6: Make script parameters configurable.
Consider reading $KEYCLOAK_URL, $REALM_NAME, and $REALM_FILE from env vars or script parameters so the script can be reused across environments without edits.

Filipe Frigini added 2 commits January 27, 2026 09:43
- Update GetProviderByUserIdQueryHandlerTests to expect 'Erro ao buscar prestador'
- Update GetProviderByIdQueryHandlerTests to expect 'Erro ao buscar prestador'
- Update GetProvidersByStateQueryHandlerTests to expect 'obrigatório' and 'erro ao recuperar'
- Update GetProvidersByVerificationStatusQueryHandlerTests to expect 'erro ao recuperar'
- Update GetProvidersByTypeQueryHandlerTests to expect 'erro ao recuperar'
- Update GetProvidersByCityQueryHandlerTests to expect 'erro ao recuperar'

All tests now pass (12/12 succeeded)
Security Improvements:
- Remove hardcoded client secret from meajudaai-realm.dev.json
- Remove hardcoded user passwords from realm configuration
- Rewrite reimport-realm.ps1 with secure credential handling:
  * Environment variable support (KEYCLOAK_ADMIN_USER, KEYCLOAK_CLIENT_ID, etc.)
  * Secure password prompt using Read-Host -AsSecureString
  * -ShowSecrets switch parameter (default: false)
  * Fail-fast error handling with proper exit codes
  * Clear post-import instructions for manual credential setup

Code Quality:
- Create RoleConstants.cs with centralized Keycloak role definitions
- Update KeycloakPermissionResolver to use RoleConstants instead of hardcoded strings
- Eliminate magic strings for improved maintainability

Authorization Provider Fixes:
- Add null guard in KeycloakPermissionProvider (use null-coalescing operator)
- Fix cancellation handling: rethrow OperationCanceledException
- Update ModuleName property to return wildcard '*' for cross-module permissions
- Add XML documentation explaining cross-module permission support

All changes improve security posture, code maintainability, and authorization robustness.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@src/Modules/Providers/Tests/Unit/Application/Handlers/Queries/GetProviderByIdQueryHandlerTests.cs`:
- Line 98: The test suite GetProviderByIdQueryHandlerTests and the handler
should use Portuguese for user-facing "not found" messages: update the handler
method (GetProviderByIdQueryHandler.Handle or wherever Error.NotFound("Provider
not found") is returned) to return a Portuguese message such as "Prestador não
encontrado", then update the unit test expectation (the assertion that currently
looks for "Provider not found") to assert a Portuguese substring like "Prestador
não encontrado" so both the code path and tests match localization.
🧹 Nitpick comments (4)
src/Shared/Authorization/Keycloak/KeycloakPermissionResolver.cs (1)

307-371: Consider pre-computing lowercased role constants.

Each switch arm calls ToLowerInvariant() on RoleConstants.* at runtime. While the performance impact is minimal, you could simplify by either:

  1. Defining the constants in lowercase in RoleConstants.cs (if Keycloak role names are always lowercase), or
  2. Using StringComparison.OrdinalIgnoreCase with a different pattern.

Current approach works correctly but involves redundant string allocations on each call.

src/Shared/Utilities/Constants/RoleConstants.cs (1)

1-35: Well-structured centralized role constants.

Good approach centralizing Keycloak role strings to avoid duplication and ensure consistency. The XML documentation correctly uses Portuguese per the codebase language policy.

Minor: Inline comments (e.g., // System/Admin roles) are in English. Per the established language policy from learnings, comments should be in Portuguese while logs remain in English. Consider translating these section comments.

Based on learnings, Shared.Contracts is the preferred location for shared enums/constants to keep Web aligned with backend. Consider whether this class should be moved there for consistency.

src/Shared/Authorization/Keycloak/KeycloakPermissionProvider.cs (2)

2-2: Unused import.

MeAjudaAi.Shared.Utilities.Constants is imported but no symbols from this namespace are used in this file.

🧹 Proposed fix
 using MeAjudaAi.Shared.Authorization.Core;
-using MeAjudaAi.Shared.Utilities.Constants;
 using Microsoft.Extensions.Logging;

62-74: Duplicate MaskUserId implementation.

This method is identical to KeycloakPermissionResolver.MaskUserId (lines 57-66). Consider extracting it to a shared utility class to avoid duplication and ensure consistent masking behavior.

♻️ Suggested approach

Create a shared utility, for example in MeAjudaAi.Shared.Utilities:

public static class PiiMaskingHelper
{
    public static string MaskUserId(string userId)
    {
        if (string.IsNullOrWhiteSpace(userId))
            return "[EMPTY]";

        if (userId.Length <= 6)
            return $"{userId[0]}***{userId[^1]}";

        return $"{userId[..3]}***{userId[^3..]}";
    }
}

Then reference it from both KeycloakPermissionResolver and KeycloakPermissionProvider.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/Shared/Utilities/PiiMaskingHelper.cs`:
- Around line 13-16: The MaskUserId method currently accepts a non-nullable
string but explicitly handles null/whitespace; change the parameter type from
string to string? in the MaskUserId signature to reflect nullable input, update
any nullable annotations/imports if needed, and fix any call sites that rely on
the previous non-nullable contract (adjusting or adding null checks/casts as
required) so the project compiles cleanly while keeping the existing
null/whitespace handling inside MaskUserId.

In
`@tests/MeAjudaAi.Architecture.Tests/Authorization/PermissionArchitectureTests.cs`:
- Around line 151-156: The assertion currently uses parts[0].All(...) and
parts[1].All(...) which returns true on empty strings, allowing empty or
hyphen-only segments; update the checks in PermissionArchitectureTests to first
ensure parts[0] and parts[1] are non-empty and contain at least one letter
(e.g., ensure parts[i].Length > 0 and parts[i].Any(char.IsLetter)), and also
reject strings that consist only of '-' or that have leading/trailing hyphens if
desired; modify the Assert.True calls referencing parts[0] and parts[1] to
combine these non-empty/has-letter checks with the existing char.IsLower/== '-'
validation so empty or hyphen-only segments fail the test.

Filipe Frigini added 2 commits January 27, 2026 11:50
…or message

Updated test assertion to expect 'erro ao recuperar' instead of 'error occurred'
to match the Portuguese error messages returned by the handler.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@src/Modules/Providers/Tests/Unit/Application/Handlers/Queries/GetProvidersByIdsQueryHandlerTests.cs`:
- Line 148: Create a centralized constant for the duplicated error text (e.g.,
ValidationMessages.Providers.ErrorRetrievingProviders = "Ocorreu um erro ao
recuperar os prestadores"), then update all five query handlers
(GetProvidersByIdsQueryHandler, GetProvidersByCityQueryHandler,
GetProvidersByStateQueryHandler, GetProvidersByTypeQueryHandler,
GetProvidersByVerificationStatusQueryHandler) to use that constant when
producing the error message, and update the corresponding unit tests (e.g.,
GetProvidersByIdsQueryHandlerTests.cs and the other four tests) to assert
against ValidationMessages.Providers.ErrorRetrievingProviders instead of the
hard-coded string.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/MeAjudaAi.Shared.Tests/Unit/Authorization/PermissionTests.cs`:
- Line 127: The test AllPermissions_ShouldHaveValidModuleNames fails because
GetModule() on the new LocationsRead and LocationsManage permissions yields
"locations" but the validModules array declared in PermissionTests (var
validModules = new[] { "system", "users", "providers", "orders", "reports",
"admin", "service-catalogs" };) does not include "locations"; fix by adding
"locations" to that validModules array so it contains "locations" alongside the
other module names.
🧹 Nitpick comments (3)
src/Modules/Providers/Application/Handlers/Queries/GetProvidersByStateQueryHandler.cs (1)

24-24: Consider centralizing this validation message as well.

For consistency with the error message on line 35 and the broader refactoring effort, consider adding a constant like ValidationMessages.Providers.StateParameterRequired and using it here instead of the inline string.

♻️ Suggested approach

In src/Contracts/Utilities/Constants/ValidationMessages.cs:

 public static class Providers
 {
     public const string ErrorRetrievingProviders = "Ocorreu um erro ao recuperar os prestadores";
+    public const string StateParameterRequired = "O parâmetro de estado é obrigatório";
 }

Then in this file:

-                return Result<IReadOnlyList<ProviderDto>>.Failure("O parâmetro de estado é obrigatório");
+                return Result<IReadOnlyList<ProviderDto>>.Failure(ValidationMessages.Providers.StateParameterRequired);
src/Modules/Providers/Tests/Unit/Application/Handlers/Queries/GetProvidersByStateQueryHandlerTests.cs (1)

68-68: Consider using a centralized constant for the "required" validation message.

These assertions use partial string matching (.Contain("obrigatório")), while line 156 uses the exact constant ValidationMessages.Providers.ErrorRetrievingProviders. For consistency, consider adding a constant for the state required message (e.g., ValidationMessages.Providers.StateRequired) and asserting against it directly.

This would improve maintainability if the message wording changes and align with the centralization approach used elsewhere in this PR. Based on learnings, prefer reuse of Contracts constants.

Also applies to: 87-87, 106-106

src/Contracts/Utilities/Constants/ValidationMessages.cs (1)

96-103: Approve the new Providers class; minor punctuation inconsistency.

The new Providers class follows the established pattern and the Portuguese message is correct per the language policy. However, ErrorRetrievingProviders lacks a trailing period while other messages in this file consistently end with one (e.g., "O email é obrigatório.").

Optional: Add trailing period for consistency
 public static class Providers
 {
-    public const string ErrorRetrievingProviders = "Ocorreu um erro ao recuperar os prestadores";
+    public const string ErrorRetrievingProviders = "Ocorreu um erro ao recuperar os prestadores.";
 }

Filipe Frigini added 3 commits January 27, 2026 14:05
… tests and validation messages, and add `PermissionTests` for authorization.
…oviding a configurable environment with mock services and dedicated test databases.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@tests/MeAjudaAi.Shared.Tests/TestInfrastructure/Handlers/BaseTestAuthenticationHandler.cs`:
- Around line 68-73: Replace the two new English inline comments above the
permission claims in BaseTestAuthenticationHandler.cs with Portuguese versions:
change “Add all admin permissions for ServiceCatalogs” to “Adicionar todas as
permissões administrativas para Catálogos de Serviço” and change “Add all admin
permissions for Locations” to “Adicionar todas as permissões administrativas
para Localizações”; update the comments immediately preceding the claims.Add
calls that use AuthConstants.Claims.Permission and
EPermission.ServiceCatalogs*/EPermission.Locations* so the wording matches the
repo policy.

…t API endpoints, along with a new admin dashboard page.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/Modules/Providers/API/Endpoints/ProviderAdmin/GetProvidersByCityEndpoint.cs (1)

21-63: ⚠️ Potential issue | 🟡 Minor

Update remarks: endpoint is no longer “consulta pública.”
With RequirePermission(EPermission.ProvidersRead), the XML remarks should reflect authorized access.

✍️ Suggested update
-/// filtrados por cidade específica. Utiliza arquitetura CQRS e permite
-/// consulta pública para facilitar descoberta de serviços.
+/// filtrados por cidade específica. Utiliza arquitetura CQRS e permite
+/// consulta autorizada para facilitar descoberta de serviços.
🤖 Fix all issues with AI agents
In
`@src/Modules/Providers/API/Endpoints/ProviderAdmin/GetProvidersByVerificationStatusEndpoint.cs`:
- Around line 105-109: Translate the inline comment following the throw in the
catch block that handles OperationCanceledException inside
GetProvidersByVerificationStatus to Portuguese, keeping the code behavior
unchanged and preserving the single-line comment syntax (//); leave the
logger.LogInformation message in English as-is and only replace the comment text
"Propagate cancellation to pipeline" with an appropriate Portuguese translation
(e.g., "Propagar cancelamento para o pipeline") next to the throw statement.

In
`@src/Modules/Providers/API/Endpoints/ProviderServices/AddServiceToProviderEndpoint.cs`:
- Line 48: The authorization change removed the context-aware SelfOrAdmin policy
and replaced it with a role-only permission check, blocking regular providers
from updating their own provider record; revert or adjust this by restoring the
context-aware policy usage on the endpoint (use
.RequireAuthorization("SelfOrAdmin") instead of
.RequirePermission(EPermission.ProvidersUpdate)) or, if you prefer
permission-based control, update the role-to-permission mapping so the Provider
role is granted EPermission.ProvidersUpdate; locate the endpoint configuration
where .RequirePermission(EPermission.ProvidersUpdate) is applied
(AddServiceToProviderEndpoint) and either swap it back to
.RequireAuthorization("SelfOrAdmin") or add EPermission.ProvidersUpdate to the
Provider role in the authorization/role mapping.
🧹 Nitpick comments (2)
src/Modules/Providers/API/Endpoints/ProviderAdmin/UpdateProviderProfileEndpoint.cs (1)

54-54: Documentation may be inconsistent with new permission model.

Line 54 states "🔒 Controle de acesso: próprio prestador ou administrador" (self or admin), but the endpoint now requires EPermission.ProvidersUpdate. If providers aren't granted this permission by default, the description should be updated to reflect the actual access control.

Consider updating the description to match the permission-based model, e.g., "🔒 Controle de acesso: usuários com permissão ProvidersUpdate".

📝 Suggested description update
-                - 🔒 Controle de acesso: próprio prestador ou administrador
+                - 🔒 Controle de acesso: usuários com permissão ProvidersUpdate

Also applies to: 68-68

src/Modules/Providers/API/Endpoints/ProviderAdmin/RemoveDocumentEndpoint.cs (1)

52-52: Documentation inconsistency with permission model.

Similar to UpdateProviderProfileEndpoint, line 52 states "🔒 Controle de acesso: próprio prestador ou administrador" but the endpoint now requires EPermission.ProvidersUpdate. Consider updating the description to reflect the permission-based access control.

📝 Suggested description update
-                - 🔒 Controle de acesso: próprio prestador ou administrador
+                - 🔒 Controle de acesso: usuários com permissão ProvidersUpdate

Also applies to: 74-74

Filipe Frigini added 2 commits February 3, 2026 18:08
…t endpoints, a development data seeder, a provider DTO, and an admin UI page for providers.
…agement, and core infrastructure components.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (7)
src/Modules/SearchProviders/Application/ModuleApi/SearchProvidersModuleApi.cs (1)

66-71: ⚠️ Potential issue | 🟠 Major

Cancellation exceptions are now swallowed and logged as errors.

The removal of the explicit OperationCanceledException catch block changes the behavior:

  1. Cancellation is no longer propagated — callers cannot distinguish between a genuine availability failure and a cancelled operation.
  2. Logged as Error instead of Debug — cancellations will pollute error logs, making real issues harder to spot.
  3. Cooperative cancellation semantics broken — during graceful shutdown or request timeouts, the caller should see OperationCanceledException, not false.

Consider restoring the explicit handling so cancellations propagate correctly.

🔧 Proposed fix to restore OperationCanceledException handling
         return testResult.IsSuccess;
     }
-
+    catch (OperationCanceledException)
+    {
+        logger.LogDebug("SearchProviders availability check was cancelled");
+        throw;
+    }
     catch (Exception ex)
     {
         logger.LogError(ex, "Error checking SearchProviders module availability");
         return false;
     }
src/Modules/Users/Application/ModuleApi/UsersModuleApi.cs (2)

74-79: ⚠️ Potential issue | 🟡 Minor

Cancellation logged at Error level after removing explicit handler.

With OperationCanceledException no longer caught explicitly, cancellation during shutdown or timeout will be logged via LogError (line 77). Cancellation is expected control flow, not an error, and logging it at Error level pollutes error metrics and complicates diagnostics.

Consider filtering cancellation from error logging:

🛠️ Proposed fix
-        catch (Exception ex)
+        catch (OperationCanceledException)
+        {
+            logger.LogDebug("Users module availability check was cancelled");
+            return false;
+        }
+        catch (Exception ex)
         {
             logger.LogError(ex, "Error checking Users module availability");
             return false;
         }

112-117: ⚠️ Potential issue | 🟡 Minor

Same cancellation concern applies here.

OperationCanceledException will now be logged as "Basic operations test failed" (Warning level), which is misleading—the test was cancelled, not failed. For consistency with the suggested fix above and clearer diagnostics:

🛠️ Proposed fix
-        catch (Exception ex)
+        catch (OperationCanceledException)
+        {
+            logger.LogDebug("Basic operations test was cancelled for Users module");
+            return false;
+        }
+        catch (Exception ex)
         {
             logger.LogWarning(ex, "Basic operations test failed for Users module");
             return false;
         }
src/Modules/Providers/Application/ModuleApi/ProvidersModuleApi.cs (1)

82-87: ⚠️ Potential issue | 🟠 Major

Swallowing OperationCanceledException masks cancellation semantics.

The removal of dedicated OperationCanceledException handling causes cancellation to be logged as an error and return false instead of propagating. This can interfere with graceful shutdown, request timeouts, and caller expectations — they won't know the operation was cancelled vs. actually failed.

Consider restoring explicit handling or filtering it from the generic catch:

🛡️ Proposed fix to propagate cancellation
         logger.LogDebug("Providers module is available and healthy");
         return true;
     }
-
-    catch (Exception ex)
+    catch (OperationCanceledException)
+    {
+        throw; // Let cancellation propagate
+    }
+    catch (Exception ex)
     {
         logger.LogError(ex, "Error checking Providers module availability");
         return false;
     }

Apply the same pattern to CanExecuteBasicOperationsAsync:

         return false;
     }
-
-    catch (Exception ex)
+    catch (OperationCanceledException)
+    {
+        throw;
+    }
+    catch (Exception ex)
     {
         logger.LogWarning(ex, "Basic operations test failed for Providers module");
         return false;
     }

Also applies to: 110-115

src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs (1)

136-141: ⚠️ Potential issue | 🟡 Minor

Translate user-facing error message to Portuguese.

The error message on line 140 should be in Portuguese per the codebase language policy. User-facing messages and API responses shown to the frontend must be in Portuguese, while logs should remain in English (line 139 is correct).

-            throw new InvalidOperationException("Failed to upload document. Please try again later.", ex);
+            throw new InvalidOperationException("Falha ao enviar documento. Por favor, tente novamente mais tarde.", ex);
src/Modules/Documents/Application/ModuleApi/DocumentsModuleApi.cs (2)

151-166: ⚠️ Potential issue | 🟡 Minor

Translate XML documentation to Portuguese.

These XML docs are in English; policy requires Portuguese for comments and XML summaries.

✍️ Proposed translation
-    /// <summary>
-    /// Gets the status of a document.
-    /// </summary>
-    /// <param name="documentId">Document ID</param>
-    /// <param name="cancellationToken">Cancellation token</param>
-    /// <returns>Document status DTO or null if not found</returns>
-    /// <remarks>
-    /// <para><strong>UpdatedAt Semantics:</strong></para>
-    /// <para>Uses VerifiedAt ?? UploadedAt where VerifiedAt represents the timestamp of the last
-    /// status change (verification or rejection). The domain model sets VerifiedAt when documents
-    /// are verified OR rejected. For documents still in Uploaded/PendingVerification status,
-    /// falls back to UploadedAt.</para>
-    /// <para><strong>Note:</strong> RejectedAt is NOT used in the fallback chain because the domain
-    /// already populates VerifiedAt for rejected documents, making VerifiedAt the authoritative
-    /// timestamp for all terminal states (Verified/Rejected).</para>
-    /// </remarks>
+    /// <summary>
+    /// Obtém o status de um documento.
+    /// </summary>
+    /// <param name="documentId">ID do documento</param>
+    /// <param name="cancellationToken">Token de cancelamento</param>
+    /// <returns>DTO de status do documento ou null se não encontrado</returns>
+    /// <remarks>
+    /// <para><strong>Semântica de UpdatedAt:</strong></para>
+    /// <para>Usa VerifiedAt ?? UploadedAt, onde VerifiedAt representa o timestamp da última mudança
+    /// de status (verificação ou rejeição). O modelo de domínio define VerifiedAt quando documentos
+    /// são verificados OU rejeitados. Para documentos ainda em Uploaded/PendingVerification,
+    /// usa UploadedAt como fallback.</para>
+    /// <para><strong>Nota:</strong> RejectedAt NÃO é usado na cadeia de fallback porque o domínio
+    /// já popula VerifiedAt para documentos rejeitados, tornando VerifiedAt o timestamp
+    /// autoritativo para todos os estados terminais (Verified/Rejected).</para>
+    /// </remarks>

Based on learnings: "Adopt a language policy across the codebase: 1) All logs must be in English, 2) Comments and XML summaries (///,

, , etc.) must be in Portuguese, 3) User-facing validation messages and API responses shown to the frontend must be in Portuguese."


45-83: ⚠️ Potential issue | 🟠 Major

Preserve cancellation semantics instead of treating cancellation as failure.

All methods accepting cancellationToken catch generic Exception without distinguishing OperationCanceledException. When the token is canceled, the exception is logged as an error and converted to failure/false, which masks cancellation semantics and inflates error telemetry. Each method should explicitly handle cancellation and either rethrow or return without error logging.

🛠️ Suggested pattern (apply to all methods with a cancellationToken)
         try
         {
             logger.LogDebug("Checking Documents module availability");
             ...
         }
+        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+        {
+            logger.LogDebug("Documents module availability check canceled");
+            throw;
+        }
         catch (Exception ex)
         {
             logger.LogError(ex, "Error checking Documents module availability");
             return false;
         }

Applies to: IsAvailableAsync, CanExecuteBasicOperationsAsync, GetDocumentByIdAsync, GetProviderDocumentsAsync, GetDocumentStatusAsync, HasVerifiedDocumentsAsync, HasRequiredDocumentsAsync, GetDocumentStatusCountAsync, HasPendingDocumentsAsync, HasRejectedDocumentsAsync

🤖 Fix all issues with AI agents
In `@src/Shared/Exceptions/GlobalExceptionHandler.cs`:
- Around line 17-20: The unwrap logic in GlobalExceptionHandler that checks for
wrappers (InvalidOperationException,
System.Reflection.TargetInvocationException, AggregateException) currently only
unwraps to a set of exceptions that excludes NotFoundException, causing wrapped
404s to be treated as 500s; update the conditional that inspects
exception.InnerException (in the unwrap branch inside GlobalExceptionHandler) to
also include NotFoundException in the list (alongside ValidationException,
BadRequestException, UnprocessableEntityException, ArgumentException,
DomainException) so wrapped NotFoundException instances are unwrapped and
handled with their correct 404 response.
🧹 Nitpick comments (4)
.editorconfig (1)

12-31: Consider scoping the global suppressions or documenting rationale per rule.

Disabling many Sonar rules in the global *.cs section can mask real issues across the entire codebase. If these are needed only for specific areas (e.g., migrations, generated, or legacy code), scope them to those paths or add explicit justification comments for each rule.

src/Modules/Providers/Application/ModuleApi/ProvidersModuleApi.cs (1)

140-151: Avoid double enumeration of IEnumerable.

providerIds.Count() on line 142 and ToList() on line 144 each enumerate the input. If the caller passes a deferred sequence (e.g., a LINQ query), it will be evaluated twice.

♻️ Suggested refactor
 public async Task<Result<IReadOnlyList<ModuleProviderBasicDto>>> GetProvidersBasicInfoAsync(IEnumerable<Guid> providerIds, CancellationToken cancellationToken = default)
 {
-    logger.LogDebug("Getting basic provider info for {Count} provider IDs", providerIds.Count());
-
-    var result = await getProvidersByIdsHandler.HandleAsync(new GetProvidersByIdsQuery(providerIds.ToList()), cancellationToken);
+    var idList = providerIds.ToList();
+    logger.LogDebug("Getting basic provider info for {Count} provider IDs", idList.Count);
+
+    var result = await getProvidersByIdsHandler.HandleAsync(new GetProvidersByIdsQuery(idList), cancellationToken);
src/Shared/Seeding/DevelopmentDataSeeder.cs (1)

151-154: Comment should be in Portuguese per project language policy.

The log message is correctly in English, but the inline comment should be in Portuguese for consistency with the rest of the file. Based on learnings, comments and XML documentation should be in Portuguese while logs must be in English.

Proposed fix
-            // Always seed providers (uses ON CONFLICT DO NOTHING)
+            // Sempre semeia providers (usa ON CONFLICT DO NOTHING)
src/Modules/Locations/Application/Handlers/UpdateAllowedCityHandler.cs (1)

25-39: Consider using ValidationMessages constants for consistency.

The error messages are hardcoded, while the endpoint maps errors by status code to ValidationMessages.Locations.AllowedCityNotFound and ValidationMessages.Locations.DuplicateCity. Using the constants directly in the handler would improve maintainability if these messages are ever logged or inspected.

♻️ Suggested change
         if (allowedCity == null)
         {
-            return Result.Failure(Error.NotFound("Cidade permitida não encontrada"));
+            return Result.Failure(Error.NotFound(ValidationMessages.Locations.AllowedCityNotFound));
         }
         // ...
         if (existing is not null && existing.Id != command.Id)
         {
-            return Result.Failure(Error.Conflict($"Cidade '{command.CityName}-{command.StateSigla}' já cadastrada"));
+            return Result.Failure(Error.Conflict(ValidationMessages.Locations.DuplicateCity));
         }

Add the import:

using static MeAjudaAi.Modules.Locations.ValidationMessages;

frigini and others added 2 commits February 4, 2026 09:11
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…d Providers, and add administrative UI for managing allowed cities.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
src/Modules/Documents/Application/ModuleApi/DocumentsModuleApi.cs (2)

207-248: ⚠️ Potential issue | 🟡 Minor

Translate remaining English comments/XML docs to Portuguese.
The PERFORMANCE NOTE block and helper method summaries (plus the cancellationToken param description) are still in English—please localize them to Portuguese to match the repository language policy.

Based on learnings, adopt a language policy across the codebase: 1) All logs must be in English, 2) Comments and XML summaries (///,

, , etc.) must be in Portuguese, 3) User-facing validation messages and API responses shown to the frontend must be in Portuguese.

Also applies to: 275-279


119-137: ⚠️ Potential issue | 🟠 Major

Add OperationCanceledException handling to Result-returning methods.
The generic catch (Exception) swallows OperationCanceledException, converting cancellations into failure Results and error logs rather than propagating the cancellation. This prevents upstream cancellation from short-circuiting and masks request timeouts. Your codebase already demonstrates the correct pattern in IsAvailableAsync and CanExecuteBasicOperationsAsync. Apply this pattern to all Result-returning methods: add a filtered catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } before the generic catch.

Methods requiring this fix: GetDocumentByIdAsync, GetProviderDocumentsAsync, GetDocumentStatusAsync, HasVerifiedDocumentsAsync, HasRequiredDocumentsAsync, GetDocumentStatusCountAsync, HasPendingDocumentsAsync, and HasRejectedDocumentsAsync.

Pattern (already implemented in IsAvailableAsync, line 77-81)
-        catch (Exception ex)
+        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+        {
+            throw;
+        }
+        catch (Exception ex)
         {
             logger.LogError(ex, "...");
             return Result<T>.Failure("...");
         }
🤖 Fix all issues with AI agents
In `@src/Modules/Documents/Application/Handlers/UploadDocumentCommandHandler.cs`:
- Around line 136-141: The catch-all in UploadDocumentCommandHandler's method is
wrapping intentional UnauthorizedAccessException and ArgumentException into
InvalidOperationException; change the error handling so known exceptions are not
swallowed—either narrow the try scope to only the risky calls so the explicitly
thrown UnauthorizedAccessException and ArgumentException occur outside the try,
or in the catch (Exception ex) check and rethrow when ex is
UnauthorizedAccessException or ArgumentException (e.g., throw;), logging only
unexpected exceptions and preserving original messages and types for
GlobalExceptionHandler to map to proper HTTP statuses. Ensure references: the
catch (Exception ex) block in UploadDocumentCommandHandler and the places where
UnauthorizedAccessException/ArgumentException are thrown remain intact.

In `@src/Shared/Seeding/DevelopmentDataSeeder.cs`:
- Around line 440-441: The comment "We use simple SQL injection for development
seeder brevity" is misleading; update the comment in the Insert Document
(Primary) section of DevelopmentDataSeeder (the seeding code that uses EF Core
parameter placeholders like {0}, {1}) to say you are using simple/raw SQL or EF
Core parameterized/raw SQL for brevity and that EF Core parameters are safer and
prevent SQL injection; keep the rest of the logic unchanged and ensure the
comment accurately reflects that the code uses parameterized queries rather than
describing a vulnerability.
- Around line 384-409: The third provider entry uses Guid.NewGuid() for Id and
UserId which breaks idempotent seeding; add two stable GUID constants (e.g.,
private static readonly Guid Provider3UserId and Provider3Id) near the other
provider constants in the DevelopmentDataSeeder class and replace the
Guid.NewGuid() occurrences in the third provider object with those constants so
subsequent seed runs use the same user_id and id values.
🧹 Nitpick comments (1)
src/Web/MeAjudaAi.Web.Admin/Components/Dialogs/EditAllowedCityDialog.razor (1)

35-57: Consider making the latitude/longitude row responsive.

Without wrapping, the two fields can overflow on narrow screens. Add a responsive flex class or wrap behavior.

Proposed tweak
-            <div class="d-flex gap-4 mb-3">
+            <div class="d-flex flex-column flex-sm-row gap-4 mb-3">

Filipe Frigini added 2 commits February 4, 2026 09:36
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/Shared/Seeding/DevelopmentDataSeeder.cs`:
- Around line 24-30: The seeder currently generates new document IDs each run
causing duplicate rows; change the seeding to use stable, pre-defined GUIDs
(e.g., Provider1Id, Provider2Id, Provider3Id and their corresponding
Provider1UserId/Provider2UserId/Provider3UserId) when creating provider
documents instead of calling UuidGenerator.NewId(), and ensure the insert/update
logic uses those stable IDs so the ON CONFLICT (provider_id, id) DO NOTHING
behavior becomes idempotent; apply the same fix to the other seeding block
referenced (the entries around the later provider seeding, the section noted as
lines 445-452).
- Around line 442-444: The comment block above the SQL insert in
DevelopmentDataSeeder.cs is in English; translate the three English comment
lines ("Insert Document (Primary)", "Note: We use parameterized SQL with EF Core
to ensure safety and prevent SQL injection.", and "provider_id and id are
composite primary key") into Portuguese and replace the existing comments in
that location so comments and XML summaries follow the project's Portuguese
policy while leaving code and identifiers unchanged.
🧹 Nitpick comments (1)
src/Modules/Locations/Application/Handlers/UpdateAllowedCityHandler.cs (1)

51-55: Consider documenting the coordinate threshold constant.

The 0.0001 threshold for detecting "missing" coordinates (~11 meters at the equator) is reasonable for Brazilian cities, but extracting it to a named constant would improve readability and maintainability.

💡 Optional: Extract threshold to a named constant
+private const double CoordinateZeroThreshold = 0.0001;
+
 public async Task<Result> HandleAsync(UpdateAllowedCityCommand command, CancellationToken cancellationToken = default)
 {
     // ...
-    var existingCoordsMissing = Math.Abs(allowedCity.Latitude) < 0.0001 && Math.Abs(allowedCity.Longitude) < 0.0001;
-    var commandCoordsMissing = Math.Abs(lat) < 0.0001 && Math.Abs(lon) < 0.0001;
+    var existingCoordsMissing = Math.Abs(allowedCity.Latitude) < CoordinateZeroThreshold && Math.Abs(allowedCity.Longitude) < CoordinateZeroThreshold;
+    var commandCoordsMissing = Math.Abs(lat) < CoordinateZeroThreshold && Math.Abs(lon) < CoordinateZeroThreshold;

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Shared/Seeding/DevelopmentDataSeeder.cs`:
- Around line 424-425: Translate the inline English comment "// Insert Provider"
to Portuguese per repo policy; locate the comment immediately above the
ExecuteSqlRawAsync call in DevelopmentDataSeeder.cs and replace it with a
Portuguese comment such as "// Inserir Provedor" (or another appropriate
Portuguese term for "provider") ensuring the rest of the code is unchanged.

…evelopment and test data, including categories, locations, and providers.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Modules/Documents/Application/ModuleApi/DocumentsModuleApi.cs`:
- Around line 133-141: There are two identical filtered catch blocks for
OperationCanceledException in DocumentsModuleApi.cs which makes the second
unreachable; remove the duplicate so only a single "catch
(OperationCanceledException) when (cancellationToken.IsCancellationRequested)"
remains and ensures it rethrows to propagate cancellation to the caller
(preserve the existing throw; behavior), keeping only one filtered catch in the
method.
🧹 Nitpick comments (1)
src/Modules/Documents/Tests/Unit/Application/UploadDocumentCommandHandlerTests.cs (1)

576-576: Prefer a shared constant for the localized error text.

Hard-coding the full Portuguese message makes the test brittle if localization changes. If a shared constant (e.g., ValidationMessages) exists for this message, assert against that instead.

frigini and others added 2 commits February 5, 2026 08:58
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…nit tests for document upload functionality.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Modules/Documents/Application/ModuleApi/DocumentsModuleApi.cs`:
- Around line 133-136: No bloco catch que captura OperationCanceledException em
DocumentsModuleApi.cs (condição cancellationToken.IsCancellationRequested),
substitua o termo em inglês "caller" no comentário por uma palavra em português
— por exemplo, altere "// Propagar cancelamento para o caller" para "// Propagar
cancelamento para o chamador" (ou "// Propagar cancelamento para o invocador")
para manter o comentário totalmente em Português.

…h a development data seeder and module provider DTO.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/Shared/Seeding/DevelopmentDataSeeder.cs`:
- Around line 450-459: The seed inserts into providers.document using
ExecuteSqlRawAsync but never uses the per-provider DocumentId fields
(provider.DocumentId) and the comment incorrectly claims the composite PK is
(provider_id, id) while the ON CONFLICT uses (provider_id, document_type);
either remove the unused DocumentId properties and all assignments (e.g.,
Provider.*.DocumentId) and update the comment to reflect the actual composite
key (provider_id, document_type), or include provider.DocumentId in the
INSERT/VALUES and change the ON CONFLICT clause/comment to match the true
schema; locate the ExecuteSqlRawAsync call, the provider.DocumentId assignments,
and the surrounding comment in DevelopmentDataSeeder.cs to apply the chosen fix.

In `@src/Web/MeAjudaAi.Web.Admin/Pages/Dashboard.razor`:
- Line 237: The inline comment "// TEMPORARY: Charts disabled for debugging" in
Dashboard.razor is misleading because charts are processed and displayed; remove
or update that comment to reflect the actual behavior (e.g., delete the line or
change to "Charts enabled — debugging comment removed") so future maintainers
aren't confused; locate the comment text in the Dashboard.razor component and
update it accordingly.
- Around line 296-299: The branch that handles numeric type keys parses typeKey
and returns ProviderType.ToDisplayName(typeValue) but doesn't guard against a
null result; update the numeric branch (the int.TryParse block that uses typeKey
and typeValue and calls ProviderType.ToDisplayName) to return a non-null
fallback by applying the same null-coalescing fallback used elsewhere (e.g., ??
"Desconhecido") so unknown/ unmapped integer values yield "Desconhecido" instead
of null.
🧹 Nitpick comments (6)
src/Web/MeAjudaAi.Web.Admin/Pages/Documents.razor (1)

37-37: Localization changes look good, but inconsistent terminology remains.

The updates from "Provider" to "Provedor" are correct per the Portuguese user-facing text policy. However, the file still contains "Provider" in other locations:

  • Line 30: "Documentos por Provider"
  • Line 52: "Provider ID:"
  • Line 119: "Nenhum documento encontrado para este provider"

Consider updating these for consistency across the page.

Also applies to: 45-45, 133-133

src/Web/MeAjudaAi.Web.Admin/Pages/Providers.razor (4)

118-118: Move @inject directive to the top with other injections.

This injection is placed between the markup and @code block, while other @inject directives are grouped at lines 9-13. Relocating it improves readability and follows Blazor conventions.

♻️ Suggested relocation

Move to the directive block at the top:

`@inject` ILogger<Providers> Logger
`@inject` IPermissionService PermissionService
+@inject NavigationManager Navigation
`@implements` IDisposable

Then remove line 118.


128-140: Use QueryHelpers.ParseQuery for robust query string parsing.

Manual parsing doesn't handle URL-encoded values or edge cases reliably. The built-in utility provides safer parsing.

♻️ Suggested fix using QueryHelpers

Add the using directive at the top:

`@using` Microsoft.AspNetCore.WebUtilities

Then replace the manual parsing:

-        var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
-        var query = uri.Query.TrimStart('?');
-        
-        if (!string.IsNullOrEmpty(query))
-        {
-             var queryParams = query.Split('&');
-             if (queryParams.Any(q => q.Equals("filter=pending", StringComparison.OrdinalIgnoreCase)))
-             {
-                 _showOnlyPending = true;
-             }
-        }
+        var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
+        var queryParams = QueryHelpers.ParseQuery(uri.Query);
+        
+        if (queryParams.TryGetValue("filter", out var filterValue) &&
+            filterValue.ToString().Equals("pending", StringComparison.OrdinalIgnoreCase))
+        {
+            _showOnlyPending = true;
+        }

147-155: Consider subscribing to state changes instead of filtering on every render.

UpdateFilteredProviders() is called on every re-render after the first. While memoization reduces the cost, this still incurs overhead on each render cycle. Consider using Fluxor's SubscribeToAction or reacting to ProvidersState changes more selectively.


200-208: Cache the RowStyleFunc delegate to avoid unnecessary re-renders.

The expression-bodied property creates a new delegate instance on each access, which may cause MudDataGrid to detect a "changed" parameter and re-render unnecessarily.

♻️ Suggested fix: use a readonly field or method

Option 1: Readonly field (preferred)

 private bool _showOnlyPending = false;
+private readonly Func<ModuleProviderDto, int, string> _rowStyleFunc = (x, _) =>
+    x.VerificationStatus == VerificationStatus.Pending.ToString()
+        ? "background-color: var(--mud-palette-warning-hover); font-weight: 500;"
+        : "";

Then in the markup:

-RowStyleFunc="@RowStyleFunc"
+RowStyleFunc="@_rowStyleFunc"

Option 2: Private method

-private Func<ModuleProviderDto, int, string> RowStyleFunc => (x, i) =>
-{
-    if (x.VerificationStatus == VerificationStatus.Pending.ToString())
-        return "background-color: var(--mud-palette-warning-hover); font-weight: 500;";
-        
-    return "";
-};
+private string GetRowStyle(ModuleProviderDto provider, int index) =>
+    provider.VerificationStatus == VerificationStatus.Pending.ToString()
+        ? "background-color: var(--mud-palette-warning-hover); font-weight: 500;"
+        : "";

Then use RowStyleFunc="@GetRowStyle" in the markup.

src/Web/MeAjudaAi.Web.Admin/Pages/Dashboard.razor (1)

265-270: Consider using ProviderTypeOrderInts for consistent semantic ordering.

ProviderTypeOrderInts is defined at line 183-186 but unused. The status chart uses StatusOrderInts for semantic ordering while types use alphabetical sort, creating inconsistency.

Optional: Apply same ordering pattern as status chart
         // Provider Type Chart Data
         var typeGroups = providers
             .Where(p => !string.IsNullOrEmpty(p.ProviderType))
             .GroupBy(p => p.ProviderType)
-            .OrderBy(g => g.Key) // Simple alphabetical sort for now, or assume string matches Enum names
+            .OrderBy(g => {
+                if (int.TryParse(g.Key, out int typeValue))
+                {
+                    var index = Array.IndexOf(ProviderTypeOrderInts, typeValue);
+                    return index >= 0 ? index : int.MaxValue;
+                }
+                return int.MaxValue;
+            })
             .ToList();

…ts management, including new documentation and development data seeding.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
docs/roadmap.md (2)

4004-4008: ⚠️ Potential issue | 🟡 Minor

Fix the corrupted heading glyph.
The replacement character breaks the heading rendering.

✏️ Suggested change
-#### � Resultado Esperado Sprint 8
+#### ✅ Resultado Esperado Sprint 8

4043-4064: ⚠️ Potential issue | 🟡 Minor

Clean up the migrations code block (contains unrelated prose).
The code sample is currently broken and will render incorrectly.

✏️ Suggested change
 ```csharp
 private static void EnsureDatabaseMigrations(WebApplication app)
 {
-    Keycloak client automation script (setup em 1 comando) - **DAY 1**
-- ✅ 0 analyzer warnings no Admin Portal (S2094, S2953, S2933, MUD0002 resolvidos)
-- ✅ 30-40 testes bUnit (10 → 30+, +200% cobertura)
-
-**Timeline**:
-- **Dia 1** (17 Jan): Keycloak automation script - **CRITICAL PATH**
-- **Semana 1** (17-24 Jan): Customer App Home + Busca + Warnings fix
-- **Semana 2** (24-31 Jan): Customer App Perfil + Mobile + Testes
-- **Semana 3** (31 Jan): PolishingVariable("APPLY_MIGRATIONS");
+    var applyMigrations = Environment.GetEnvironmentVariable("APPLY_MIGRATIONS");
     if (!string.IsNullOrEmpty(applyMigrations) && 
         bool.TryParse(applyMigrations, out var shouldApply) && !shouldApply)
     {
         logger?.LogInformation("Migrações automáticas desabilitadas via APPLY_MIGRATIONS=false");
         return;
     }
 
     // Aplicar migrations normalmente
     context.Database.Migrate();
 }

</details>

</blockquote></details>

</blockquote></details>
🤖 Fix all issues with AI agents
In `@docs/admin-portal/architecture.md`:
- Around line 39-48: Update the code fence in the architecture.md feature-tree
to specify a language (e.g., replace the opening ``` with ```text) so
markdownlint passes and rendering is clearer; apply this to the block that shows
the tree containing ProvidersState.cs, ProvidersActions.cs,
ProvidersReducers.cs, ProvidersEffects.cs, and ProvidersSelectors.cs so the tree
is treated as plain text by the renderer.

In `@docs/admin-portal/overview.md`:
- Around line 62-99: The fenced directory-tree code block lacks a language tag;
update the opening fence for the block that begins with
"src/Web/MeAjudaAi.Web.Admin/" from ``` to ```text so the directory tree is
marked as plain text (satisfying MD040); keep the block content unchanged and
only modify the opening fence.
🧹 Nitpick comments (3)
src/Web/MeAjudaAi.Web.Admin/Pages/Dashboard.razor (2)

110-116: @key bindings will force chart re-creation on every data update.

Using the array reference as @key means a new key on every UpdateChartData() call, causing full chart destruction and re-creation. If this is intentional for forcing MudBlazor chart re-renders, it's fine. If you want smoother updates, consider using a version counter or stable identifier instead.

Also applies to: 140-146


183-186: ProviderTypeOrderInts appears unused.

The array at lines 183-186 is defined but never referenced—the type grouping at line 268 uses alphabetical sorting instead. Consider either:

  1. Removing the unused array, or
  2. Applying semantic ordering similar to status (using ProviderTypeOrderInts) if a specific order is desired.
Option: Remove unused array
     private static readonly int[] StatusOrderInts = { 
         (int)VerificationStatus.Pending,
         (int)VerificationStatus.Verified,
         (int)VerificationStatus.Rejected
     };
-    private static readonly int[] ProviderTypeOrderInts = { 
-        (int)ProviderType.Individual,
-        (int)ProviderType.Company
-    };

Also applies to: 264-269

src/Web/MeAjudaAi.Web.Admin/Pages/Providers.razor (1)

124-139: Consider adding a using directive for cleaner code.

The query parameter parsing logic is correct. For improved readability, consider adding a @using Microsoft.AspNetCore.WebUtilities directive at the top of the file instead of using the fully qualified name inline.

♻️ Optional refactor

Add at the top with other using statements:

`@using` Microsoft.AspNetCore.WebUtilities

Then simplify the parsing:

-        var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query);
+        var queryParams = QueryHelpers.ParseQuery(uri.Query);

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@Directory.Packages.props`:
- Line 15: The PackageVersion entry for HtmlSanitizer pins an invalid NuGet
version ("9.0.890"); update the <PackageVersion Include="HtmlSanitizer"
Version="9.0.890" /> entry to a released version (e.g., Version="9.0.892" or
Version="9.0.889") so package restore succeeds—locate the PackageVersion element
with Include="HtmlSanitizer" and change the Version attribute accordingly.

In `@docs/admin-portal/architecture.md`:
- Around line 162-164: The example uses a Portuguese operation label in the call
to _errorHandler.HandleApiError ("carregar provedores"), which could flow into
logs/telemetry; change that label to an English operation description (e.g.,
"load providers") to comply with the English-only logging policy, or if it's
intended as a user-facing message instead of a log label, clarify that in the
example and leave user-facing text documented as such; update the string passed
to _errorHandler.HandleApiError and any related calls (also review the value
propagated into _snackbar.Add and LoadProvidersFailureAction) to ensure
consistency with the chosen intent.

In `@docs/admin-portal/overview.md`:
- Around line 19-22: Update the Frontend docs entry that currently reads
"MudBlazor 8.0" to the actual pinned version "MudBlazor 8.15.0" so it matches
the project's Directory.Packages.props; edit the "Frontend" section in
overview.md and replace the MudBlazor version string accordingly.
🧹 Nitpick comments (1)
docs/admin-portal/overview.md (1)

88-92: Clarify how constants align with Shared.Contracts to avoid duplication.

The Constants/ section reads like module-specific constants live in Web.Admin. If these are shared across backend/UI, consider pointing to Shared.Contracts (or add a note that only UI-specific constants live here).

Based on learnings: “For frigini/MeAjudaAi Web reviews, prefer commit-by-commit (newest-first) with concise verdicts and concrete follow-ups; prioritize reuse of Shared.Contracts for enums/constants to keep Web aligned with backend/shared code.”

…ncluding overview and architecture, and introduce new package lock files.
@github-actions
Copy link

github-actions bot commented Feb 5, 2026

📊 Code Coverage Report

Coverage: 90.25% (extracted from Cobertura (Aggregated Direct))

📈 Coverage Details

  • Coverage badges: Coverage
  • Minimum threshold: 80% (warning) / 90% (good)
  • Report format: Auto-detected from OpenCover/Cobertura XML files
  • Coverage source: Cobertura (Aggregated Direct)

📋 Coverage Analysis

  • Line Coverage: Shows percentage of code lines executed during tests
  • Branch Coverage: Shows percentage of code branches/conditions tested
  • Complexity: Code complexity metrics for maintainability

🎯 Quality Gates

  • Pass: Coverage ≥ 90%
  • ⚠️ Warning: Coverage 80-89%
  • Fail: Coverage < 80%

📁 Artifacts

  • Coverage reports: Available in workflow artifacts
  • Test results: TRX files with detailed test execution data

This comment is updated automatically on each push to track coverage trends.

@frigini frigini merged commit 0658bc8 into master Feb 5, 2026
8 checks passed
@frigini frigini deleted the refactor/fix-blazor-references branch February 5, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant