""" control/seed.py --------------- Idempotent seeding of the baseline plans (upsert by `code`). Mirrors the plan-tier matrix in MULTI_TENANT_PLAN.md §6. Safe to re-run — re-running updates existing plan rows in place rather than creating duplicates. stripe_price_id values are read from the environment at seed time so this file never contains live keys. Set before running: export STRIPE_PRICE_STARTER=price_xxx export STRIPE_PRICE_PRO=price_yyy export STRIPE_PRICE_ENTERPRISE=price_zzz Rows where the env var is absent keep their current stripe_price_id value (existing rows) or get NULL (new rows) — no data is lost on a partial run. """ import os from dotenv import load_dotenv load_dotenv() from control.base import control_session from control.models import Plan # Read Stripe price IDs from the environment; fall back to None so the # seeder is safe to run even when Stripe is not yet configured. _STRIPE_PRICES = { 'free': None, # Free plan is never sold via Stripe 'starter': os.environ.get('STRIPE_PRICE_STARTER'), 'pro': os.environ.get('STRIPE_PRICE_PRO'), 'enterprise': os.environ.get('STRIPE_PRICE_ENTERPRISE'), } PLAN_DEFS = [ dict(code='free', name='Free', max_users=3, max_facilities=2, max_inspections_month=50, max_issues_month=50, allow_custom_domain=False, allow_mobile_api=False, allow_scheduled_reports=False, allow_branding=False, price_cents=0, billing_period='month'), dict(code='starter', name='Starter', max_users=15, max_facilities=10, max_inspections_month=500, max_issues_month=500, allow_custom_domain=False, allow_mobile_api=True, allow_scheduled_reports=False, allow_branding=False, price_cents=None, billing_period='month'), dict(code='pro', name='Pro', max_users=50, max_facilities=50, max_inspections_month=5000, max_issues_month=5000, allow_custom_domain=True, allow_mobile_api=True, allow_scheduled_reports=True, allow_branding=True, price_cents=None, billing_period='month'), dict(code='enterprise', name='Enterprise', max_users=None, max_facilities=None, max_inspections_month=None, max_issues_month=None, allow_custom_domain=True, allow_mobile_api=True, allow_scheduled_reports=True, allow_branding=True, price_cents=None, billing_period='month'), ] def seed_plans(): """Upsert baseline plans. Returns (created_count, updated_count). stripe_price_id is only written when the corresponding env var is set — existing values are never overwritten with None on a partial run. """ created, updated = 0, 0 with control_session() as s: for d in PLAN_DEFS: plan = s.query(Plan).filter_by(code=d['code']).first() price_id = _STRIPE_PRICES.get(d['code']) if plan is None: plan = Plan(**d) plan.stripe_price_id = price_id # None is fine for new rows s.add(plan) created += 1 else: for k, v in d.items(): setattr(plan, k, v) # Only overwrite stripe_price_id when the env var is present. if price_id is not None: plan.stripe_price_id = price_id updated += 1 return created, updated if __name__ == '__main__': c, u = seed_plans() print(f'Plans seeded — created={c} updated={u}') for code, pid in _STRIPE_PRICES.items(): label = pid or '(not set — env var absent)' print(f' {code}: stripe_price_id = {label}')