From df4ebdb7c6e77f790ca343c1903e6a8ee49eceda Mon Sep 17 00:00:00 2001 From: Ovidiu U Date: Wed, 10 Jun 2026 10:55:34 +0100 Subject: [PATCH] Remove Livewire docs, add Vue SPA frontend docs, document log-masking runbook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete `.claude/rules/livewire.md` (Livewire is vestigial – only starter-kit auth screens remain) - Add `.claude/rules/frontend.md` documenting Vue 3 SPA stack, router, composables, search-URL mirroring, and Leaflet integration - Add `docs/ops/nginx-log-masking.md` runbook for redacting lat/lng from Nginx access/error logs and PHP-FPM on IONOS VPS - Update `CLAUDE.md` and `architecture.md` to reflect Vue SPA as primary frontend, REST API as backend contract, and Livewire's reduced role - Note stale service names in legacy domain docs (`scoring.md`, `prediction.md`, `notifications.md`) now refactored into `Services/Forecasting/`, `Services/StationSearch/`, and `PlanFeatures` --- .claude/rules/architecture.md | 131 +++++++++++++++------ .claude/rules/frontend.md | 74 ++++++++++++ .claude/rules/livewire.md | 61 ---------- CLAUDE.md | 35 +++++- docs/ops/nginx-log-masking.md | 214 ++++++++++++++++++++++++++++++++++ 5 files changed, 413 insertions(+), 102 deletions(-) create mode 100644 .claude/rules/frontend.md delete mode 100644 .claude/rules/livewire.md create mode 100644 docs/ops/nginx-log-masking.md diff --git a/.claude/rules/architecture.md b/.claude/rules/architecture.md index 3e53155..2c3552c 100644 --- a/.claude/rules/architecture.md +++ b/.claude/rules/architecture.md @@ -1,57 +1,118 @@ # Architecture +## Shape: Vue SPA ⇄ REST API ⇄ fat Laravel Services + +The frontend is a **Vue 3 single-page app** (`resources/js/`). It talks to the +backend exclusively over a **REST API** (`routes/api.php` → +`app/Http/Controllers/Api/`). Controllers stay thin; all business logic lives in +**Service classes** (`app/Services/`). Blade is reduced to a one-line SPA shell, +server-rendered legal pages, transactional emails, and the Filament admin panel. + +``` +Browser + └── Vue SPA (resources/js, mounted on #app via app.blade.php) + └── axios (resources/js/axios.js — baseURL '/api', cookie + XSRF auth) + └── REST API (routes/api.php → Http/Controllers/Api/*) + └── Services (app/Services/*) ← all business logic + └── Models / Jobs / Events / Notifications +``` + ## Core principle: fat Services, thin everything else -All business logic lives in Service classes. Controllers, Livewire components, -and console commands are thin orchestrators — they call Services and return results. -This keeps the app API-extractable later without a rewrite. +All business logic lives in Service classes. API controllers, console commands, +jobs, listeners, and Filament resources are thin orchestrators — they call +Services and return results. Never put domain logic in a controller or a Vue +component. -## Directory structure +## Directory structure (actual) ``` app/ -├── Console/Commands/ # Scheduler commands (PollFuelPrices, PredictOilPrices, RunScoringEngine) -├── Http/Controllers/ # Minimal — auth + Stripe webhook only -├── Livewire/ # Classic two-file Livewire components -├── Models/ # Eloquent models -├── Notifications/ # Laravel Notification classes (multi-channel) -├── Services/ # ALL business logic lives here -│ ├── FuelPriceService.php # Fuel Finder API polling + storage -│ ├── AlertScoringService.php # Fill-up timing recommendation engine -│ ├── StationTaggingService.php # Supermarket brand detection -│ ├── NotificationDispatchService.php # Tier-aware notification routing -│ ├── SubscriptionService.php # Cashier/tier helpers -│ ├── PostcodeService.php # Resolves postcodes/outcodes/place names → lat/lng -│ ├── OilPriceService.php # FRED fetch + EWMA/LLM Brent crude prediction -│ ├── LocationResult.php # DTO returned by PostcodeService -│ └── ApiLogger.php # Wraps all outbound HTTP calls, logs to api_logs -└── Jobs/ # Queued jobs (dispatch notifications per user) +├── Console/Commands/ # Scheduler: PollFuelPrices, FetchOilPrices, BackfillOilPrices, +│ # RunLlmOverlay, EvaluateVolatilityRegime, ResolveForecastOutcomes, +│ # ArchiveOldPricesCommand, ImportBeisFuelPrices, ImportPostcodes +├── Http/ +│ ├── Controllers/Api/ # AuthController, StationController, StatsController, UserController +│ ├── Controllers/ # BillingController (Stripe Checkout/Portal redirects) +│ ├── Middleware/ # VerifyApiKey (API-key gate), RequiresFeature (tier gate) +│ ├── Requests/ # Form requests +│ └── Resources/Api/ # StationResource (API output shaping) +├── Actions/Fortify/ # Fortify auth actions (CreateNewUser, …) +├── Services/ # ALL business logic — see below +├── Models/ # Eloquent models +├── Notifications/ # FuelPriceAlert + custom Channels (OneSignal, Vonage WA/SMS) +├── Jobs/ # DispatchUserNotificationJob, PollFuelPricesJob, +│ # SendScheduledWhatsAppJob, SendPaymentFailedReminderJob +├── Events/ # PricesUpdatedEvent +├── Listeners/ # HandleStripeWebhook (Cashier WebhookReceived) +├── Filament/ # Admin panel (Resources, Widgets) + Providers/Filament +├── Enums/ # FuelType, PlanTier, … +└── Livewire/Actions/ # Logout only — Livewire is otherwise vestigial -resources/views/ -├── livewire/ # Livewire Blade templates -└── emails/ # Mailable templates +app/Services/ +├── FuelPriceService.php # Fuel Finder API polling + storage +├── StationTaggingService.php # Supermarket brand detection +├── PostcodeService.php # postcode/outcode/place → lat/lng (+ LocationResult DTO) +├── PlanFeatures.php # Single source of tier-entitlement truth (see tiers.md) +├── HaversineQuery.php # Distance SQL helper +├── ApiLogger.php # Wraps outbound HTTP, logs to api_logs +├── StationSearch/ # StationSearchService (+ SearchCriteria, SearchResult DTOs) +├── BrentPriceSources/ # FRED + EIA Brent sources (+ BrentPriceFetcher) +└── Forecasting/ # Weekly forecast subsystem — WeeklyForecastService, + # LlmOverlayService, BacktestRunner, OutcomeResolver, + # VolatilityRegimeService, feature/leak tooling, … + +resources/ +├── js/ # Vue 3 SPA — see .claude/rules/frontend.md +└── views/ + ├── app.blade.php # SPA shell (mounts #app) + ├── legal/ # Server-rendered legal pages + ├── emails/ # Mailable templates + └── livewire/auth/ # Starter-kit Volt auth screens (left untouched) routes/ -├── web.php # All web routes (Livewire pages) -└── api.php # Empty for now — API added later if needed +├── web.php # SPA shell + catch-all, legal pages, billing redirects, server logout +└── api.php # REST API consumed by the SPA (see below) ``` +> **Stale service names elsewhere.** Domain rule files (`scoring.md`, +> `prediction.md`, `notifications.md`) still name services that have since been +> refactored away — e.g. `AlertScoringService`, `OilPriceService`, +> `NationalFuelPredictionService`, `NotificationDispatchService`, +> `SubscriptionService`. Those concerns now live under `Services/Forecasting/`, +> `Services/StationSearch/`, and `PlanFeatures`. Always verify a service name +> against `app/Services/` before trusting it from those files. + ## Service class conventions - Constructor injection only — no facade usage inside Services -- Services are bound in AppServiceProvider if they need interfaces - Each Service has one responsibility — do not merge concerns - Return typed DTOs or Eloquent collections — never raw arrays from Services -- Services never dispatch jobs directly — that's the controller/command's job +- Services never dispatch jobs directly — that's the controller/command/listener's job -## No API yet +## The REST API -`routes/api.php` stays empty for v1. Do not create API controllers or Sanctum -token auth. The app is Livewire-only until subscriber count justifies a native app. -When the API is added later, it will reuse the same Service classes. +`routes/api.php` is the SPA's backend. Three access tiers: -## Livewire components (classic only) +- **Public** (no auth): `POST /api/auth/register`, `POST /api/auth/login`, + `GET /api/auth/me`, `GET /api/fuel-types`, `GET /api/stats/live` +- **API-key** (`VerifyApiKey` + `throttle:60,1`): `GET /api/stations`, + `GET /api/stats/searches` +- **Sanctum** (`auth:sanctum`): `POST /api/auth/logout`, `/api/user/*` + (preferences, saved stations, profile, password, delete account) -Use two-file classic Livewire components. Do NOT use Volt single-file syntax. -Volt files from the starter kit (auth screens) are left as-is — do not convert them. -New components go in `app/Livewire/` with corresponding Blade in `resources/views/livewire/`. +Conventions: + +- Shape responses with API Resources (`app/Http/Resources/Api/`), never raw arrays. +- The fuel **prediction is embedded in the `/api/stations` response** under the + `prediction` key — there is no standalone prediction endpoint (see `prediction.md`). +- Tier-gate routes with the `feature` middleware (`RequiresFeature`); never + inline entitlement checks (see `tiers.md`). +- Auth is cookie/session via Sanctum SPA mode (axios sends XSRF + credentials); + the API-key gate protects the expensive station search from scraping. + +## Frontend + +The Vue SPA is documented in `.claude/rules/frontend.md`. Do **not** build new +Livewire components — Livewire/Flux remain only for the starter-kit auth screens +and the `Logout` action. diff --git a/.claude/rules/frontend.md b/.claude/rules/frontend.md new file mode 100644 index 0000000..5a6f1a5 --- /dev/null +++ b/.claude/rules/frontend.md @@ -0,0 +1,74 @@ +# Frontend — Vue 3 SPA + +The entire user-facing app (landing, station search, dashboard, settings) is a +**Vue 3 single-page application** under `resources/js/`. It is served from the +Blade shell `resources/views/app.blade.php` via the SPA catch-all in +`routes/web.php` and consumes the REST API (`routes/api.php`). There is **no +Livewire in the product UI** — do not add Livewire / Volt / Alpine components for +new features. Build Vue. + +## Stack + +- Vue 3.5 with `