Skip to content

Add affiliate links feature#146

Open
wtfloris wants to merge 13 commits into
masterfrom
add-affiliate-links
Open

Add affiliate links feature#146
wtfloris wants to merge 13 commits into
masterfrom
add-affiliate-links

Conversation

@wtfloris

Copy link
Copy Markdown
Owner

Adds an affiliate-links framework so users can support Hestia by signing up for services (energy, internet, etc.) through partner links, as an alternative to donating. Surfaces on the Telegram bot and web dashboard; the API is ready for the iOS app.

What's included

Data model (misc/hestia.ddl)

  • affiliate_categories (slug, localized name_en/name_nl, icon, ordering, enabled)
  • affiliate_links (provider, real url, optional localized title/blurb, logo, ordering, enabled, click_count) — title_* are nullable; rendering skips them when unset
  • Content is managed via direct SQL, consistent with how targets are handled

Backend / API (web/hestia_web/app.py)

  • GET /api/affiliate-links?lang=en|nl — auth via session cookie or X-Device-Id (same as the other app APIs). Returns categories→links with a go_url; the raw affiliate url is never exposed
  • GET /go/<id> — public 302 redirect to the real URL with server-side click counting; rejects non-http(s) schemes, 404s unknown/disabled links

Telegram bot (hestia/bot.py, strings.py)

  • /support + /steun commands, fully localized (en/nl)
  • Cross-referenced from /start, /stop, /help, and the weekly reminder broadcast (which is now localized per-subscriber via get_user_lang)
  • Donation system left fully intact

Web dashboard (templates/dashboard.html, static/)

  • "Support Hestia" section below settings: permanent/expanded on desktop, collapsible accordion on mobile
  • Client-rendered from /api/affiliate-links, localized, re-renders on language toggle, opens links via /go/<id>

Notes

  • Bot/scraper tests pass; web tests cover the API shape, auth, redirect, click increment, bad-scheme, and 404 paths.
  • The two tables exist in the dev DB; prod needs the CREATE TABLEs + grants run (they're not auto-migrated).
  • The bot emits https://hestia.bot/go/<id> (hardcoded HESTIA_BASE_URL); the web derives the host from the request.
  • iOS recommendation: open go_url in the user's default browser (UIApplication.open), not an in-app SFSafariViewController, so affiliate attribution cookies land in the real browser.

🤖 Generated with Claude Code

@wtfloris

Copy link
Copy Markdown
Owner Author

Prod migration

These tables aren't auto-migrated — run this against the prod DB before deploying (mirrors misc/hestia.ddl, with the app-role grants this code needs):

BEGIN;

CREATE TABLE hestia.affiliate_categories (
  id int4 GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  slug varchar NOT NULL,
  name_en varchar NOT NULL,
  name_nl varchar NOT NULL,
  icon varchar NULL,
  sort_order int4 DEFAULT 0 NOT NULL,
  enabled bool DEFAULT true NOT NULL,
  CONSTRAINT affiliate_categories_slug_key UNIQUE (slug)
);

CREATE TABLE hestia.affiliate_links (
  id int4 GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  category_id int4 NOT NULL REFERENCES hestia.affiliate_categories(id),
  provider varchar NOT NULL,
  url varchar NOT NULL,
  title_en varchar NULL,
  title_nl varchar NULL,
  blurb_en varchar NULL,
  blurb_nl varchar NULL,
  logo varchar NULL,
  sort_order int4 DEFAULT 0 NOT NULL,
  enabled bool DEFAULT true NOT NULL,
  click_count int4 DEFAULT 0 NOT NULL,
  created_at timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL,
  updated_at timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL
);
CREATE INDEX affiliate_links_category_idx ON hestia.affiliate_links USING btree (category_id);

-- Grants: bot/scraper read via the `hestia` role; web reads + increments click_count via `svc_hestia_web`.
GRANT SELECT, INSERT, UPDATE, DELETE ON hestia.affiliate_categories, hestia.affiliate_links TO hestia, svc_hestia_web;

COMMIT;

Notes:

  • svc_hestia_web needs SELECT (API) + UPDATE (the /go/<id> click counter); INSERT/DELETE are included so content can also be curated as that role if desired.
  • Then seed affiliate_categories + affiliate_links rows directly (managed via SQL, same as targets).
  • No GRANT to a claude role here — that was a dev-only read role.

wtfloris and others added 8 commits June 14, 2026 16:57
Adds affiliate_categories/affiliate_links tables, /api/affiliate-links
and /go/<id> redirect with click tracking, and bot /support (/steun)
command. Donation system unchanged; /support referenced in start/stop
and weekly reminder messages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Renders /api/affiliate-links below the settings panel: permanent on
desktop, collapsible accordion on mobile. Localized via i18n, re-renders
on language toggle, links route through /go/<id>.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the Friday thanks/reminder into strings.py (en+nl) and send each
subscriber their language via get_user_lang. Leads with /support, Tikkie
as the alternative.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite EN/NL affiliate intro text and render the paragraph break.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite start/stop/support/weekly_reminder copy, fix MarkdownV2 escaping,
make affiliate link title nullable + optional in /support, update tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Align web panel, /support and weekly reminder copy (small bonus, ~1 week,
€79/mo). Move cost overview to an info icon, drop donation subtext from the
modal. Add a swappable 'comparison' affiliate entry surfaced as a Pro tip on
web and in /support; update tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@wtfloris wtfloris force-pushed the add-affiliate-links branch from 2a09047 to e9b3875 Compare June 14, 2026 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant