"""Rotas VM123 — Spec 027 Fase 3.""" from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel, Field from app import auth from app.permissions import ( can_access_foss_admin, can_create_foss_order, can_manage_users, can_openpanel_autologin, can_read_billing, ) from app.platform_role_catalog import catalog_export from app.vm123 import foss_client, odoo_client, openpanel_client, provision, provision_store from app.vm123.role_map import PROVISIONABLE_DESK_ROLES router = APIRouter(prefix="/api/v1/vm123", tags=["vm123"]) class FossOrderBody(BaseModel): client_id: int | None = None domain: str | None = None product_id: int | None = None note: str | None = None class ProvisionUserBody(BaseModel): desk_username: str = Field(min_length=3) desk_role: str | None = None @router.get("/platform/catalog") def platform_role_catalog(user: auth.DeskUser = Depends(auth.get_current_user)): """Catálogo mestre função → serviços (padrão Odoo res.groups na plataforma DevOps).""" return catalog_export() @router.get("/health") def vm123_health(user: auth.DeskUser = Depends(auth.get_current_user)): if user.role not in ("super_admin", "devops", "developer"): raise HTTPException(403, "permissão insuficiente") out: dict = {"odoo": {"configured": odoo_client._configured()}} try: out["odoo"]["role_model_sales_admin"] = odoo_client.list_role_model("sales_admin") except Exception as exc: out["odoo"]["error"] = str(exc) out["foss"] = {"configured": foss_client._configured()} out["openpanel"] = openpanel_client.health() return out @router.get("/odoo/role-model/{role}") def odoo_role_model(role: str, user: auth.DeskUser = Depends(auth.get_current_user)): if not can_manage_users(user.role) and user.role not in ("devops", "developer"): raise HTTPException(403, "permissão insuficiente") try: return odoo_client.list_role_model(role) except odoo_client.OdooConfigError as exc: raise HTTPException(503, str(exc)) from exc @router.get("/odoo/partner") def odoo_partner(email: str = Query(..., min_length=3), user: auth.DeskUser = Depends(auth.get_current_user)): if not can_read_billing(user.role): raise HTTPException(403, "permissão insuficiente") try: partner = odoo_client.find_partner_by_email(email) except odoo_client.OdooConfigError as exc: raise HTTPException(503, str(exc)) from exc if not partner: raise HTTPException(404, "parceiro não encontrado") return { "partner": partner, "login_url": odoo_client.ODOO_PUBLIC_URL, } @router.get("/foss/client/{domain}") def foss_client_by_domain(domain: str, user: auth.DeskUser = Depends(auth.get_current_user)): if not can_read_billing(user.role): raise HTTPException(403, "permissão insuficiente") try: client_row = foss_client.find_client_by_domain(domain) except foss_client.FossConfigError as exc: raise HTTPException(503, str(exc)) from exc if not client_row: raise HTTPException(404, "cliente FOSS não encontrado") return { "client": client_row, "admin_url": foss_client.FOSS_PUBLIC_ADMIN, "can_order": can_create_foss_order(user.role), "can_admin": can_access_foss_admin(user.role), } @router.post("/foss/order") def foss_create_order(body: FossOrderBody, user: auth.DeskUser = Depends(auth.get_current_user)): if not can_create_foss_order(user.role): raise HTTPException(403, "permissão insuficiente") if not body.client_id and not body.domain: raise HTTPException(400, "informe client_id ou domain") # MVP: delegar criação real à UI FOSS até mapear product_id return { "accepted": True, "message": "Pedido registado — criação FOSS via Admin até product_id estar mapeado", "payload": body.model_dump(), "foss_admin": foss_client.FOSS_PUBLIC_ADMIN, } @router.post("/openpanel/autologin/{username}") def openpanel_autologin(username: str, user: auth.DeskUser = Depends(auth.get_current_user)): if not can_openpanel_autologin(user.role): raise HTTPException(403, "permissão insuficiente") return openpanel_client.autologin_payload(username) @router.get("/identity/{desk_username}") def get_identity_map(desk_username: str, user: auth.DeskUser = Depends(auth.get_current_user)): if not can_manage_users(user.role): raise HTTPException(403, "permissão insuficiente") with auth.db() as conn: row = provision_store.get_map(conn, desk_username) if not row: raise HTTPException(404, "sem registo VM123") return row @router.post("/provision/user") def provision_user(body: ProvisionUserBody, user: auth.DeskUser = Depends(auth.get_current_user)): if not can_manage_users(user.role): raise HTTPException(403, "permissão insuficiente") with auth.db() as conn: urow = conn.execute( "SELECT username, role, display_name, email FROM desk_users WHERE username = ?", (body.desk_username.strip().lower(),), ).fetchone() if not urow: raise HTTPException(404, "utilizador Desk não encontrado") role = body.desk_role or urow["role"] if role not in PROVISIONABLE_DESK_ROLES: raise HTTPException(400, f"role {role} não provisionável") email = urow["email"] or urow["username"] result = provision.provision_desk_user( conn, desk_username=urow["username"], desk_role=role, display_name=urow["display_name"] or email, email=email, ) return result @router.get("/links/client") def client_deep_links( domain: str = Query(..., min_length=3), email: str = "", user: auth.DeskUser = Depends(auth.get_current_user), ): """Deep-links drawer «Conta do cliente» — Spec 023 + 027.""" if not can_read_billing(user.role): raise HTTPException(403, "permissão insuficiente") links = { "domain": domain.strip().lower(), "foss": {"url": foss_client.FOSS_PUBLIC_ADMIN, "label": "FOSSBilling Admin"}, "odoo": {"url": odoo_client.ODOO_PUBLIC_URL, "label": "Odoo ligbox"}, "openpanel": {"url": openpanel_client.OPENADMIN_URL, "label": "OpenAdmin"}, } out: dict = {"links": links, "role": user.role} if foss_client._configured(): try: fc = foss_client.find_client_by_domain(domain) if fc: out["foss"]["client_id"] = fc.get("id") out["foss"]["client_email"] = fc.get("email") except Exception: pass bill_email = (email or "").strip() if bill_email and odoo_client._configured(): try: partner = odoo_client.find_partner_by_email(bill_email) if partner: out["odoo"]["partner_id"] = partner.get("id") out["odoo"]["partner_name"] = partner.get("name") except Exception: pass out["permissions"] = { "can_order": can_create_foss_order(user.role), "can_foss_admin": can_access_foss_admin(user.role), "can_openpanel_autologin": can_openpanel_autologin(user.role), } return out