diff --git a/openspec/changes/laravel-admin/.openspec.yaml b/openspec/changes/laravel-admin/.openspec.yaml new file mode 100644 index 0000000..878dc31 --- /dev/null +++ b/openspec/changes/laravel-admin/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-07 diff --git a/openspec/changes/laravel-admin/README.md b/openspec/changes/laravel-admin/README.md new file mode 100644 index 0000000..c97046c --- /dev/null +++ b/openspec/changes/laravel-admin/README.md @@ -0,0 +1,3 @@ +# laravel-admin + +Laravel + Filament admin dashboard: users, tags (product/order assignment), products, orders, dashboard stats — shared Postgres, Docker dev on .27 diff --git a/openspec/changes/laravel-admin/design.md b/openspec/changes/laravel-admin/design.md new file mode 100644 index 0000000..fdb9782 --- /dev/null +++ b/openspec/changes/laravel-admin/design.md @@ -0,0 +1,41 @@ +## Context + +Phase 1–3 built the GOAT frontend + shared Postgres (users, tags, scans, products, orders on `.13:5434`). Phase 4 adds the Laravel admin: a management UI for users, tags (with the product/order linkage built in Phase 2.5), products/orders, and a dashboard. It reuses the same DB — no sync layer. + +## Goals / Non-Goals + +**Goals:** +- A working Laravel + Filament admin on .27 via Docker (no PHP installed natively). +- Reads/writes the shared Postgres on `.13:5434` (single source of truth). +- Admin login gated by `users.is_admin`; full CRUD for users/tags/products/orders; dashboard stats. + +**Non-Goals:** +- Billing/payments (Phase 5). +- Photo uploads in admin (frontend has it; admin can reuse photo_url display). +- Public-facing Laravel pages (this is admin-only for now). +- Laravel owning migrations yet — `db/schema.sql` stays canonical; Eloquent reads the existing tables. + +## Decisions + +1. **Docker dev on .27** — `admin/docker-compose.yml` with `php:8.3-fpm` + a composer bootstrap; `php artisan serve --host=0.0.0.0 --port=3030`. No PHP/Composer installed natively (keeps .27 clean). Docker volume mounts `admin/` so edits are live. +2. **Filament v3** for the admin panel (CRUD resources: User, Tag, Product, Order + dashboard stats). Rationale: the 2014 system needed forms-heavy admin; Filament is the Laravel-idiomatic generator and the best skills-update vehicle for Laravel conventions. +3. **Shared DB via Eloquent** — models map to the existing tables (users, tags, scans, products, orders). No migrations in Phase 4; `db/schema.sql` remains the source of truth (Laravel takes over migrations at deploy, generating from it). `users.is_admin` added via the existing idempotent ALTER in schema.sql. +4. **Auth** — Laravel's session auth + a middleware/policy: `Filament::auth()->user()->is_admin` gate. Seed one admin user (env-configured email, password from env). +5. **Connection** — `admin/.env` `DB_CONNECTION=pgsql`, `DB_HOST=192.168.20.13`, `DB_PORT=5434`, `DB_DATABASE=wherewoof`, `DB_USERNAME=wherewoof`, password via env (gitignored `.env`). + +## Risks / Trade-offs + +- [Docker image pulls + composer install are slow first-time] → one-time cost; cached after. +- [Laravel writing to a schema it doesn't own] → read-only via Eloquent where possible; writes (tag product/order assignment, order status) are simple column updates already exercised by the GOAT app; migrations reconciled at deploy. +- [Shared users table across both apps] → `is_admin` flag only; no schema conflict. +- [NixOS Docker networking to .13] → plain TCP to 192.168.20.13:5434, already proven by the Go app. + +## Migration Plan + +1. `make db-up` (adds `users.is_admin`). +2. `admin/docker-compose.yml` up → composer create-project laravel/laravel admin → filament install → models/resources. +3. Seed admin user; verify login + CRUD against the shared DB. + +## Open Questions + +- Whether the admin gets public-facing pages later (likely not — the GOAT frontend serves the public). diff --git a/openspec/changes/laravel-admin/proposal.md b/openspec/changes/laravel-admin/proposal.md new file mode 100644 index 0000000..101c3a1 --- /dev/null +++ b/openspec/changes/laravel-admin/proposal.md @@ -0,0 +1,34 @@ +## Why + +The product is owner-facing and working (Phases 1–3). What's missing is the **admin layer**: Where Woof needs a dashboard where the operator manages users, tags, products/orders, and sees what's happening (scans, alerts, lapsed payments). The 2014 system had this via `product_accounts`/`online_orders`; the schema for it (products, orders, tag linkage) was built in Phase 2.5 — Phase 4 delivers the management UI on top, as the long-planned **Laravel + Filament** system. + +## What Changes + +- **New Laravel app** (`admin/`) running via **Docker** on .27 for dev (php:8.3 + composer), connecting to the **shared Postgres** on `.13:5434` (same DB as the GOAT frontend — one source of truth). +- **Auth**: admin login (Laravel session auth) gated to admin users; new `users.is_admin` flag (idempotent ALTER) so the same `users` table serves both owners and admins. +- **Admin users**: list/search owner accounts, view details (tags, scans), suspend. +- **Admin tags**: list all tags (registry + owned), view each tag's scans/alerts, **assign `product_id` + `order_id`** (the Phase 2.5 linkage), suspend/unsuspend. +- **Products & orders**: CRUD product templates; record orders (status lifecycle pending/paid/lapsed/cancelled). +- **Dashboard**: stats — tags by status, scans in last 24 h, recent scans, lapsed orders. +- **No change** to the GOAT frontend or the shared schema beyond `users.is_admin`. + +## Capabilities + +### New Capabilities + +- `admin-auth`: admin login + gate; `users.is_admin`. +- `admin-users`: manage owner accounts. +- `admin-tags`: manage tags incl. product/order assignment + suspend. +- `admin-products-orders`: CRUD products + orders. +- `admin-dashboard`: operational stats. + +### Modified Capabilities + +- `database`: ADDED — `users.is_admin` column (idempotent). + +## Impact + +- **New**: `admin/` Laravel app (Filament panel), `admin/docker-compose.yml`, models (User, Tag, Scan, Product, Order), Filament resources, `.env` for the shared DB. +- **Schema**: idempotent `ALTER TABLE users ADD COLUMN IF NOT EXISTS is_admin BOOLEAN NOT NULL DEFAULT FALSE;` +- **Secrets**: `DB_*` env in `admin/.env` (gitignored); no real secrets committed. +- No changes to the GOAT frontend code paths. diff --git a/openspec/changes/laravel-admin/specs/admin-auth/spec.md b/openspec/changes/laravel-admin/specs/admin-auth/spec.md new file mode 100644 index 0000000..de2df17 --- /dev/null +++ b/openspec/changes/laravel-admin/specs/admin-auth/spec.md @@ -0,0 +1,19 @@ +## ADDED Requirements + +### Requirement: Admin login +The system SHALL provide a login for admin users at `/admin/login`. Only users with `is_admin = true` SHALL be able to access the admin panel. + +#### Scenario: Admin logs in +- **WHEN** an admin user logs in with valid credentials +- **THEN** they reach the admin dashboard + +#### Scenario: Non-admin blocked +- **WHEN** an owner (not admin) tries to access the admin panel +- **THEN** access is denied (403 / redirect) + +### Requirement: is_admin flag +The `users` table SHALL have an `is_admin` boolean, default false. + +#### Scenario: Promote a user +- **WHEN** an existing user is marked admin +- **THEN** they can log into the admin panel diff --git a/openspec/changes/laravel-admin/specs/admin-dashboard/spec.md b/openspec/changes/laravel-admin/specs/admin-dashboard/spec.md new file mode 100644 index 0000000..1294004 --- /dev/null +++ b/openspec/changes/laravel-admin/specs/admin-dashboard/spec.md @@ -0,0 +1,8 @@ +## ADDED Requirements + +### Requirement: Dashboard stats +The admin dashboard SHALL show operational numbers: tags by status (unset/active/suspended), scans in the last 24 hours, recent scans with tag + owner, and lapsed orders. + +#### Scenario: View dashboard +- **WHEN** an admin opens the dashboard +- **THEN** the stats and recent activity are displayed diff --git a/openspec/changes/laravel-admin/specs/admin-products-orders/spec.md b/openspec/changes/laravel-admin/specs/admin-products-orders/spec.md new file mode 100644 index 0000000..cb347eb --- /dev/null +++ b/openspec/changes/laravel-admin/specs/admin-products-orders/spec.md @@ -0,0 +1,15 @@ +## ADDED Requirements + +### Requirement: Product CRUD +The admin SHALL create, edit, and list product templates (sku unique, name, item type). + +#### Scenario: Create a product +- **WHEN** an admin creates a product with a unique sku and name +- **THEN** it appears in the products list and can be assigned to tags + +### Requirement: Order CRUD with lifecycle +The admin SHALL create and edit orders with the status lifecycle `pending → paid → lapsed → cancelled`. + +#### Scenario: Advance an order status +- **WHEN** an admin changes an order from pending to paid +- **THEN** the order reflects the paid status diff --git a/openspec/changes/laravel-admin/specs/admin-tags/spec.md b/openspec/changes/laravel-admin/specs/admin-tags/spec.md new file mode 100644 index 0000000..0e9562d --- /dev/null +++ b/openspec/changes/laravel-admin/specs/admin-tags/spec.md @@ -0,0 +1,22 @@ +## ADDED Requirements + +### Requirement: List and manage tags +The admin SHALL list all tags (registry + owned), searchable, with status and owner shown. + +#### Scenario: Browse tags +- **WHEN** an admin opens the tags list +- **THEN** all tags are shown with owner, status, product/order + +### Requirement: Assign product and order +The admin SHALL assign a `product_id` and `order_id` to a tag (the Phase 2.5 linkage). + +#### Scenario: Link a tag to a sale +- **WHEN** an admin sets product + order on a tag and saves +- **THEN** the tag row carries both foreign keys + +### Requirement: Suspend / unsuspend a tag +The admin SHALL suspend or unsuspend a tag (existing `SetTagStatus` behaviour). + +#### Scenario: Suspend a tag +- **WHEN** an admin suspends a tag +- **THEN** the tag page shows unavailable and alerts stop diff --git a/openspec/changes/laravel-admin/specs/admin-users/spec.md b/openspec/changes/laravel-admin/specs/admin-users/spec.md new file mode 100644 index 0000000..da9c1ae --- /dev/null +++ b/openspec/changes/laravel-admin/specs/admin-users/spec.md @@ -0,0 +1,15 @@ +## ADDED Requirements + +### Requirement: List and view users +The admin SHALL list owner accounts (searchable) and view a user's details: contact info, tags owned, recent scans. + +#### Scenario: Find a user +- **WHEN** an admin searches by email/name +- **THEN** matching accounts are listed + +### Requirement: Suspend a user +The admin SHALL be able to suspend a user, which prevents new alert SMS for their tags (via existing tag status). + +#### Scenario: Suspend then re-enable +- **WHEN** an admin suspends a user and later re-enables them +- **THEN** the state flips accordingly and tag alerts reflect it diff --git a/openspec/changes/laravel-admin/specs/database/spec.md b/openspec/changes/laravel-admin/specs/database/spec.md new file mode 100644 index 0000000..cc1d714 --- /dev/null +++ b/openspec/changes/laravel-admin/specs/database/spec.md @@ -0,0 +1,12 @@ +## ADDED Requirements + +### Requirement: users.is_admin column +The `users` table SHALL include an `is_admin` boolean column, NOT NULL default FALSE, added idempotently. + +#### Scenario: Fresh database +- **WHEN** `db/schema.sql` is applied to an empty database +- **THEN** `users.is_admin` exists defaulting to FALSE + +#### Scenario: Existing database +- **WHEN** `db/schema.sql` is re-applied to an existing database +- **THEN** the column is added without error diff --git a/openspec/changes/laravel-admin/tasks.md b/openspec/changes/laravel-admin/tasks.md new file mode 100644 index 0000000..cb08495 --- /dev/null +++ b/openspec/changes/laravel-admin/tasks.md @@ -0,0 +1,24 @@ +## 1. Schema & Scaffold + +- [ ] 1.1 `db/schema.sql`: idempotent `ALTER TABLE users ADD COLUMN IF NOT EXISTS is_admin BOOLEAN NOT NULL DEFAULT FALSE;`; `make db-up` +- [ ] 1.2 `admin/docker-compose.yml` (php:8.3-fpm + composer bootstrap, port 3030, volume mount admin/); bootstrap `laravel/laravel` via composer image; `admin/.env` → shared DB on .13:5434 (gitignored) + +## 2. Laravel Core + +- [ ] 2.1 Eloquent models: User (is_admin), Tag, Scan, Product, Order with relationships (Tag belongsTo Product/Order; User hasMany Tag; Tag hasMany Scan) +- [ ] 2.2 Filament install (v3) + admin panel provider; admin auth gate (is_admin); seed admin user (env email/password) + +## 3. Filament Resources + +- [ ] 3.1 UserResource (list/search, view with tags+scans, suspend) +- [ ] 3.2 TagResource (list/search, view scans, product/order assignment selects, suspend/unsuspend) +- [ ] 3.3 ProductResource (CRUD sku/name/item_type) +- [ ] 3.4 OrderResource (CRUD, status lifecycle) +- [ ] 3.5 Dashboard widgets (tags by status, scans 24h, recent scans, lapsed orders) + +## 4. Verification + +- [ ] 4.1 Admin login works (is_admin user); owner (non-admin) blocked +- [ ] 4.2 CRUD smoke: create product, create order, assign product+order to a tag, suspend a tag, update order status +- [ ] 4.3 GOAT frontend unaffected (suites 53/53 on shared DB) +- [ ] 4.4 `openspec validate laravel-admin`; commit