openspec: laravel-admin change — Filament admin (users/tags/products/orders/dashboard) on shared Postgres
This commit is contained in:
2
openspec/changes/laravel-admin/.openspec.yaml
Normal file
2
openspec/changes/laravel-admin/.openspec.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-08-07
|
||||||
3
openspec/changes/laravel-admin/README.md
Normal file
3
openspec/changes/laravel-admin/README.md
Normal file
@@ -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
|
||||||
41
openspec/changes/laravel-admin/design.md
Normal file
41
openspec/changes/laravel-admin/design.md
Normal file
@@ -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).
|
||||||
34
openspec/changes/laravel-admin/proposal.md
Normal file
34
openspec/changes/laravel-admin/proposal.md
Normal file
@@ -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.
|
||||||
19
openspec/changes/laravel-admin/specs/admin-auth/spec.md
Normal file
19
openspec/changes/laravel-admin/specs/admin-auth/spec.md
Normal file
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
22
openspec/changes/laravel-admin/specs/admin-tags/spec.md
Normal file
22
openspec/changes/laravel-admin/specs/admin-tags/spec.md
Normal file
@@ -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
|
||||||
15
openspec/changes/laravel-admin/specs/admin-users/spec.md
Normal file
15
openspec/changes/laravel-admin/specs/admin-users/spec.md
Normal file
@@ -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
|
||||||
12
openspec/changes/laravel-admin/specs/database/spec.md
Normal file
12
openspec/changes/laravel-admin/specs/database/spec.md
Normal file
@@ -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
|
||||||
24
openspec/changes/laravel-admin/tasks.md
Normal file
24
openspec/changes/laravel-admin/tasks.md
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user