diff --git a/AGENTS.md b/AGENTS.md index 0698952..742ac56 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,8 @@ # AGENTS.md — Where Woof (resume brief) > The **source of truth is `where_woof.md`** (product plan + roadmap) and the **`openspec/` changes**. This file is a quick-resume overview for any agent or session picking the project back up. +> +> **Fuller docs: `docs/ARCHITECTURE.md` (how systems tie together + where things run), `docs/TOOLS.md` (toolchain), `docs/RESUME-HIGHLIGHTS.md` (resume-ready summary).** ## What this is diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..5f8dfcc --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,68 @@ +# Where Woof — Architecture + +How the system ties together, where everything runs, and how data flows. + +## Systems at a glance + +``` + ┌─────────────────────────────────────────────────┐ + Finder scans tag ──► │ where-woof.com / www / .home.lab (public) │ + (QR / NFC → URL) │ Caddy (.35, TLS) ──► .13:3020 │ + │ GOAT frontend (Go + HTMX + Alpine + Tailwind) │ + │ systemd user service `where-woof` │ + └───────────────────┬─────────────────────────────┘ + │ same Postgres + ┌───────────────────▼─────────────────────────────┐ + Staff manage ──► │ admin.where-woof.com / www.admin (staff) │ + │ Caddy (.35, TLS) ──► .13:3031 │ + │ Laravel + Filament admin (Docker) │ + └───────────────────┬─────────────────────────────┘ + │ + ┌───────────────────▼─────────────────────────────┐ + │ Postgres 16 `wherewoof-db` (.13:5434) │ + │ tables: users, tags, scans, products, orders │ + └─────────────────────────────────────────────────┘ +``` + +- **Public path**: tag QR/NFC → `where-woof.com/t/` → Caddy on `.35` → Go app on `.13:3020` → Postgres on `.13:5434`. Geolocation + alerts send SMS via SMSGlobal HTTP API. +- **Admin path**: staff → `admin.where-woof.com` → Caddy → Laravel/Filament on `.13:3031` (Docker) → the **same** Postgres. +- **Single source of truth**: one database, two apps. `db/schema.sql` is canonical (Laravel reads the existing tables via Eloquent; migration ownership moves to Laravel later). + +## Where things run + +| Machine | Role | Runs | +|---|---|---| +| **.27** (desktop) | Dev | Go frontend (`make run`, :3020), Laravel admin (Docker, :3030) — dev copies; build machine for the Go binary | +| **.13** (server) | Production | `wherewoof-db` Postgres (Docker, :5434), frontend (systemd user service `where-woof`, :3020), Laravel admin (Docker, :3031) | +| **.35** (caddy) | Reverse proxy | Caddy — TLS termination for all public domains | +| Router | Public entry | Port-forwards 80/443 → .35; DNS A records → `144.6.86.11` | +| InMotion | DNS/legacy | `where-woof.com` zone — A records → router IP (legacy site retired) | + +## Data model (v2 + additions) + +- `users` — owners + admins (`is_admin`), `password_hash`, `remember_token` +- `tags` — the registry: `tag_code` (opaque AES-256-encrypted sequential IDs 0–99), status unset/active/suspended, owner, item details, `sms_enabled`, `product_id`, `order_id` +- `scans` — every tag visit: lat/lng, `location_shared`, `scanner_phone`, `alert_sent`, `fingerprint` +- `products` — product templates (sku, name, item type) +- `orders` — sales: status pending/paid/lapsed/cancelled, linked to tags + +## Key flows + +1. **Claim**: owner registers → adds `tag_code` → must exist in registry (anti-scam) → edits details → status `active`. +2. **Scan**: finder opens `/t/` → JS geolocation + fingerprint → POST scan → recorded → alert decision (sms_enabled → fingerprint 24 h block → 10-min/250 m throttle → different-finder rule) → SMS to owner via SMSGlobal HTTP API (pooled/verified number). +3. **Contact**: finder leaves number → stored on scan → owner SMS'd (deduped). +4. **Admin**: manage users/tags/products/orders; assign tag → product/order; suspend; dashboard stats. + +## Security & anti-abuse + +- Registry-only tag codes (preset IDs; case-sensitive; no user-created codes). +- Location-aware alert throttle (250 m / 10 min), different-finder re-alert, 24 h device fingerprint block. +- Sessions: signed cookies (Go), Laravel sessions (admin); bcrypt passwords. +- SMS cost protection via the above + per-tag `sms_enabled` kill-switch. + +## Deployment + +- Frontend: `make build` on .27 → rsync to `.13:/var/www/where-woof` → systemd restart. +- Admin: rsync `admin/` → `.13:/home/sam/Docker/Containers/wherewoof-admin/` → `docker compose up -d` (rebuild image on schema/code change). +- Secrets: env-only — `~/.config/where-woof.env` (.13 frontend), `admin/.env` (gitignored). +- Docs: see `TOOLS.md` (toolchain) and `where_woof.md` (product plan + roadmap). diff --git a/docs/RESUME-HIGHLIGHTS.md b/docs/RESUME-HIGHLIGHTS.md new file mode 100644 index 0000000..3166026 --- /dev/null +++ b/docs/RESUME-HIGHLIGHTS.md @@ -0,0 +1,25 @@ +# Where Woof — Project Highlights (resume-ready) + +A production return-item tag platform built end-to-end on a home-lab stack, from spec to live deployment. + +## What it is +A SaaS-style product: physical QR/NFC tags for pets/luggage; a finder scans → sees return details → the owner is alerted by SMS with the finder's location. Includes a full admin dashboard for users, tags, products and orders. + +## What I built (the pipeline) +1. **Spec-driven development** — OpenSpec: every feature went through proposal → design → specs (testable requirements) → tasks, validated, and tracked through the whole project. +2. **Public web app (Go)** — "GOAT" stack: Go stdlib + HTMX + Alpine.js + Tailwind + Postgres. Auth, tag registry (anti-scam), scan flow with geolocation, location-aware SMS throttling, browser-fingerprint anti-spam, photo uploads. +3. **Admin app (Laravel + Filament)** — CRUD for users/tags/products/orders, dashboard stats, admin auth gating — against the **same** shared Postgres. +4. **Real SMS integration** — SMSGlobal HTTP API (verified sender), with a swappable sender abstraction and a log-mode fake for testing. +5. **Deployment** — Go binary as a systemd service, Laravel in Docker, Caddy reverse proxy with automatic TLS, DNS migration off legacy hosting, internal DNS (Pi-hole), firewall management on NixOS. +6. **AI-assisted engineering workflow** — used the pi coding agent with subagent delegation (database/coder/review agents), plan review tooling, memory/runbooks, and regression test suites (53 HTTP-level checks). + +## Stack highlights +Go 1.26 · HTMX · Alpine.js · Tailwind · PostgreSQL 16 · sqlc (type-safe SQL) · Laravel 13 · Filament v3 · Docker · NixOS · systemd · Caddy · Gitea · SMSGlobal API · OpenSpec · pi/agent tooling + +## Things I'm proud of / learned +- Decrypted a legacy AES-256-ECB product-ID scheme (100 encrypted sequential IDs recovered and re-seeded as a tamper-proof registry). +- Debugged and fixed production-only issues: Filament's 403-after-login (model must implement `FilamentUser`), mixed-content asset blocking behind a reverse proxy, and a Caddy config crash from duplicate site blocks. +- Rebuilt my own 2014-era product design (its comments anticipated distance throttling and fingerprint anti-spam) with modern tooling. + +## Where it runs +Live on `where-woof.com` and `admin.where-woof.com` — home-lab infra: desktop (.27, dev) → server (.13, prod) → Caddy box (.35), one shared Postgres. diff --git a/docs/TOOLS.md b/docs/TOOLS.md new file mode 100644 index 0000000..7f229a9 --- /dev/null +++ b/docs/TOOLS.md @@ -0,0 +1,67 @@ +# Where Woof — Toolchain + +Everything used to build and run the project, with where it fits. + +## Product stack (public site — "GOAT") + +| Tool | Version | Used for | +|---|---|---| +| Go | 1.26 | Frontend: stdlib `net/http` (no framework), `html/template`, Go 1.22+ ServeMux routing | +| HTMX | 1.9.12 | Inline editing, account panel swaps, contact forms (no page reloads) | +| Alpine.js | 3.x | Live phone validation, small client behaviours | +| Tailwind CSS | CDN | Utility-first styling (swap to a build step in Phase 3 polish later) | +| Postgres | 16 | Single shared database (`wherewoof-db` on .13) | +| pgx | v5 | Postgres driver + pool for Go | +| sqlc | 1.31.1 | Type-safe query generation from SQL (compiled-checked queries) | +| gorilla/sessions | — | Signed cookie sessions | +| bcrypt | — | Password hashing | + +## Admin stack + +| Tool | Version | Used for | +|---|---|---| +| Laravel | 13 (PHP 8.3) | Admin backend; Eloquent reads the shared Postgres schema | +| Filament | v3 | Admin panel UI: CRUD resources (User/Tag/Product/Order), dashboard widgets | +| Livewire | (Filament dep) | Reactive admin pages | +| Docker | — | Laravel dev (.27:3030) + prod (.13:3031); Postgres container | + +## Infrastructure & deployment + +| Tool | Role | +|---|---| +| NixOS | .27/.13/.51 hosts; systemd user services; firewall via config | +| systemd | `where-woof.service` (frontend, auto-restart, linger) | +| Docker Compose | Postgres, admin, other services | +| Caddy | Reverse proxy + TLS on .35 (public domains) | +| Pi-hole | Local DNS (`.home.lab` domains) | +| rsync | Deploy artifacts to .13 | +| Gitea | Git hosting (`sam/where_woof`) | + +## SMS + +| Tool | Role | +|---|---| +| SMSGlobal HTTP API | Live SMS (`api.smsglobal.com/http-api.php`, action=sendsms) — verified number as sender | +| `internal/sms` (Go) | Swappable `Sender` interface: `LogSender` (dev), `HTTPClient` (live), `smsglobal` REST client (unused fallback) | + +## Development workflow + +| Tool | Role | +|---|---| +| OpenSpec | Spec-driven development: `openspec/changes/*` (proposal → design → specs → tasks), validated | +| plannotator | Plan review (browser UI) for the first two phases | +| pi subagents | Master/worker delegation: `database`, `coder-pro`, `code-analysis`, `devops` | +| Makefile | `db-up`, `seed`, `seed-registry`, `run`, `build`, `generate`, `psql` | +| Verify suites | `/tmp/verify.sh` (22 checks), `verify2.sh` (19), `verify3.sh` (12) — HTTP-level regression tests | + +## Security tooling (guards package installs) + +SafeDep Vet (`vet`), npq (typosquatting), Socket Firewall (`socket`) — the npm-security skill wraps installs. + +## Notes / gotchas (hard-won) + +- Filament v3: production 403 unless the User model implements `FilamentUser`. +- Behind Caddy, force `URL::forceScheme('https')` to avoid mixed-content asset blocks. +- sqlc: nullable columns → `pgtype.*`; explicit INSERT column lists must include new columns. +- NixOS: `CGO_ENABLED=0` for Go builds (no gcc); `make` via nix profile; `psql` via `docker exec`. +- Admin files created by Docker are root-owned — edit via `docker cp`.