From cb53718051492f0df4415262e7d6ca7b88dba7a0 Mon Sep 17 00:00:00 2001 From: hokiepokedad2 <38219945+hokiepokedad2@users.noreply.github.com> Date: Wed, 5 Aug 2026 09:51:20 -0400 Subject: [PATCH] docs: remove stale AutoMapper references (#241) AutoMapper was dropped in v2.6.0 (#173) in favour of manual mapping extensions, but four docs pages still described it as the live mapping layer -- architecture/backend.md even carried a .ForAllMembers snippet that exists nowhere in the codebase. The mapping sections in architecture/overview.md and architecture/backend.md now describe AlarmMappingExtensions (To*() / ApplyUpdate()) and EntityMappingExtensions (ToModel() / ToEntity() / ApplyTo()), with a real ApplyUpdate snippet showing the explicit null-skip guards. The Core.Mappings/ line in the solution tree, the test-coverage bullet in development/testing.md, and a passing mention in architecture/poracleng-proxy.md are corrected to match. Also renames the mapping test file, which was still called PoracleMappingProfileTests.cs while the class inside it had already been renamed to MappingExtensionTests. Nothing referenced the old file name; all 66 tests pass unchanged. --- CHANGELOG.md | 1 + ...rofileTests.cs => MappingExtensionTests.cs} | 0 docs/architecture/backend.md | 18 +++++++++++++----- docs/architecture/overview.md | 6 +++--- docs/architecture/poracleng-proxy.md | 2 +- docs/development/testing.md | 2 +- 6 files changed, 19 insertions(+), 10 deletions(-) rename Tests/Pgan.PoracleWebNet.Tests/Mappings/{PoracleMappingProfileTests.cs => MappingExtensionTests.cs} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1f41ec..a39e980d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **OIDC refresh-token consumption — silent session renewal + revocation propagation** (opt-in, provider-agnostic): building on the OIDC login above, PoracleWeb can now optionally consume the provider's **refresh token** instead of discarding it, so an SSO session renews silently in the background (no 24-hour hard re-login) and a disable/logout at the provider propagates to PoracleWeb within one short access-token lifetime. It is **off by default** (`OIDC_USE_REFRESH_TOKENS=false`) — existing deployments and providers that don't issue refresh tokens are completely unaffected (the login cleanly falls back to a standard full-lifetime session). The provider refresh token is brokered **entirely server-side**: it's encrypted at rest with DataProtection in a new `oidc_sessions` table (added via EF migration `AddOidcSessions`) and **never sent to the browser**; the browser instead holds an opaque PoracleWeb token in `localStorage` that keys a rotation **family**. A new `POST /api/auth/oidc/refresh` endpoint redeems the stored refresh token against the provider, **re-validates the user live** (existence, `enable_oidc` gate, role access, admin-disable) on every refresh, rotates both tokens, and family-revokes on replay/reuse or when the provider rejects the refresh (revocation propagation); `POST /api/auth/oidc/refresh/revoke` ends a session on logout, and an `OidcSessionCleanupService` reaps expired/stale rows. Refresh-backed OIDC sessions get a short **per-login** JWT (`OIDC_ACCESS_TOKEN_MINUTES`, default 30) while Discord/Telegram/local logins keep the 24-hour JWT — the lifetime override is scoped so non-refresh logins aren't shortened. The implementation is **fully OIDC-provider-agnostic**: `OIDC_OFFLINE_ACCESS_SCOPE` (default `offline_access`) is appended to the authorize request so standards-compliant providers issue a refresh token; `OIDC_TOKEN_AUTH_METHOD` supports both `client_secret_post` and `client_secret_basic`; non-rotating providers (no new refresh token on refresh) are handled by carrying the prior token forward; and nothing relies on discovery/JWKS/`id_token`. The frontend adds a single-flight `TokenStoreService` + an `oidcRefreshInterceptor` (proactive pre-expiry refresh and reactive 401-retry, with a null-refresh-token guard so every non-refresh login keeps the existing "401 → logout" path). Refresh on/off is controlled solely by the `OIDC_USE_REFRESH_TOKENS` env flag — there is intentionally **no** runtime admin toggle, since refresh is coupled to the per-login JWT lifetime (disabling it mid-session would strand already-issued short-lived tokens); its active state is surfaced read-only on `GET /api/auth/providers` (`oidc.refresh`) and `GET /api/settings/oidc-config`. New `OIDC_*` keys documented in `.env.example` with a per-provider config matrix (PogoAlerts, Keycloak, Authentik, Auth0, Google, Azure AD/Entra, Okta), and a full **OIDC Refresh Tokens** documentation page (configuration reference, five Mermaid flow diagrams, the provider matrix, and the security model) added to the docs site. Backend tests cover the session rotation/replay/cap/cleanup mechanics and the provider-agnostic client (auth method, optional/non-rotating refresh tokens); frontend tests cover the token store's single-flight refresh and the interceptor's proactive/reactive/loop-guard behavior. ### Changed +- **Removed stale AutoMapper references from the docs site** ([#241](https://github.com/PGAN-Dev/PoracleWeb.NET/issues/241)): AutoMapper was dropped in v2.6.0 ([#173](https://github.com/PGAN-Dev/PoracleWeb.NET/issues/173)) in favour of manual mapping extensions, but four docs pages still described it as the live mapping layer — `architecture/backend.md` even carried a `.ForAllMembers(opts => opts.Condition(...))` snippet that exists nowhere in the codebase. The mapping sections in `architecture/overview.md` and `architecture/backend.md` now describe `AlarmMappingExtensions` (`To*()` / `ApplyUpdate()`) and `EntityMappingExtensions` (`ToModel()` / `ToEntity()` / `ApplyTo()`), with a real `ApplyUpdate` snippet showing the explicit null-skip guards; the `Core.Mappings/` line in the solution tree, the test-coverage bullet in `development/testing.md`, and a passing mention in `architecture/poracleng-proxy.md` are corrected to match. The last piece of AutoMapper residue outside the docs goes with it: the mapping test file was still named `PoracleMappingProfileTests.cs` while the class inside it had been renamed to `MappingExtensionTests`, so the file is renamed to match. No behaviour change. - **Localized the external SSO / OIDC strings** across all bundled locales. The SSO login feature added 30 i18n keys to English only, so every non-English locale fell back to English for the "Sign in with {provider}" button, the signed-out panel, the OIDC error messages, and the admin Authentication / External SSO settings group. These are now translated into Danish, German, Spanish, French, Italian, Dutch, Polish, Portuguese (PT & BR), and Swedish. Translation-only — no code or behavior change. - **Admin Server Settings page UX overhaul.** Adds a live **search/filter** (sticky bar, match highlighting, `/` or Ctrl/Cmd+K to focus), a **sticky save + discard bar** so saving is always reachable on the long page, **sign-in providers grouped under Authentication** (Telegram/Discord moved up), and **collapsible sections** (persisted) with per-section "unsaved" chips and state summaries (e.g. "7 of 9 enabled"). Headline fix: the alarm-type/feature toggles were a confusing **double negative** ("Disable X", ON = feature off) mixed with positive `enable_*` toggles; they are now **uniformly positive** (ON = enabled, labels are the feature name, descriptions are "Let users …"). The stored `disable_*` keys are **unchanged** — a presentation-only inversion — so backend feature-gating is unaffected. New UX i18n keys and the reframed positive labels/descriptions are translated across all 11 locales. diff --git a/Tests/Pgan.PoracleWebNet.Tests/Mappings/PoracleMappingProfileTests.cs b/Tests/Pgan.PoracleWebNet.Tests/Mappings/MappingExtensionTests.cs similarity index 100% rename from Tests/Pgan.PoracleWebNet.Tests/Mappings/PoracleMappingProfileTests.cs rename to Tests/Pgan.PoracleWebNet.Tests/Mappings/MappingExtensionTests.cs diff --git a/docs/architecture/backend.md b/docs/architecture/backend.md index 5b82a2b3..644c4d57 100644 --- a/docs/architecture/backend.md +++ b/docs/architecture/backend.md @@ -29,15 +29,23 @@ PoracleNG's tracking POST endpoint handles both creates and updates. When the re !!! note "`BaseRepository` removed" The generic `BaseRepository` and all alarm repository classes have been removed. `EnsureNotNullDefaults()` is no longer needed -- PoracleNG handles NULL defaults for alarm writes, and the remaining repositories handle null normalization as needed. -## AutoMapper (non-alarm entities only) +## Mapping extensions -AutoMapper is used for `humans` and `profiles` entities. Alarm tracking data flows as raw JSON through the PoracleNG API proxy and does not use AutoMapper. +Mapping is done with static extension methods in `Core.Mappings/`. There is no AutoMapper dependency. -All `*Update` models for non-alarm entities use **nullable `int?`** properties so partial updates don't zero out unset fields. +`AlarmMappingExtensions` covers the alarm DTOs: `To*()` builds a model from a `*Create` DTO (`create.ToMonster()`), and `ApplyUpdate()` merges a `*Update` DTO onto an existing model (`update.ApplyUpdate(existing)`). + +`EntityMappingExtensions` covers `Human`, `Profile`, and the `poracle_web`-owned entities (user geofences, site settings, webhook delegates, quick picks) with `ToModel()`, `ToEntity()`, and `ApplyTo()`. + +All `*Update` models use **nullable** properties so partial updates don't zero out unset fields. `ApplyUpdate` skips nulls explicitly: ```csharp -// The mapping profile skips null properties -.ForAllMembers(opts => opts.Condition((_, _, srcMember) => srcMember != null)) +public static void ApplyUpdate(this MonsterUpdate src, Monster dest) +{ + if (src.Ping != null) dest.Ping = src.Ping; + if (src.Distance != null) dest.Distance = src.Distance.Value; + // ... one guarded assignment per field +} ``` ## Alarm field defaults diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 67ace4b2..a649d60b 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -19,7 +19,7 @@ Pgan.PoracleWebNet.slnx ├── Core/ │ ├── Core.Abstractions/ Interfaces (IService, IPoracleTrackingProxy, IPoracleHumanProxy) │ ├── Core.Models/ DTOs passed between layers -│ ├── Core.Mappings/ AutoMapper profiles (Human, Profile, PoracleWeb.NET tables) +│ ├── Core.Mappings/ Mapping extension methods (alarm DTOs, Human, Profile, PoracleWeb.NET tables) │ ├── Core.Repositories/ Data access (Human, Profile, PoracleWeb-owned tables) │ └── Core.Services/ Business logic + PoracleNG API proxies ├── Data/ @@ -91,8 +91,8 @@ PoracleWeb.NET does **not** modify the Poracle DB schema. The Poracle database i ### Unified geofence feed PoracleWeb.NET acts as the single geofence source for PoracleJS. It fetches admin geofences from Koji, merges them with user-drawn geofences, and serves everything via one endpoint (`GET /api/geofence-feed`). No custom code needed in PoracleJS or Koji. User geofences support GeoJSON import/export for interoperability with external mapping tools. -### AutoMapper for partial updates -All update models use nullable `int?` properties so partial updates don't zero out unset fields. The mapping profile skips null properties automatically. Note: AutoMapper is now only used for non-alarm entities (humans, profiles). Alarm data flows as raw JSON through the PoracleNG API proxy. +### Manual mapping extensions +Mapping lives in static extension methods under `Core.Mappings/` -- there is no AutoMapper dependency. `AlarmMappingExtensions` provides `To*()` for `*Create` DTOs and `ApplyUpdate()` for `*Update` DTOs; `EntityMappingExtensions` provides `ToModel()`, `ToEntity()`, and `ApplyTo()` for Human, Profile, and the `poracle_web`-owned tables. Update models use nullable properties and `ApplyUpdate` skips nulls, so partial updates don't zero out unset fields. Alarm data itself flows as raw JSON through the PoracleNG API proxy. See [Backend Patterns](backend.md). ### Gym picker The `GymPickerComponent` (shared) lets users search for specific gyms when creating team, raid, or egg alarms. It calls the `ScannerService` (frontend) which hits scanner gym search endpoints on the backend (`ScannerController`). Search results use the `GymSearchResult` model and include photo thumbnails and area names resolved via the `PointInPolygon` geo utility. The scanner DB is optional — when not configured, the gym picker is hidden. diff --git a/docs/architecture/poracleng-proxy.md b/docs/architecture/poracleng-proxy.md index d4889006..5ae9bab8 100644 --- a/docs/architecture/poracleng-proxy.md +++ b/docs/architecture/poracleng-proxy.md @@ -145,7 +145,7 @@ See [PoracleNG Enhancement Requests](../poracleng-enhancement-requests.md) for t 3. Register the service in `ServiceCollectionExtensions.cs`. 4. Create the corresponding controller under `Controllers/`. -No repository, entity, or AutoMapper mapping is needed for alarm types -- the proxy handles all database interaction through PoracleNG. +No repository or entity is needed for alarm types -- the proxy handles all database interaction through PoracleNG. ## Registration diff --git a/docs/development/testing.md b/docs/development/testing.md index b9f09c70..e49c7ecb 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -31,7 +31,7 @@ Uses xUnit with Moq. Tests cover: - Human/profile services (`HumanServiceTests`, `ProfileServiceTests`) -- mock `IPoracleHumanProxy` for single-user ops, `IHumanRepository` for admin bulk ops - Active hours validation (`ActiveHoursValidationTests`) -- 17 tests for server-side active hours validation rules - Other services (`UserGeofenceServiceTests`, `CleaningServiceTests`, `DashboardServiceTests`, `SiteSettingServiceTests`, `WebhookDelegateServiceTests`, `SettingsMigrationServiceTests`, `QuickPickServiceSecurityTests`, `PokemonAvailabilityServiceTests`) -- AutoMapper mappings (non-alarm entities) +- Mapping extensions (`MappingExtensionTests`) -- alarm DTO `To*()` / `ApplyUpdate()` and entity `ToModel()` / `ToEntity()` / `ApplyTo()` !!! info "Alarm service tests mock IPoracleTrackingProxy" Since alarm services no longer use repositories, their tests mock `IPoracleTrackingProxy` instead of `IRepository`. The mock returns `JsonElement` values matching PoracleNG's snake_case JSON format.