"""Seed / upsert the NexusCRM software-provisioning integration credentials. Migration 315 seeds the empty ``integrations`` shell row (``slug='nexuscrm-primary'``, disabled). This script fills in the IT Connected App credentials (encrypted) and enables the row. Idempotent: re-running refreshes the credentials. **Never commits the Consumer Secret to git.** Credentials come from env: HELIX_NEXUSCRM_INSTANCE_URL=https://nexuscrm-new-prod.pingcap.com HELIX_NEXUSCRM_CONSUMER_KEY=... HELIX_NEXUSCRM_CONSUMER_SECRET=... [optional] HELIX_NEXUSCRM_API_VERSION=v63.0 [optional] HELIX_NEXUSCRM_SLUG=nexuscrm-primary Run from the server/ dir so the app package is on the path, with the target DB's CREDENTIAL_ENCRYPTION_KEY in env: cd server HELIX_NEXUSCRM_INSTANCE_URL=... HELIX_NEXUSCRM_CONSUMER_KEY=... \\ HELIX_NEXUSCRM_CONSUMER_SECRET=... CREDENTIAL_ENCRYPTION_KEY=... \\ uv run python ../scripts/seed_nexuscrm_integration.py After it runs, enable the ``nexuscrm.seat_sync`` scheduled job in /admin/scheduled-jobs (or via SQL) to start the daily reconciliation. """ from __future__ import annotations import asyncio import os import sys import uuid from pathlib import Path def _ensure_path() -> None: here = Path(__file__).resolve() server = here.parent.parent / "server" if server.exists() and str(server) not in sys.path: sys.path.insert(0, str(server)) _ensure_path() async def _main() -> None: instance_url = os.environ.get("HELIX_NEXUSCRM_INSTANCE_URL", "").strip() consumer_key = os.environ.get("HELIX_NEXUSCRM_CONSUMER_KEY", "").strip() consumer_secret = os.environ.get("HELIX_NEXUSCRM_CONSUMER_SECRET", "").strip() api_version = os.environ.get("HELIX_NEXUSCRM_API_VERSION", "v63.0").strip() or "v63.0" slug = os.environ.get("HELIX_NEXUSCRM_SLUG", "nexuscrm-primary").strip() or "nexuscrm-primary" if not instance_url or not consumer_key or not consumer_secret: print( "error: HELIX_NEXUSCRM_INSTANCE_URL, HELIX_NEXUSCRM_CONSUMER_KEY " "and HELIX_NEXUSCRM_CONSUMER_SECRET must be set", file=sys.stderr, ) sys.exit(2) from sqlalchemy import select from app.core.database import async_session_factory from app.identity.person_models import IdentityPerson from app.integrations.models import Integration from app.integrations.service import IntegrationService async with async_session_factory() as session: existing = (await session.execute( select(Integration).where(Integration.slug == slug) )).scalar_one_or_none() actor_row = (await session.execute( select(IdentityPerson).limit(1) )).scalar_one_or_none() actor_id = actor_row.id if actor_row else uuid.uuid4() service = IntegrationService(session) config = { "instance_url": instance_url, "consumer_key": consumer_key, "consumer_secret": consumer_secret, "api_version": api_version, } trace_id = f"seed-nexuscrm-{uuid.uuid4().hex[:12]}" if existing is None: print(f"[seed] creating nexuscrm integration slug={slug!r}") resp = await service.create( kind="software_provisioning", provider="nexuscrm", name="NexusCRM", slug=slug, description=( "NexusCRM(Salesforce 兼容)— IT Connected App " "client_credentials。坐席列表只读;revoke 需写 scope。" ), is_primary=True, enabled=True, capabilities=None, config=config, trace_id=trace_id, actor_user_id=actor_id, ) else: print(f"[seed] updating nexuscrm integration slug={slug!r}") resp = await service.update( existing.slug, name="NexusCRM", description=None, enabled=True, is_primary=None, capabilities=None, config=config, trace_id=trace_id, actor_user_id=actor_id, ) await session.commit() print(f"[seed] done. integration_id={resp.id} enabled=True") print( "[seed] next: enable the 'nexuscrm.seat_sync' scheduled job to " "import seat-holders, and run scripts/nexuscrm_wipe_seats.py " "(needs write scope) to clear current seats." ) if __name__ == "__main__": asyncio.run(_main())