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:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user