Skip to content

Architecture Overview

PoracleWeb.NET is a full-stack application with a .NET 10 backend API and Angular 21 frontend SPA.

Solution structure

Pgan.PoracleWebNet.slnx
├── Applications/
│   ├── Web.Api/                    ASP.NET Core host
│   │   ├── Controllers/            REST API controllers (all under /api/)
│   │   ├── Configuration/          DI registration, settings classes
│   │   └── Services/               Background services (avatar cache, DTS cache)
│   └── Web.App/ClientApp/          Angular 21 SPA
│       └── src/app/
│           ├── core/               Guards, services, interceptors, models
│           ├── modules/            Feature pages (dashboard, pokemon, raids, etc.)
│           └── shared/             Reusable components, utilities
├── Core/
│   ├── Core.Abstractions/          Interfaces (IService, IPoracleTrackingProxy, IPoracleHumanProxy)
│   ├── Core.Models/                DTOs passed between layers
│   ├── 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/
│   ├── Data/                       EF Core DbContexts, Entities, Configurations
│   └── Data.Scanner/               Optional scanner DB context (Golbat)
└── Tests/
    └── Pgan.PoracleWebNet.Tests/     xUnit backend tests

Layer diagram

graph TB
    subgraph Frontend
        Angular[Angular 21 SPA
Material Design 3] end subgraph Backend["ASP.NET Core API"] Controllers[Controllers
20+ REST endpoints] AlarmServices[Alarm Services
MonsterService, RaidService, etc.] TrackingProxy[IPoracleTrackingProxy
HTTP proxy to PoracleNG] HumanProxy[IPoracleHumanProxy
HTTP proxy to PoracleNG] OtherServices[Other Services
Geofences, Settings, QuickPicks] Repositories[Repositories
Admin bulk ops, PoracleWeb DB] end subgraph Data PoracleDB[(Poracle DB
MySQL)] WebDB[(PoracleWeb DB
MySQL)] ScannerDB[(Scanner DB
Golbat · Optional)] end subgraph External PoracleNG[PoracleNG API
Alarm tracking + state reload] Koji[Koji Server] Discord[Discord API] Golbat[Golbat API
Pokemon availability] end Angular -->|HTTP / JWT| Controllers Controllers --> AlarmServices Controllers --> OtherServices AlarmServices --> TrackingProxy Controllers --> HumanProxy TrackingProxy -->|HttpClient
X-Poracle-Secret| PoracleNG HumanProxy -->|HttpClient
X-Poracle-Secret| PoracleNG PoracleNG --> PoracleDB OtherServices --> Repositories Repositories --> PoracleDB Repositories --> WebDB Repositories --> ScannerDB OtherServices -->|HttpClient| Koji OtherServices -->|HttpClient| Discord OtherServices -->|HttpClient| Golbat Controllers -->|Test alerts| PoracleNG

All operations go through PoracleNG

All alarm tracking CRUD (including Fort Change and Max Battle types) is proxied via IPoracleTrackingProxy. Single-user human/profile operations (reads, creates, location, areas, profile switch) are proxied via IPoracleHumanProxy. Direct database access is used for admin bulk operations (GetAllAsync, DeleteUserAsync, UpdateAsync), for profile rename, for user-geofence area writes, for the per-alarm override_areas column, and for application-owned data (poracle_web database). PoracleNG's schema_migrations table is also read directly, by the server capability probe. See Backend. Optional integrations include Pokemon availability from the Golbat API and weather from the optional scanner database via IScannerService. See PoracleNG API Proxy for details.

Key design decisions

Operations proxied through PoracleNG

All alarm tracking writes (create, update, delete) and single-user human/profile operations go through the PoracleNG REST API, not directly to the database. PoracleNG applies field defaults (cleanRow()), detects duplicates, handles area dual-writes, and triggers immediate state reload. This eliminates data integrity bugs caused by missing defaults or stale state. Profile duplication from the Profiles page (POST /api/profile-overview/duplicate/{n}) copies each alarm with its own CreateAsync call and rolls back on failure; only POST /api/profiles/duplicate uses PoracleNG's copy endpoint. Supported alarm types include Pokemon, Raids, Eggs, Quests, Invasions, Lures, Nests, Gyms, Fort Changes, and Max Battles. Test alerts are sent via PoracleNG's POST /api/test endpoint, which formats and delivers a mock notification to the user. See PoracleNG API Proxy.

Separate databases

PoracleWeb.NET does not modify the Poracle DB schema. The Poracle database is managed by PoracleNG. Application-owned data (user geofences, site settings, webhook delegates, quick pick definitions) lives in a separate poracle_web database managed by EF Core migrations.

Unified geofence feed

PoracleWeb.NET acts as the single geofence source for PoracleNG. 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 PoracleNG or Koji. User geofences support GeoJSON import/export for interoperability with external mapping tools.

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.

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.

Per-IP rate limiting

Auth endpoints use per-IP partitioned rate limiting (not global). This prevents one user's activity from locking out others.