Fase 0 complete: extract all business logic, auth, database, and 176 endpoints from monolithic app.py into dedicated modules. Structure: - app.py: FastAPI hub (CORS, routers, startup/shutdown) - models.py: 13 Pydantic request models - utils.py: shared utilities (embed status, upload validation, process registries) - config.py: constants, env vars, model catalogs - auth/: crypto, jwt_auth, oidc, rate_limit - database/: db(), init_db(), schema DDL - services/: genai, compliance, chat, terraform, embeddings, cis_reports, mcp - routes/: 13 APIRouter modules (auth, users, oci_config, oci_explorer, genai, mcp, adb, embeddings, reports, chat, terraform, settings, cis_engine) Also: README updated with OCIR auth instructions for manual docker run. 86 tests passing.
65 lines
3.0 KiB
Python
65 lines
3.0 KiB
Python
"""User routes — list, me, update, delete, timezone."""
|
|
from fastapi import APIRouter, HTTPException, Depends
|
|
|
|
from database import db
|
|
from auth.jwt_auth import current_user, require, _audit
|
|
from models import UserUpdateReq
|
|
|
|
router = APIRouter()
|
|
|
|
# ── Users ─────────────────────────────────────────────────────────────────────
|
|
@router.get("/api/users")
|
|
async def list_users(u=Depends(require("admin"))):
|
|
with db() as c: rows = c.execute("SELECT id,first_name,last_name,username,email,role,mfa_enabled,timezone,is_active,created_at,last_login,auth_provider FROM users").fetchall()
|
|
return [dict(r) for r in rows]
|
|
|
|
@router.get("/api/users/me")
|
|
async def me(u=Depends(current_user)):
|
|
fields = ("id","first_name","last_name","username","email","role","mfa_enabled","timezone","auth_provider","must_change_password")
|
|
r = {k: u.get(k, "local" if k == "auth_provider" else None) for k in fields}
|
|
r["must_change_password"] = bool(r.get("must_change_password", 0))
|
|
return r
|
|
|
|
@router.put("/api/users/{uid}")
|
|
async def update_user(uid: str, req: UserUpdateReq, adm=Depends(require("admin"))):
|
|
sets, vals = [], []
|
|
if req.email is not None: sets.append("email=?"); vals.append(req.email)
|
|
if req.role is not None:
|
|
if req.role not in ("admin","user","viewer"): raise HTTPException(400, "Role inválida")
|
|
sets.append("role=?"); vals.append(req.role)
|
|
if req.is_active is not None: sets.append("is_active=?"); vals.append(int(req.is_active))
|
|
if req.timezone is not None: sets.append("timezone=?"); vals.append(req.timezone)
|
|
if sets:
|
|
vals.append(uid)
|
|
with db() as c: c.execute(f"UPDATE users SET {','.join(sets)} WHERE id=?", vals)
|
|
_audit(adm["id"], adm["username"], "update_user", uid)
|
|
return {"ok": True}
|
|
|
|
@router.delete("/api/users/{uid}")
|
|
async def del_user(uid: str, adm=Depends(require("admin"))):
|
|
if uid == adm["id"]: raise HTTPException(400, "Não pode desativar a si mesmo")
|
|
with db() as c: c.execute("UPDATE users SET is_active=0 WHERE id=?", (uid,))
|
|
_audit(adm["id"], adm["username"], "deactivate_user", uid)
|
|
return {"ok": True}
|
|
|
|
@router.put("/api/users/me/timezone")
|
|
async def set_user_timezone(body: dict, u=Depends(current_user)):
|
|
"""Set the current user's timezone."""
|
|
tz = body.get("timezone", "").strip()
|
|
if not tz:
|
|
raise HTTPException(400, "timezone is required")
|
|
try:
|
|
import zoneinfo
|
|
zoneinfo.ZoneInfo(tz)
|
|
except Exception:
|
|
raise HTTPException(400, f"Invalid timezone: {tz}")
|
|
with db() as c:
|
|
c.execute("UPDATE users SET timezone=? WHERE id=?", (tz, u["id"]))
|
|
_audit(u["id"], u["username"], "set_timezone", tz)
|
|
return {"ok": True, "timezone": tz}
|
|
|
|
@router.get("/api/users/me/timezone")
|
|
async def get_user_timezone(u=Depends(current_user)):
|
|
"""Get the current user's timezone."""
|
|
return {"timezone": u.get("timezone", "America/Sao_Paulo")}
|