Rewrite plan: return-tag platform (not tracker), GOAT + shared Postgres
This commit is contained in:
296
where_woof.md
296
where_woof.md
@@ -1,250 +1,172 @@
|
||||
# WhereWoof — Local Pet Location Tracker
|
||||
# WhereWoof — Return-Tag Platform
|
||||
|
||||
## Architecture — Two Separate Systems
|
||||
WhereWoof is a **return-item tag platform** (PetHub / ReturnMe style). Physical tags are pre-coded with a unique QR code and NFC chip, each pointing to its own URL. A finder scans the tag on a lost item (dog, baggage, skis…) and immediately sees how to return it.
|
||||
|
||||
### 1. Front-Facing Website (GOAT stack)
|
||||
> **This is NOT a location/GPS tracker.** The tag has no electronics beyond the printed code and NFC. The system carries the *return details*, and — only when a finder permits — sends the *finder's* location to the owner so they can collect their item.
|
||||
|
||||
Quick win — lightweight, public-facing, pet owner experience.
|
||||
## How it works — customer journey
|
||||
|
||||
| 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` |
|
||||
1. **Buy** — user buys a tag (pre-coded, unique `tag_code` printed on QR + NFC).
|
||||
2. **First scan / first visit** — `https://where-woof.com/t/<tag_code>` shows "This tag is not set up yet."
|
||||
3. **Set up** — the user creates an account or logs in → **adds the tag to their account** → fills in the item details.
|
||||
4. **Details form** — item type (dog / cat / baggage / skis / other), description, photo, phone number, address, notes.
|
||||
5. **Done** — scanning the tag from now on shows the return details.
|
||||
6. **Edit** — the tag page shows an edit icon for a logged-in owner → account CRUD page. The account page is also reachable from the main site (`where-woof.com`).
|
||||
|
||||
## Scan flow — finder experience (front-end)
|
||||
|
||||
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.
|
||||
When someone finds a lost item and scans the tag:
|
||||
|
||||
**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.
|
||||
1. Page loads the return details: item type, description, photo, owner contact.
|
||||
2. **Geolocation prompt** (browser permission). If granted, the location is sent immediately to the server → **SMS alert to the owner** with the coordinates. If rejected, the page still works fully.
|
||||
3. **Ring owner** — `tel:` link that dials the owner's number directly.
|
||||
4. **Re-check location** — button to re-request the finder's location (in case they changed their mind).
|
||||
5. **Send SMS** — `sms:` link with a prefilled message asking the finder for their phone number (opens their SMS app).
|
||||
6. **Finder contact input** — the finder can type their mobile number; it is forwarded to the owner (not stored long-term).
|
||||
7. The location button is hidden once location has been shared.
|
||||
|
||||
### 2. Admin Dashboard (Laravel)
|
||||
> ⚠️ **Auto-reading the finder's phone number is NOT possible on the web** — there is no browser API for it (privacy restriction; only native apps can). Manual input only.
|
||||
|
||||
Separate system — full business management. Needs proper auth, billing, multi-user.
|
||||
## Architecture — two systems, one shared Postgres
|
||||
|
||||
| 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` |
|
||||
| System | Stack | Port / DNS | Audience | Purpose |
|
||||
|--------|-------|-----------|----------|---------|
|
||||
| **Front-end** | **GOAT**: Go + HTMX + Alpine.js + Tailwind + **Postgres** | `.13:3020` → `where-woof.com` | Finders + owners | Public tag pages, scan flow, account setup/CRUD |
|
||||
| **Admin** | Laravel + Filament/Cashier + Postgres | `.13:3030` → `admin.where-woof.com` | Staff (Sam) | Users, tags, plans, billing, system management |
|
||||
|
||||
**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)
|
||||
**One shared Postgres database.** Both apps are peers over it — the Go app reads/writes directly (via `pgx` / `sqlc`), Laravel manages the same schema, and **Laravel owns migrations** as the long-term convention. (Until Laravel exists, a canonical `db/schema.sql` is the source of truth.)
|
||||
|
||||
---
|
||||
**Why this design:**
|
||||
- The GOAT stack is a **skills-update exercise** — the point is Go + HTMX + Alpine + Tailwind as the front-end stack. The "S" (SQLite) from the original AI-written plan is swappable and is **dropped in favour of Postgres**: a multi-tenant product needs one real database, not hundreds of per-user SQLite files (no cross-user queries, migration/backup nightmare, Laravel can't query them).
|
||||
- Dev Postgres already available locally: Supabase Postgres on `.27:5434`.
|
||||
- Both apps sharing one DB means no sync layer and one backup.
|
||||
|
||||
## Features
|
||||
**Domains:**
|
||||
- Live site at InMotion Hosting redirects → `where-woof.com` → **.35 (Caddy)** → **.13** (apps).
|
||||
- `admin.where-woof.com` → .35 → .13:3030.
|
||||
|
||||
### 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
|
||||
## Database schema (v2)
|
||||
|
||||
```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,
|
||||
CREATE TABLE users (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
name TEXT,
|
||||
phone TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE tags (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
tag_code TEXT NOT NULL UNIQUE, -- printed on QR + NFC, public identifier
|
||||
owner_id BIGINT REFERENCES users(id),
|
||||
status TEXT NOT NULL DEFAULT 'unset'
|
||||
CHECK (status IN ('unset', 'active', 'suspended')),
|
||||
item_type TEXT CHECK (item_type IN ('dog', 'cat', 'baggage', 'skis', 'other')),
|
||||
description TEXT,
|
||||
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"
|
||||
phone TEXT, -- owner contact phone (shown via tel:)
|
||||
address TEXT,
|
||||
notes TEXT,
|
||||
reported_at TEXT DEFAULT (datetime('now'))
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_locations_pet_time ON locations(pet_id, reported_at DESC);
|
||||
CREATE TABLE scans (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
tag_id BIGINT NOT NULL REFERENCES tags(id),
|
||||
scanned_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
lat DOUBLE PRECISION,
|
||||
lng DOUBLE PRECISION,
|
||||
location_shared BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
scanner_phone TEXT,
|
||||
alert_sent BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
```
|
||||
|
||||
### Endpoints
|
||||
*Plans / subscriptions tables come later with billing (Phase 5).*
|
||||
|
||||
| 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 |
|
||||
## Accounts, plans & anti-abuse
|
||||
|
||||
### UI Sketch
|
||||
- One account can hold **10–20 tags** (limit enforced).
|
||||
- Business model: **annual fee + per-tag cost + max scans per plan**.
|
||||
- **Scan limits are the anti-scam / cost protection** — every scan can trigger an SMS (which costs us money), so caps prevent SMS-bombing and tag re-selling abuse.
|
||||
- Tag status: `unset` → `active` → `suspended` (admin can suspend).
|
||||
- Billing: **Stripe (Australia)** — **NOT in the front-end phase**; phased later.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 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][✕]│
|
||||
└──────────┴────────┴──────────────────┴──────────┴─────────────┘
|
||||
```
|
||||
## Endpoints — GOAT front-end
|
||||
|
||||
---
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `GET` | `/` | Main site home |
|
||||
| `GET` | `/t/:tag_code` | Public tag page — setup prompt or details + scan flow |
|
||||
| `POST` | `/t/:tag_code/scan` | Record scan + optional location → alert owner |
|
||||
| `POST` | `/t/:tag_code/contact` | Finder leaves their phone number for the owner |
|
||||
| `GET/POST` | `/register`, `/login`, `/logout` | Account auth (email + password, session cookie) |
|
||||
| `GET` | `/account` | Account CRUD: my tags |
|
||||
| `POST` | `/account/tags` | Add a tag to the account (enter tag code) |
|
||||
| `GET/POST` | `/account/tags/:id/edit` | Edit tag details |
|
||||
| `POST` | `/account/tags/:id/delete` | Remove tag |
|
||||
|
||||
## Directory Structure
|
||||
## Admin — Laravel (later phases)
|
||||
|
||||
```
|
||||
/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)
|
||||
```
|
||||
|
||||
---
|
||||
User management, tag management (suspend/transfer), plans & billing (Stripe/Cashier), scan history & alerts dashboard, audit log. Not built in Phase 1.
|
||||
|
||||
## 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
|
||||
cd frontend && go run . --port 3020 # GOAT front-end
|
||||
cd admin && php artisan serve --port 3030 # Laravel (later)
|
||||
```
|
||||
|
||||
### Build + Push to .13
|
||||
|
||||
### Build + push to .13
|
||||
```bash
|
||||
# Frontend — Go binary
|
||||
cd /home/sam/home_network/web_sites/where_woof/frontend
|
||||
cd 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
|
||||
```
|
||||
Admin (later): rsync + `composer install` + `php artisan migrate` on .13.
|
||||
|
||||
### 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
|
||||
|
||||
---
|
||||
### DNS
|
||||
- Live site (InMotion) → redirect / DNS update → `where-woof.com` → public DNS → .35 (Caddy) → .13.
|
||||
- Pi-hole: `where-woof.home.lab` → 192.168.20.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 | ⬜ |
|
||||
| 1 | **Front-end foundation**: Go scaffold, Postgres schema, auth, tag setup, public tag page, account CRUD (GOAT) | ⬜ |
|
||||
| 2 | **Scan flow**: geolocation → SMS alert, `tel:`/`sms:` links, finder contact, re-check button | ⬜ |
|
||||
| 3 | HTMX / Alpine polish — inline edit, animations | ⬜ |
|
||||
| 4 | Laravel admin: users, tags, dashboard (same Postgres) | ⬜ |
|
||||
| 5 | Billing: Stripe (AU), plans, scan-limit enforcement, invoices | ⬜ |
|
||||
| 6 | Photo uploads + object storage | ⬜ |
|
||||
| 7 | Deploy front-end to .13, Caddy, DNS, InMotion redirect | ⬜ |
|
||||
| 8 | Deploy admin to .13 | ⬜ |
|
||||
| 9 | Anti-abuse hardening: relay numbers, rate limits | ⬜ |
|
||||
|
||||
## Future ideas
|
||||
|
||||
- NTFY ping to owner when a scan happens (alongside SMS).
|
||||
- Tag transfer / reset for resale (admin feature).
|
||||
- Relay number so the owner's real mobile isn't exposed to scanners.
|
||||
|
||||
Reference in New Issue
Block a user