diff --git a/app/api/routers/rbac_operator.py b/app/api/routers/rbac_operator.py index 390e721..a3f42c3 100644 --- a/app/api/routers/rbac_operator.py +++ b/app/api/routers/rbac_operator.py @@ -11,7 +11,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request, Response from pydantic import BaseModel from ..dependencies import get_db -from ..schemas.operator import TaskCreateRequest, TaskUpdateRequest, TaskStatusRequest, TaskAssignRequest, TaskRecommendRequest, TaskSelectRecommendRequest, ProviderCreateRequest, ProviderUpdateRequest, ContentCreateRequest, ContentStatusRequest, ConfigUpdateRequest, ComputePingResponse, ComputeBalanceRequest, ComputeProvisionRequest, ComputeProvisionResponse, CourseCreateRequest, CourseStatusRequest, ActivityCreateRequest, ActivityStatusRequest, BookingUpdateRequest, TestCreateRequest, TestStatusRequest +from ..schemas.operator import TaskCreateRequest, TaskUpdateRequest, TaskStatusRequest, TaskAssignRequest, TaskRecommendRequest, TaskSelectRecommendRequest, ProviderCreateRequest, ProviderUpdateRequest, ContentCreateRequest, ContentStatusRequest, ConfigUpdateRequest, ComputePingResponse, ComputeBalanceRequest, ComputeProvisionRequest, ComputeProvisionResponse, CourseCreateRequest, CourseStatusRequest, CourseUpdateRequest, ActivityCreateRequest, ActivityStatusRequest, ActivityUpdateRequest, BookingUpdateRequest, TestCreateRequest, TestStatusRequest from ...rbac import require_permission, require_roles, write_audit from ...infrastructure.repositories import Database from ...services import compute_client @@ -441,6 +441,37 @@ async def set_course_status( return course +@router.put("/courses/{course_id}", summary="编辑课程") +async def update_course( + course_id: str, + req: CourseUpdateRequest, + request: Request, + db: Database = Depends(get_db), + actor: dict = Depends(require_permission("action:course.manage")), +): + course = training.update_course(course_id, req.model_dump(exclude_unset=True, exclude_none=True)) + if course is None: + raise HTTPException(status_code=404, detail="Course not found") + await write_audit(db, action="course.update", resource="course", resource_id=course_id, + detail=course.get("title", ""), user=actor, request=request) + return course + + +@router.delete("/courses/{course_id}", summary="删除课程") +async def delete_course( + course_id: str, + request: Request, + db: Database = Depends(get_db), + actor: dict = Depends(require_permission("action:course.manage")), +): + ok = training.delete_course(course_id) + if not ok: + raise HTTPException(status_code=404, detail="Course not found") + await write_audit(db, action="course.delete", resource="course", resource_id=course_id, + detail=course_id, user=actor, request=request) + return {"ok": True} + + # ── 培训业务 · 活动(桥接 app.training events 表)────────────────────────── @router.get("/activities", summary="活动列表") async def list_activities( @@ -479,6 +510,37 @@ async def set_activity_status( return activity +@router.put("/activities/{event_id}", summary="编辑活动") +async def update_activity( + event_id: str, + req: ActivityUpdateRequest, + request: Request, + db: Database = Depends(get_db), + actor: dict = Depends(require_permission("action:activity.manage")), +): + activity = training.update_activity(event_id, req.model_dump(exclude_unset=True, exclude_none=True)) + if activity is None: + raise HTTPException(status_code=404, detail="Activity not found") + await write_audit(db, action="activity.update", resource="activity", resource_id=event_id, + detail=activity.get("title", ""), user=actor, request=request) + return activity + + +@router.delete("/activities/{event_id}", summary="删除活动") +async def delete_activity( + event_id: str, + request: Request, + db: Database = Depends(get_db), + actor: dict = Depends(require_permission("action:activity.manage")), +): + ok = training.delete_activity(event_id) + if not ok: + raise HTTPException(status_code=404, detail="Activity not found") + await write_audit(db, action="activity.delete", resource="activity", resource_id=event_id, + detail=event_id, user=actor, request=request) + return {"ok": True} + + # ── 培训业务 · 报名(桥接 app.training bookings 表)──────────────────────── @router.get("/bookings", summary="报名列表") async def list_bookings( diff --git a/app/api/schemas/operator.py b/app/api/schemas/operator.py index ec23ac9..5c07206 100644 --- a/app/api/schemas/operator.py +++ b/app/api/schemas/operator.py @@ -128,6 +128,23 @@ class CourseStatusRequest(BaseModel): status: str # draft/published/ended/offline +class CourseUpdateRequest(BaseModel): + """课程更新(字段全可选,仅更新传入项)。""" + title: str | None = None + category: str | None = None + level: str | None = None + subtitle: str | None = None + desc: str | None = None + price: float | None = None + status: str | None = None + start_at: str | None = None + end_at: str | None = None + venue: str | None = None + quota: int | None = None + image: str | None = None + host: str | None = None + + class ActivityCreateRequest(BaseModel): title: str type: str | None = None # free/salon @@ -148,6 +165,23 @@ class ActivityStatusRequest(BaseModel): status: str # open/invite/pending/done/ended +class ActivityUpdateRequest(BaseModel): + """活动更新(字段全可选,仅更新传入项)。""" + title: str | None = None + type: str | None = None + mode: str | None = None + status: str | None = None + subtitle: str | None = None + desc: str | None = None + location: str | None = None + host: str | None = None + image: str | None = None + link: str | None = None + start_at: str | None = None + duration_min: int | None = None + capacity: int | None = None + + class BookingUpdateRequest(BaseModel): status: str | None = None audit_status: str | None = None diff --git a/app/services/training_admin_bridge.py b/app/services/training_admin_bridge.py index 9956ad4..28cf0da 100644 --- a/app/services/training_admin_bridge.py +++ b/app/services/training_admin_bridge.py @@ -44,6 +44,21 @@ def set_course_status(course_id: str, status: str) -> dict | None: return tdb.fetch_by_id("courses", course_id) +def update_course(course_id: str, patch: dict) -> dict | None: + if tdb.fetch_by_id("courses", course_id) is None: + return None + if patch: + tdb.update_row("courses", course_id, patch) + return tdb.fetch_by_id("courses", course_id) + + +def delete_course(course_id: str) -> bool: + if tdb.fetch_by_id("courses", course_id) is None: + return False + tdb.delete_row("courses", course_id) + return True + + # ── 活动(events 表)───────────────────────────────────────────────────── def list_activities(status: str | None = None) -> list[dict]: @@ -70,6 +85,21 @@ def set_activity_status(event_id: str, status: str) -> dict | None: return tdb.fetch_by_id("events", event_id) +def update_activity(event_id: str, patch: dict) -> dict | None: + if tdb.fetch_by_id("events", event_id) is None: + return None + if patch: + tdb.update_row("events", event_id, patch) + return tdb.fetch_by_id("events", event_id) + + +def delete_activity(event_id: str) -> bool: + if tdb.fetch_by_id("events", event_id) is None: + return False + tdb.delete_row("events", event_id) + return True + + # ── 报名(bookings 表)─────────────────────────────────────────────────── def list_bookings(status: str | None = None, audit_status: str | None = None) -> list[dict]: