"""API sandbox — Área Gerente create-only (Spec 035).""" from __future__ import annotations import sqlite3 from typing import Any from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from app import auth, domain_console_sandbox as sandbox from app.permissions import can_manage_vm112_domains router = APIRouter(prefix="/api/v1/domain-console/sandbox", tags=["domain-console-sandbox"]) def _require_sandbox(user: auth.DeskUser = Depends(auth.get_current_user)) -> auth.DeskUser: if not can_manage_vm112_domains(user.role): raise HTTPException(403, "Sem permissão sandbox domain console") return user def _db() -> sqlite3.Connection: from app.main import db return db() class CreateScenarioBody(BaseModel): label: str = Field(..., min_length=2, max_length=120) domain: str | None = None slug: str | None = Field(None, max_length=40) plan_mock: str = "Business" admin_password: str | None = Field(None, min_length=8) class AddAccountBody(BaseModel): local_part: str = Field(..., min_length=1, max_length=64) display_name: str = Field("", max_length=120) password: str | None = Field(None, min_length=8) @router.get("/config") def sandbox_config(_user: auth.DeskUser = Depends(_require_sandbox)): return { "enabled": sandbox.SANDBOX_ENABLED, "mode": "create-only", "delete_blocked": True, "domain_prefix": sandbox.SANDBOX_DOMAIN_PREFIX, "domain_suffix": sandbox.SANDBOX_ALLOWED_SUFFIX, "suggested_domain": sandbox.suggest_scenario_domain(), "protected_domains": sorted(sandbox.PROTECTED_DOMAINS), } @router.get("/domains") def list_domains_readonly( q: str = "", _user: auth.DeskUser = Depends(_require_sandbox), ): return sandbox.list_domains_readonly(q) @router.get("/domains/{domain}") def get_domain_readonly( domain: str, _user: auth.DeskUser = Depends(_require_sandbox), ): return sandbox.fetch_domain_readonly(domain) @router.get("/scenarios") def list_scenarios( user: auth.DeskUser = Depends(_require_sandbox), ): from app.domain_console_sandbox_store import init_schema, list_scenarios as ls conn = _db() init_schema(conn) return {"scenarios": ls(conn), "delete_blocked": True} @router.post("/scenarios") def create_scenario( body: CreateScenarioBody, user: auth.DeskUser = Depends(_require_sandbox), ): from app.domain_console_sandbox_store import init_schema conn = _db() init_schema(conn) try: return sandbox.create_scenario( conn, label=body.label, domain=body.domain, slug=body.slug, created_by=user.username, plan_mock=body.plan_mock, admin_password=body.admin_password, ) except sandbox.SandboxForbidden as e: raise HTTPException(403, str(e)) from e except sandbox.SandboxError as e: raise HTTPException(400, str(e)) from e @router.get("/scenarios/{scenario_id}/state") def scenario_state( scenario_id: str, _user: auth.DeskUser = Depends(_require_sandbox), ): from app.domain_console_sandbox_store import init_schema conn = _db() init_schema(conn) try: return sandbox.build_console_state(conn, scenario_id) except sandbox.SandboxError as e: raise HTTPException(404, str(e)) from e @router.post("/scenarios/{scenario_id}/accounts") def add_account( scenario_id: str, body: AddAccountBody, _user: auth.DeskUser = Depends(_require_sandbox), ): from app.domain_console_sandbox_store import init_schema conn = _db() init_schema(conn) try: return sandbox.add_account_to_scenario( conn, scenario_id=scenario_id, local_part=body.local_part, display_name=body.display_name, password=body.password, ) except sandbox.SandboxForbidden as e: raise HTTPException(403, str(e)) from e except sandbox.SandboxError as e: raise HTTPException(400, str(e)) from e @router.delete("/scenarios/{scenario_id}") def block_delete_scenario(scenario_id: str, _user: auth.DeskUser = Depends(_require_sandbox)): try: sandbox.assert_no_delete(f"delete scenario {scenario_id}") except sandbox.SandboxForbidden as e: raise HTTPException(403, str(e)) from e raise HTTPException(403, "delete bloqueado") @router.delete("/scenarios/{scenario_id}/accounts/{email}") def block_delete_account( scenario_id: str, email: str, _user: auth.DeskUser = Depends(_require_sandbox), ): try: sandbox.assert_no_delete(f"delete account {email}") except sandbox.SandboxForbidden as e: raise HTTPException(403, str(e)) from e raise HTTPException(403, "delete bloqueado") @router.post("/scenarios/{scenario_id}/purge") def block_purge_scenario(scenario_id: str, _user: auth.DeskUser = Depends(_require_sandbox)): try: sandbox.assert_no_delete(f"purge scenario {scenario_id}") except sandbox.SandboxForbidden as e: raise HTTPException(403, str(e)) from e raise HTTPException(403, "purge bloqueado")