Files
where_woof/where_woof.md

183 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WhereWoof — Return-Tag Platform
> **Branding (2026-08):** "WhereWoof" → **"Where Woof"** (play on *werewolf* — dog-themed wolf). Page title: **"Where Woof !"**. Future logo concept follows the werewolf wordplay (dog/wolf). Phase 2 change includes the rename task (headings, title, templates).
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.
> **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.
## How it works — customer journey
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)
When someone finds a lost item and scans the tag:
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.
> ⚠️ **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.
## Architecture — two systems, one shared Postgres
| 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 |
**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.
**Domains:**
- Live site at InMotion Hosting redirects → `where-woof.com`**.35 (Caddy)** → **.13** (apps).
- `admin.where-woof.com` → .35 → .13:3030.
## Database schema (v2)
```sql
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,
phone TEXT, -- owner contact phone (shown via tel:)
address TEXT,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
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
);
```
*Plans / subscriptions tables come later with billing (Phase 5).*
## Accounts, plans & anti-abuse
- One account can hold **1020 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.
### ⚠️ Preset tag IDs (anti-scam, confirmed 2026-08)
Tag codes are **preset at manufacture** (printed QR + NFC) and **must match the database registry** — there are no user-created tag codes. A scanned or claimed code that does not exist in the `tags` table is rejected ("tag not found / already claimed"). The 2014 system enforced the same rule via a `productid_hash` on the QR URL — *"if productid_hash is not set we can do NO REGISTERING"* — and tied each tag to a sold product (`onlineorderproductid`).
- The DB must be **seeded with the real manufactured tag IDs** (production list from the manufacturer, or recovered old IDs) — TEST codes are dev-only.
- Long-term (Laravel admin, Phase 4+): each tag is linked to a sold product/order for stronger validation (mirrors the old `products`/`online_order_product_details` model).
- Old product IDs from the 2014 system are **not in the .13 archive** (old live `websitebuilder` DB not preserved) — recover from the old hosting account / manufacturer list, or generate fresh IDs.
## 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 |
## Admin — Laravel (later phases)
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
cd frontend && go run . --port 3020 # GOAT front-end
cd admin && php artisan serve --port 3030 # Laravel (later)
```
### Build + push to .13
```bash
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 (later): rsync + `composer install` + `php artisan migrate` on .13.
### Run on .13
```bash
cd /var/www/where-woof && ./where-woof --port 3020
```
### Caddy (.35)
```caddy
where-woof.com {
reverse_proxy 192.168.20.13:3020
}
where-woof.home.lab {
reverse_proxy 192.168.20.13:3020
}
admin.where-woof.com {
reverse_proxy 192.168.20.13:3030
}
```
### 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 | **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.