#!/usr/bin/env python3 """Apply Facturapi configuration to all active tenant databases. Reads FACTURAPI_SECRET_KEY from the environment and stores it in each tenant's tenant_config table as cfdi_facturapi_key. Also runs the v4.3_facturapi.sql migration against every tenant DB. Usage: export MASTER_DB_URL=postgresql://... export TENANT_DB_URL_TEMPLATE="postgresql://.../{db_name}" export FACTURAPI_SECRET_KEY=sk_user_xxxxxxxxxxxxxxxx python3 scripts/apply_facturapi_to_all_tenants.py """ import os import sys import psycopg2 MIGRATION_SQL = """ ALTER TABLE cfdi_queue RENAME COLUMN xml_unsigned TO payload_unsigned; COMMENT ON COLUMN cfdi_queue.payload_unsigned IS 'Facturapi JSON payload (previously unsigned XML for Horux)'; COMMENT ON COLUMN cfdi_queue.xml_signed IS 'Signed+stamped XML returned by Facturapi'; ALTER TABLE cfdi_queue ADD COLUMN IF NOT EXISTS external_id VARCHAR(64); COMMENT ON COLUMN cfdi_queue.external_id IS 'Facturapi invoice id'; CREATE INDEX IF NOT EXISTS idx_cfdi_queue_external_id ON cfdi_queue(external_id); INSERT INTO tenant_config (key, value) VALUES ('cfdi_facturapi_key', ''), ('cfdi_facturapi_org_id', ''), ('cfdi_facturapi_customer_sync', 'true') ON CONFLICT (key) DO NOTHING; UPDATE tenant_config SET value = %s WHERE key = 'cfdi_facturapi_key'; """ def get_tenant_db_names(master_dsn): conn = psycopg2.connect(master_dsn) try: cur = conn.cursor() cur.execute( "SELECT id, db_name FROM tenants WHERE is_active = true ORDER BY id" ) rows = cur.fetchall() cur.close() return rows finally: conn.close() def apply_to_tenant(tenant_id, db_name, template_dsn, api_key): dsn = template_dsn.format(db_name=db_name) conn = psycopg2.connect(dsn) try: cur = conn.cursor() cur.execute(MIGRATION_SQL, (api_key,)) conn.commit() cur.close() print(f"[OK] tenant {tenant_id} ({db_name})") except Exception as e: print(f"[ERROR] tenant {tenant_id} ({db_name}): {e}") finally: conn.close() def main(): master_dsn = os.environ.get("MASTER_DB_URL") template_dsn = os.environ.get("TENANT_DB_URL_TEMPLATE") api_key = os.environ.get("FACTURAPI_SECRET_KEY") if not master_dsn or not template_dsn: print("Set MASTER_DB_URL and TENANT_DB_URL_TEMPLATE", file=sys.stderr) sys.exit(1) if not api_key: print("Set FACTURAPI_SECRET_KEY", file=sys.stderr) sys.exit(1) tenants = get_tenant_db_names(master_dsn) if not tenants: print("No active tenants found.") return print(f"Applying Facturapi config to {len(tenants)} tenant(s)...") for tenant_id, db_name in tenants: apply_to_tenant(tenant_id, db_name, template_dsn, api_key) print("Done.") if __name__ == "__main__": main()