# 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/` 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 **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. ## 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.