Initial travel knowledge graph release
This commit is contained in:
155
app/api/rbac.py
Normal file
155
app/api/rbac.py
Normal file
@@ -0,0 +1,155 @@
|
||||
"""RBAC & Accounts (P1) — roles, capability matrix, users CRUD."""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from app.auth import CurrentUser
|
||||
from app.contracts import (
|
||||
CapabilityCreate,
|
||||
RoleCapCell,
|
||||
RoleCreate,
|
||||
RoleUpdate,
|
||||
UserAreasSet,
|
||||
UserCreate,
|
||||
UserUpdate,
|
||||
)
|
||||
from app.db import (
|
||||
create_capability,
|
||||
create_role,
|
||||
create_user,
|
||||
delete_capability,
|
||||
delete_role,
|
||||
delete_user,
|
||||
get_permission_matrix,
|
||||
list_capabilities,
|
||||
list_roles,
|
||||
list_user_areas,
|
||||
list_users,
|
||||
set_role_cap,
|
||||
set_user_areas,
|
||||
update_role,
|
||||
update_user,
|
||||
upsert_custom_area,
|
||||
)
|
||||
from app.security import hash_password
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ── Roles ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/roles")
|
||||
async def _list_roles(_user: CurrentUser = None):
|
||||
return await list_roles()
|
||||
|
||||
|
||||
@router.post("/roles")
|
||||
async def _create_role(body: RoleCreate, _user: CurrentUser):
|
||||
try:
|
||||
return await create_role(body.model_dump())
|
||||
except Exception as e: # unique violation etc.
|
||||
raise HTTPException(400, f"创建角色失败:{str(e)[:200]}")
|
||||
|
||||
|
||||
@router.patch("/roles/{role_key}")
|
||||
async def _update_role(role_key: str, body: RoleUpdate, _user: CurrentUser):
|
||||
row = await update_role(role_key, body.model_dump(exclude_none=True))
|
||||
if not row:
|
||||
raise HTTPException(404, "角色不存在")
|
||||
return row
|
||||
|
||||
|
||||
@router.delete("/roles/{role_key}")
|
||||
async def _delete_role(role_key: str, _user: CurrentUser):
|
||||
try:
|
||||
ok = await delete_role(role_key)
|
||||
except ValueError as e:
|
||||
raise HTTPException(400, str(e))
|
||||
if not ok:
|
||||
raise HTTPException(404, "角色不存在")
|
||||
return {"deleted": role_key}
|
||||
|
||||
|
||||
# ── Capabilities ─────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/capabilities")
|
||||
async def _list_caps(_user: CurrentUser = None):
|
||||
return await list_capabilities()
|
||||
|
||||
|
||||
@router.post("/capabilities")
|
||||
async def _create_cap(body: CapabilityCreate, _user: CurrentUser):
|
||||
try:
|
||||
return await create_capability(body.model_dump())
|
||||
except Exception as e:
|
||||
raise HTTPException(400, f"创建能力项失败:{str(e)[:200]}")
|
||||
|
||||
|
||||
@router.delete("/capabilities/{cap_key}")
|
||||
async def _delete_cap(cap_key: str, _user: CurrentUser):
|
||||
await delete_capability(cap_key)
|
||||
return {"deleted": cap_key}
|
||||
|
||||
|
||||
# ── Permission matrix ────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/permission-matrix")
|
||||
async def _matrix(_user: CurrentUser = None):
|
||||
return await get_permission_matrix()
|
||||
|
||||
|
||||
@router.put("/permission-matrix")
|
||||
async def _set_cell(body: RoleCapCell, _user: CurrentUser):
|
||||
await set_role_cap(body.role_key, body.cap_key, body.value)
|
||||
return {"ok": True, "role_key": body.role_key, "cap_key": body.cap_key, "value": body.value}
|
||||
|
||||
|
||||
# ── Users ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/users")
|
||||
async def _list_users(_user: CurrentUser = None):
|
||||
return await list_users()
|
||||
|
||||
|
||||
@router.post("/users")
|
||||
async def _create_user(body: UserCreate, _user: CurrentUser):
|
||||
data = body.model_dump()
|
||||
roles = data.pop("roles", [])
|
||||
pw = data.pop("password")
|
||||
data["hashed_password"] = hash_password(pw)
|
||||
try:
|
||||
return await create_user(data, roles)
|
||||
except Exception as e:
|
||||
raise HTTPException(400, f"创建用户失败:{str(e)[:200]}")
|
||||
|
||||
|
||||
@router.patch("/users/{user_id}")
|
||||
async def _update_user(user_id: int, body: UserUpdate, _user: CurrentUser):
|
||||
data = body.model_dump(exclude_none=True)
|
||||
roles = data.pop("roles", None)
|
||||
if "password" in data:
|
||||
data["hashed_password"] = hash_password(data.pop("password"))
|
||||
row = await update_user(user_id, data, roles)
|
||||
if not row:
|
||||
raise HTTPException(404, "用户不存在")
|
||||
return row
|
||||
|
||||
|
||||
@router.delete("/users/{user_id}")
|
||||
async def _delete_user(user_id: int, _user: CurrentUser):
|
||||
await delete_user(user_id)
|
||||
return {"deleted": user_id}
|
||||
|
||||
|
||||
@router.get("/users/{user_id}/areas")
|
||||
async def _get_user_areas(user_id: int, _user: CurrentUser = None):
|
||||
return await list_user_areas(user_id)
|
||||
|
||||
|
||||
@router.put("/users/{user_id}/areas")
|
||||
async def _set_user_areas(user_id: int, body: UserAreasSet, _user: CurrentUser):
|
||||
"""Set the areas this user is responsible for (existing + free-text)."""
|
||||
area_ids = list(body.area_ids)
|
||||
for name in body.custom_areas:
|
||||
if name and name.strip():
|
||||
area_ids.append(await upsert_custom_area(name))
|
||||
await set_user_areas(user_id, area_ids)
|
||||
return await list_user_areas(user_id)
|
||||
Reference in New Issue
Block a user