# WhereWoof — Local Pet Location Tracker ## Architecture — Two Separate Systems ### 1. Front-Facing Website (GOAT stack) Quick win — lightweight, public-facing, pet owner experience. | Detail | Value | |--------|-------| | **Stack** | Go + HTMX + Alpine.js + Tailwind CSS + SQLite (**GOAT stack**) | | **Audience** | Pet owners (family members) Lost pets| | **Purpose** | View pet info, update location, quick CRUD | | **Auth** | Minimal — HTTP basic auth or Tailscale-only (household, not public) | | **Deploy** | .13:3020 behind Caddy on .35 | | **DNS** | `where-woof.com` | URL format. https://where-woof.com/x=5&productid=TXQ3NHR4OGNuZGVwdFdkcFBTbm5BZz094 Will be redirected from live website to local .13 where caddy will handle local DNS. **IMPORTANT INFO** - Users scan QR code on tag or use NFC. This opens webiste at URL example. On load website asks to ALLOW USERS LOCATION to send info to owners. If request is granted location is sent back with productid and the server admin with send alerts via SMS, email of location of dog. Info of dog is displayed with phone number - press for automatic call hook into phone. Also a button if user declines location then they can press button to initiate again. Also input box for the person who has found the dog to input their mobile. Finder of dog is advised that number is not kept only sent to owner so they can contact you. ### 2. Admin Dashboard (Laravel) Separate system — full business management. Needs proper auth, billing, multi-user. | Detail | Value | |--------|-------| | **Stack** | Laravel + MySQL/PostgreSQL + Blade (or Filament/Nova) | | **Audience** | Staff / admins | | **Purpose** | User management, subscriptions, payments, device provisioning | | **Auth** | Laravel built-in (roles, permissions, password reset, 2FA) | | **Deploy** | .13:3030 (or Docker container) behind Caddy on .35 | | **DNS** | `admin.where-woof.com` | **Why separate:** - The GOAT frontend is an HTMX experiment — fast UI, minimal JS, quick to build - The admin needs forms-heavy workflows, role-based access, payment processing — Laravel excels here with Filament (admin panel generator), Cashier (Stripe/Paddle billing), and built-in auth - Two codebases, one shared SQLite/MySQL for pet/location data (read by both) --- ## Features ### Frontend (GOAT — Quick Win) - **Add a pet** — form with name, type (dog/cat/other), photo URL, Address, Two phones - **Update location** — - **View last known** — table showing each pet's last reported location + time ago - **Delete pet** — soft delete ### Admin (Laravel) - **User management** — CRUD, roles (admin/staff/owner), password reset, 2FA - **Subscription & payments** — Stripe/Paddle via Laravel Cashier, invoices, overdue tracking - **Device provisioning** — register new trackers, assign to user accounts, activation keys - **Dashboard** — overdue accounts, expiring subscriptions, new signups, active devices - **Audit log** — who changed what, login history ### Future / Shared - Location history log per pet (timeline view) - "Notify me" → NTFY ping when a pet's location updates - Map view (leaflet.js) of all current locations - Multi-user family group linking --- ## Tech Details ### Stack Rationale The **GOAT stack** — **G**o + **A**lpine.js + **T**ailwind + HTMX + SQLite: - **Go `net/http`** — routing, form handling, template rendering. No framework. - **HTMX** — `hx-post`, `hx-get`, `hx-delete` for inline form submission, live table updates, delete confirmations — no page reloads. - **Alpine.js** — form validation, dropdown bindings, timestamp formatting (`x-data`, `x-model`, `x-init`). - **Tailwind CSS** — utility-first styling via CDN, rapid UI without CSS files. - **SQLite** — single file DB (`data.db`), zero config, survives restarts. Migrations via `schema.sql`. ### Database Schema ```sql CREATE TABLE pets ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, type TEXT NOT NULL CHECK(type IN ('dog', 'cat', 'other')), chip_id TEXT UNIQUE, photo_url TEXT, created_at TEXT DEFAULT (datetime('now')), deleted INTEGER DEFAULT 0 ); CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, pet_id INTEGER NOT NULL REFERENCES pets(id), location TEXT NOT NULL, -- human-readable: "Backyard", "Vet, Main St" notes TEXT, reported_at TEXT DEFAULT (datetime('now')) ); CREATE INDEX idx_locations_pet_time ON locations(pet_id, reported_at DESC); ``` ### Endpoints | Method | Path | Handler | UI Behavior | |--------|------|---------|-------------| | `GET` | `/` | `ServeIndex` | Full page: pet list table + "New Location" dropdown + "Add Pet" button | | `GET` | `/pets/table` | `ServePetTable` | HTMX: re-renders the pet+location table | | `POST` | `/pets` | `CreatePet` | HTMX: form submit → table update, form reset | | `DELETE` | `/pets/:id` | `DeletePet` | HTMX: confirm → row fade-out | | `POST` | `/pets/:id/locations` | `AddLocation` | HTMX: dropdown form submit → table cell updates | | `GET` | `/pets/:id/history` | `ServeHistory` | HTMX: expand row to show location timeline | ### UI Sketch ``` ┌──────────────────────────────────────────────────────────────┐ │ WhereWoof [+ Add Pet] │ ├──────────────────────────────────────────────────────────────┤ │ │ │ Quick Update: [Buddy ▼] is at [_______________] [Update] │ │ │ ├──────────┬────────┬──────────────────┬──────────┬─────────────┤ │ Pet │ Type │ Last Seen │ Location │ Actions │ ├──────────┼────────┼──────────────────┼──────────┼─────────────┤ │ 🐕 Buddy│ dog │ 10 min ago │ Backyard │ [History][✕]│ │ 🐈 Mittens│ cat │ 2 hours ago │ Couch │ [History][✕]│ │ 🐕 Rocky │ dog │ 3 days ago │ Vet │ [History][✕]│ └──────────┴────────┴──────────────────┴──────────┴─────────────┘ ``` --- ## Directory Structure ``` /home/sam/home_network/web_sites/where_woof ├── frontend/ # GOAT stack — public pet tracker │ ├── main.go │ ├── db.go │ ├── handlers.go │ ├── models.go │ ├── go.mod │ ├── templates/ │ │ ├── base.html │ │ ├── index.html │ │ ├── pet-table.html │ │ └── location-form.html │ ├── static/ │ │ └── style.css │ └── data/ │ └── .gitkeep ├── admin/ # Laravel — staff dashboard │ ├── (standard Laravel structure) │ ├── app/Models/ │ │ ├── User.php │ │ ├── Pet.php │ │ ├── Subscription.php │ │ └── Device.php │ ├── app/Filament/ # Filament admin panel resources │ ├── database/migrations/ │ └── routes/web.php └── shared/ # Shared between frontend and admin └── schema.sql # Core pet/location tables (used by both) ``` --- ## Deployment ### Dev (local on .27) ```bash # Frontend (GOAT) cd /home/sam/home_network/web_sites/where_woof/frontend go run . --port 3020 # Admin (Laravel) cd /home/sam/home_network/web_sites/where_woof/admin php artisan serve --port 3030 ``` ### Build + Push to .13 ```bash # Frontend — Go binary cd /home/sam/home_network/web_sites/where_woof/frontend GOOS=linux GOARCH=amd64 go build -o where-woof . rsync -avz where-woof templates/ static/ sam@192.168.20.13:/var/www/where-woof # Admin — Laravel rsync -avz --exclude vendor --exclude node_modules \ /home/sam/home_network/web_sites/where_woof/admin/ \ sam@192.168.20.13:/var/www/where-woof # On .13: composer install, php artisan migrate ``` ### Run on .13 ```bash ssh sam@192.168.20.13 # Frontend cd /var/www/where-woof && ./where-woof --port 3020 # Admin (needs PHP-FPM + nginx, or Docker) # Option A: php artisan serve --port 3030 (dev only) # Option B: Docker with php:8.3-fpm + nginx ``` ### Caddy (.35) ```caddy # Frontend — public pet tracker where-woof.com { reverse_proxy 192.168.20.13:3020 } where-woof.home.lab { reverse_proxy 192.168.20.13:3020 } # Admin — staff dashboard admin.where-woof.com { reverse_proxy 192.168.20.13:3030 } ``` ### Pi-hole DNS - `where-woof.home.lab` → `192.168.20.35` - `where-woof.com` → public DNS → .35 - admin.where-woof.com → public DNS → .35 --- ## Roadmap | Phase | What | Status | |-------|------|--------| | 1 | Go scaffold, schema, pet CRUD, table view (frontend) | ⬜ | | 2 | Quick location update, history timeline (frontend) | ⬜ | | 3 | HTMX polish — inline edits, fade transitions (frontend) | ⬜ | | 4 | Laravel admin scaffold — Filament, user CRUD | ⬜ | | 5 | Laravel billing — Cashier, subscriptions, overdue tracking | ⬜ | | 6 | NTFY integration for location alerts (frontend) | ⬜ | | 7 | Deploy frontend to .13, Caddy DNS, live | ⬜ | | 8 | Deploy admin to .13, Caddy DNS, live | ⬜ |