feat(ops): 课程/活动管理支持编辑与删除

- training_admin_bridge:+update_course/delete_course/update_activity/delete_activity(tdb.update_row/delete_row)
- schemas.operator:+CourseUpdateRequest/ActivityUpdateRequest(字段全可选,仅更新传入项)
- rbac_operator:+PUT/DELETE /admin/courses/{id}、/admin/activities/{id}(action:course.manage/activity.manage + 审计)
- 富操作(增/改/删/态)齐备

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pine
2026-08-27 10:33:15 +08:00
parent 8697119aaa
commit 5840a5cb4c
3 changed files with 127 additions and 1 deletions
+63 -1
View File
@@ -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(
+34
View File
@@ -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
+30
View File
@@ -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]: