Files

3.0 KiB
Raw Permalink Blame History

Context

docs/PRODUCT-MODEL.md established: SMS is the only real variable cost (~6¢ prepaid), plans meter it (10 SMS/yr per tag, family pools), and the alert SMS should carry a short link to a map page rather than raw coordinates. This change implements metering + the location-link flow. The existing throttles (250 m/10 min, fingerprint 24 h, daily/IP caps) remain — metering is an additional, separate gate.

Goals / Non-Goals

Goals:

  • Metered tags stop sending when credits exhaust (scan recording continues).
  • Every alert-with-location SMS carries a short URL to a map page.
  • Admin can set/refill credits (Filament field) and see usage.

Non-Goals:

  • Renewal/expiry automation + unlock messaging ("pay to renew") — next slice (needs email infra).
  • Family pooled SMS across accounts — later (per-tag metering now).
  • Top-up purchases (Stripe) — manual admin top-ups now.
  • i18n on the location page — later.

Decisions

  1. Metering = per-tag, 0 = unmetered. tags.sms_allocated/used/period_start (idempotent). sms_allocated = 0 skips credit checks → existing/transitional tags unaffected until an admin assigns a plan. sms_period_start records the anchor for the (future) renewal job.
  2. Count only successful sends. alertOwner and the finder-contact send each increment sms_used only after the sender returns success. Both count (per the product model: contact SMS uses the same meter).
  3. Short codes: 6-char base36 random, unique (url_shortened.code PK); created at alert time when location is present; maps code → scan_id. GET /s/{code} loads the scan and renders the location page.
  4. SMS message: with location, replace the raw maps URL with where-woof.com/s/{code} (short link). Without location, message unchanged (no link).
  5. Location page (location.html template set): Google Maps embed iframe (https://www.google.com/maps?q={lat},{lng}&output=embed), finder phone if present, tag-page link, scan timestamp. Private by unguessable code.
  6. Admin: TagResource — sms_allocated (numeric, editable = top-up), sms_used (view-only), sms_period_start (view). Eloquent already maps the new columns (fillable + cast).

Risks / Trade-offs

  • [Unmetered default means metering isn't enforced until admin configures] → intentional transitional state; the mechanism + admin field ship now, plan assignment comes with the billing slice.
  • [Short code collision] → retry loop on insert (regenerate up to 3×).
  • [Location page exposes finder location to anyone with the code] → codes are 6-char random (unguessable); validity window considered a later concern.

Migration Plan

  1. make db-up (ALTERs + url_shortened table).
  2. sqlc queries; Go handlers; template; admin field.
  3. Verify (new suite + regressions); deploy (frontend binary + restart; admin rebuild).

Open Questions

  • Whether contact-SMS should also get the short link — decided yes for consistency once the page exists (it shows the same location).