#!/usr/bin/env python3 """Expire demo tenants whose trial has ended. A tenant on plan='demo' is a time-limited full trial (no resource limits). When its active subscription's expires_at passes, this job downgrades it to 'free' and marks the subscription as expired. Safe by default: runs in DRY-RUN and only reports. Pass --apply to enforce. """ import argparse import os import psycopg2 MASTER_DB_URL = os.environ.get( 'MASTER_DB_URL', 'postgresql://postgres@localhost/nexus_autoparts' ) def main(): ap = argparse.ArgumentParser(description='Expire ended demo tenants.') ap.add_argument('--apply', action='store_true', help='Actually downgrade expired demos (default: dry-run)') args = ap.parse_args() conn = psycopg2.connect(MASTER_DB_URL) cur = conn.cursor() cur.execute(""" SELECT t.id, t.name, s.expires_at FROM tenants t JOIN subscriptions s ON s.tenant_id = t.id WHERE t.plan = 'demo' AND t.is_active = true AND s.status = 'active' AND s.expires_at IS NOT NULL AND s.expires_at < NOW() ORDER BY t.id """) rows = cur.fetchall() if not rows: print('No expired demo tenants.') else: print(f'{len(rows)} demo tenant(s) with an expired subscription:') for tid, name, exp in rows: print(f' #{tid} {name} (expiró {exp:%Y-%m-%d})') if args.apply: for tid, _name, _exp in rows: cur.execute("UPDATE tenants SET plan = 'free' WHERE id = %s", (tid,)) cur.execute( "UPDATE subscriptions SET status = 'expired' " "WHERE tenant_id = %s AND status = 'active'", (tid,), ) conn.commit() print(f'Applied: {len(rows)} tenant(s) downgraded to free.') else: print('DRY-RUN: nothing changed. Re-run with --apply to enforce.') cur.close() conn.close() if __name__ == '__main__': main()