ligbox-ops-platform/projects/wizard/backend/app/routers/project.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

88 lines
3.5 KiB
Python

"""Rotas project identity — Spec 037-C."""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel, Field
from app.deps import bind_onboarding_session, get_session_from_request
from app.services import activity_log, ops_webhook, project_identity
from app.services.carbonio import CarbonioError
from app.services.domain_format import normalize_domain, validate_primary_domain, invalid_domain_http_detail
router = APIRouter(
prefix="/onboarding/project",
tags=["onboarding-project"],
dependencies=[Depends(bind_onboarding_session)],
)
class ProvisionProjectEmailRequest(BaseModel):
domain: str = Field(..., min_length=3, max_length=253)
display_name: str | None = Field(None, max_length=120)
@router.post("/provision-email")
def provision_project_email(body: ProvisionProjectEmailRequest, request: Request):
"""Cria proj_{id}@ligbox.com.br e regista no domain_registry."""
get_session_from_request(request)
domain = normalize_domain(body.domain)
try:
validate_primary_domain(domain)
except ValueError as e:
raise HTTPException(status_code=400, detail=invalid_domain_http_detail()) from e
try:
activity_log.info(f"Provision e-mail projeto Ligbox: {domain}", source="project")
result = project_identity.provision_project_email(
domain,
display_name=body.display_name,
)
if result.get("already_exists"):
activity_log.ok(f"E-mail projeto já existe: {result['ligbox_project_email']}", source="project")
else:
activity_log.ok(f"E-mail projeto criado: {result['ligbox_project_email']}", source="project")
ops_webhook.emit_event(
"onboard.project.email.created",
domain=domain,
session_id=get_session_from_request(request),
data={
"ligbox_project_email": result.get("ligbox_project_email"),
"project_id": result.get("project_id"),
"already_exists": result.get("already_exists", False),
},
)
# Nunca logar password
safe = {k: v for k, v in result.items() if k != "password"}
if result.get("password"):
safe["password_available"] = True
return safe
except CarbonioError as e:
activity_log.error(f"Carbonio projeto: {e}", source="project")
raise HTTPException(status_code=502, detail=str(e)) from e
@router.get("/{domain}")
def get_project_identity(domain: str):
"""Estado identidade projeto (sem senha)."""
domain = normalize_domain(domain)
data = project_identity.get_project_identity(domain)
if not data:
raise HTTPException(404, f"Sem identidade projeto para {domain}")
return data
@router.post("/{domain}/reveal-password")
def reveal_project_password(domain: str, request: Request):
"""Uma vez por sessão — devolve senha do vault."""
get_session_from_request(request)
domain = normalize_domain(domain)
vault = project_identity.load_password_vault(domain)
if not vault or not vault.get("password"):
raise HTTPException(404, "Senha não disponível ou já entregue.")
project_identity.mark_password_delivered(domain)
return {
"domain": domain,
"ligbox_project_email": vault.get("ligbox_project_email"),
"password": vault["password"],
"webmail": f"https://mail.ligbox.com.br/",
"message": "Guarde a senha — não será mostrada novamente.",
}