ligbox-ops-platform/projects/wizard/backend/app/services/cf_client_accounts.py
Ligbox Spec Hub c1881f58e6 chore: sync Console SSO, DNS viewer, specs e infra docs pendentes
Inclui console handoff Desk↔Console (Spec 019), melhorias DNS Viewer (037),
OpenPanel/Nextcloud/VM116 deploy notes, contracts stack e sidebar actualizado.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 20:10:17 +00:00

102 lines
3.4 KiB
Python

"""Conta Cloudflare dedicada por cliente — Spec 037 Fase D."""
from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from app.services.cloudflare import CloudflareDNS, CloudflareError
from app.services.dns_account_registry import pick_provision_account, provision_ligbox_zone
_CF_CLIENT_DIR = Path("/var/lib/ligbox-wizard/cf_client_accounts")
def _path(domain: str) -> Path:
safe = domain.lower().strip().replace("/", "_")
return _CF_CLIENT_DIR / f"{safe}.json"
def save_record(domain: str, data: dict[str, Any]) -> dict[str, Any]:
domain = domain.lower().strip().rstrip(".")
_CF_CLIENT_DIR.mkdir(parents=True, exist_ok=True)
existing = load_record(domain) or {}
merged = {**existing, **data, "domain": domain}
merged["updated_at"] = datetime.now(timezone.utc).isoformat()
if not merged.get("created_at"):
merged["created_at"] = merged["updated_at"]
_path(domain).write_text(json.dumps(merged, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
return merged
def load_record(domain: str) -> dict[str, Any] | None:
p = _path(domain)
if not p.is_file():
return None
try:
return json.loads(p.read_text(encoding="utf-8"))
except json.JSONDecodeError:
return None
def provision_client_account(
domain: str,
*,
ligbox_project_email: str | None = None,
account_name: str | None = None,
) -> dict[str, Any]:
"""
Fase D: cria conta CF dedicada (Tenant) ou fallback zona na conta provision.
Hoje: fallback = provision_ligbox_zone até Tenant activo.
"""
domain = domain.lower().strip().rstrip(".")
acct = pick_provision_account()
if not acct:
raise CloudflareError("Nenhuma conta Ligbox configurada.")
prov = provision_ligbox_zone(domain, account=acct)
zone = prov["zone"]
record = save_record(
domain,
{
"ligbox_project_email": ligbox_project_email,
"cloudflare_account_id": acct.cloudflare_account_id,
"cloudflare_account_name": account_name or f"{domain}",
"zone_id": zone.get("id"),
"provision_mode": "shared_account_fallback",
"handoff_status": "pending",
"tenant_dedicated": False,
},
)
return {
"domain": domain,
"zone": zone,
"created": prov["created"],
"account": acct.id,
"record": record,
"message": (
"Zona provisionada (modo fallback conta partilhada). "
"Tenant dedicado pendente activação Cloudflare."
),
}
def handoff_manager(domain: str, manager_email: str) -> dict[str, Any]:
"""Fase D: convite gestor CF — stub até Tenant + members API."""
domain = domain.lower().strip().rstrip(".")
manager_email = manager_email.lower().strip()
rec = load_record(domain) or {}
rec = save_record(
domain,
{
**rec,
"client_manager_email": manager_email,
"handoff_status": "invited_stub",
"handoff_note": "Implementar POST /accounts/{id}/members quando Tenant activo.",
},
)
return {
"domain": domain,
"manager_email": manager_email,
"handoff_status": rec["handoff_status"],
"message": "Handoff registado (stub). Convite CF manual até Tenant API.",
}