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