Specs stay at repo root (cross-VM). Move deploy and code into logical projects with README per domain, updated manifest.yaml, and symlinks at legacy paths for VM122 backward compatibility.
195 lines
7.2 KiB
Python
195 lines
7.2 KiB
Python
"""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
|