feat(affiliate): add affiliate_programs table + migration 0027
Creates affiliate_programs for centralised retailer config (URL template, tracking tag, commission %). Adds nullable program_id + product_identifier to affiliate_products for backward compat. Seeds "Amazon" program with oneLink template. Backfills existing products by extracting ASINs from baked affiliate_url values. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
|||||||
|
"""Migration 0027: Affiliate programs table + program FK on products.
|
||||||
|
|
||||||
|
affiliate_programs: centralises retailer configs (URL template + tag + commission).
|
||||||
|
- url_template uses {product_id} and {tag} placeholders, assembled at redirect time.
|
||||||
|
- tracking_tag: e.g. "padelnomics-21" — changing it propagates to all products instantly.
|
||||||
|
- commission_pct: stored as a decimal (0.03 = 3%) for revenue estimates.
|
||||||
|
- status: active/inactive — only active programs appear in the product form dropdown.
|
||||||
|
- notes: internal field for login URLs, account IDs, etc.
|
||||||
|
|
||||||
|
affiliate_products changes:
|
||||||
|
- program_id (nullable FK): new products use a program; existing products keep their
|
||||||
|
baked affiliate_url (backward compat via build_affiliate_url() fallback).
|
||||||
|
- product_identifier: ASIN, product path, or other program-specific ID (e.g. B0XXXXX).
|
||||||
|
|
||||||
|
Amazon OneLink note: we use a single "Amazon" program pointing to amazon.de.
|
||||||
|
Amazon OneLink (configured in the Associates dashboard, no code changes needed)
|
||||||
|
auto-redirects visitors to their local marketplace (UK→amazon.co.uk, ES→amazon.es)
|
||||||
|
with the correct regional tag. One program covers all Amazon marketplaces.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def up(conn) -> None:
|
||||||
|
conn.execute("""
|
||||||
|
CREATE TABLE affiliate_programs (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
slug TEXT NOT NULL UNIQUE,
|
||||||
|
url_template TEXT NOT NULL,
|
||||||
|
tracking_tag TEXT NOT NULL DEFAULT '',
|
||||||
|
commission_pct REAL NOT NULL DEFAULT 0,
|
||||||
|
homepage_url TEXT NOT NULL DEFAULT '',
|
||||||
|
status TEXT NOT NULL DEFAULT 'active',
|
||||||
|
notes TEXT NOT NULL DEFAULT '',
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Seed the default Amazon program.
|
||||||
|
# OneLink handles geo-redirect to local marketplaces — no per-country programs needed.
|
||||||
|
conn.execute("""
|
||||||
|
INSERT INTO affiliate_programs (name, slug, url_template, tracking_tag, commission_pct, homepage_url)
|
||||||
|
VALUES ('Amazon', 'amazon', 'https://www.amazon.de/dp/{product_id}?tag={tag}', 'padelnomics-21', 3.0, 'https://www.amazon.de')
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Add program FK + product identifier to products table.
|
||||||
|
# program_id is nullable — existing rows keep their baked affiliate_url.
|
||||||
|
conn.execute("""
|
||||||
|
ALTER TABLE affiliate_products
|
||||||
|
ADD COLUMN program_id INTEGER REFERENCES affiliate_programs(id)
|
||||||
|
""")
|
||||||
|
conn.execute("""
|
||||||
|
ALTER TABLE affiliate_products
|
||||||
|
ADD COLUMN product_identifier TEXT NOT NULL DEFAULT ''
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Backfill: extract ASIN from existing Amazon affiliate URLs.
|
||||||
|
# Pattern: /dp/<ASIN> where ASIN is 10 uppercase alphanumeric chars.
|
||||||
|
amazon_program = conn.execute(
|
||||||
|
"SELECT id FROM affiliate_programs WHERE slug = 'amazon'"
|
||||||
|
).fetchone()
|
||||||
|
assert amazon_program is not None, "Amazon program must exist after seed"
|
||||||
|
amazon_id = amazon_program[0]
|
||||||
|
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT id, affiliate_url FROM affiliate_products"
|
||||||
|
).fetchall()
|
||||||
|
asin_re = re.compile(r"/dp/([A-Z0-9]{10})")
|
||||||
|
for product_id, url in rows:
|
||||||
|
if not url:
|
||||||
|
continue
|
||||||
|
m = asin_re.search(url)
|
||||||
|
if m:
|
||||||
|
asin = m.group(1)
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE affiliate_products SET program_id=?, product_identifier=? WHERE id=?",
|
||||||
|
(amazon_id, asin, product_id),
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user