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>
88 lines
3.5 KiB
Python
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.",
|
|
}
|