gigafibre-fsm/apps/ops
louispaulb c0ca5feb6f feat(campaigns): gift redirect wrapper — own expiry + reusable links
Each campaign recipient now gets a short opaque token (10 base64url
chars, ~60 bits entropy). The email contains

  https://msg.gigafibre.ca/g/<token>

which 302-redirects to the underlying Giftbit shortlink — but ONLY if
the recipient hasn't passed our own expires_at and we haven't revoked
the token. This gives us two new operational capabilities:

1. End-date control independent of Giftbit. The wizard now has a
   "Expiration interne (jours)" field (default 90) that sets our
   own deadline. Useful when the Giftbit gift is valid 12 months
   but the campaign offer should expire in 30 days.

2. Reuse of unredeemed gifts. After our expiry, the old wrapper
   stops working but the Giftbit URL is still valid on their side.
   Pasting that same gift_url into a new campaign (via the manual-add
   dialog) generates a NEW token pointing to the same Giftbit gift —
   the original recipient's old wrapper URL says "expired", the new
   recipient gets a fresh window.

Per-recipient new fields:
- gift_token            short ID used in the wrapper URL
- gift_expires_at       ISO timestamp of our cutoff
- gift_revoked          manual kill-switch (false by default)
- gift_redirected_count clicks that successfully reached Giftbit
- gift_first_redirected_at  first successful redirect timestamp

Routing:
- GET /g/:token  — public, validates and 302s (or expired-page)
- Mailjet click event handler updated to recognise wrapper URLs
  alongside legacy gft.link/giftbit.com URLs.
- /view (browser fallback for in-email rendering) also wraps the
  gift link so expiry/revoke is honoured consistently.

Bootstrap rebuilds the in-memory token→recipient index by scanning
all campaign JSONs on startup — no separate index file to keep in
sync.

CSV report adds gift_token, gift_expires_at, gift_revoked,
gift_redirected_count, gift_first_redirected_at.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 10:15:43 -04:00
..
.quasar feat(ops/client): edit/delete/reorder subscriptions + rebate nesting 2026-04-23 11:21:41 -04:00
infra docs: reorganize into architecture/features/reference/archive folders 2026-04-22 11:51:33 -04:00
public/icons feat: add ops app + CONTEXT.md, simplify URL to /ops/ 2026-03-30 22:41:58 -04:00
src feat(campaigns): gift redirect wrapper — own expiry + reusable links 2026-05-22 10:15:43 -04:00
src-pwa feat: nested tasks, project wizard, n8n webhooks, inline task editing 2026-04-01 13:01:20 -04:00
.env.example feat(hub+ops): user invite flow sends temp password via Mailjet + dev .env.example 2026-05-05 19:50:06 -04:00
deploy.sh feat: nested tasks, project wizard, n8n webhooks, inline task editing 2026-04-01 13:01:20 -04:00
index.html feat: add ops app + CONTEXT.md, simplify URL to /ops/ 2026-03-30 22:41:58 -04:00
package-lock.json feat(ops/campaigns): pivot template editor to Unlayer (vue-email-editor) 2026-05-22 06:14:06 -04:00
package.json feat(ops/campaigns): pivot template editor to Unlayer (vue-email-editor) 2026-05-22 06:14:06 -04:00
quasar.config.js feat: contract → chain → subscription → prorated invoice lifecycle + tech group claim 2026-04-22 20:40:54 -04:00