diff --git a/sentence_api/main.py b/sentence_api/main.py index e77c2c0..4961e0a 100644 --- a/sentence_api/main.py +++ b/sentence_api/main.py @@ -43,6 +43,7 @@ from .models import ( UserDubShareSummary, UserPublic, UserResultSummary, + UserAdminListResponse, VideoDetailResponse, VideoListResponse, VideoSummary, @@ -249,6 +250,26 @@ def create_app( # 后台页面本身不缓存,静态 JS 通过版本号刷新,避免发布后浏览器继续跑旧逻辑。 return FileResponse(page, headers={"Cache-Control": "no-cache"}) + @application.get( + "/api/v1/admin/users", + response_model=UserAdminListResponse, + dependencies=[Depends(require_admin)], + ) + def list_registered_users() -> UserAdminListResponse: + return UserAdminListResponse( + users=video_repository.list_users() + ) + + @application.delete("/api/v1/admin/users/{user_id}", status_code=204) + def delete_registered_user( + user_id: str, + _: None = Depends(require_admin), + ) -> Response: + deleted = video_repository.delete_user(user_id) + if not deleted: + raise HTTPException(status_code=404, detail="User was not found.") + return Response(status_code=204) + @application.get("/api/v1/videos", response_model=VideoListResponse) def list_videos() -> VideoListResponse: return VideoListResponse( diff --git a/sentence_api/models.py b/sentence_api/models.py index 32d25e5..ea482ef 100644 --- a/sentence_api/models.py +++ b/sentence_api/models.py @@ -155,6 +155,19 @@ class AuthResponse(BaseModel): user: UserPublic +class UserAdminItem(BaseModel): + id: str + username: str + nickname: str + phone: Optional[str] = None + created_at: datetime + enrollment_count: int = 0 + + +class UserAdminListResponse(BaseModel): + users: List[UserAdminItem] + + class CourseSummary(BaseModel): video_hash: str title: str diff --git a/sentence_api/repository.py b/sentence_api/repository.py index 81cd404..5b6eff9 100644 --- a/sentence_api/repository.py +++ b/sentence_api/repository.py @@ -805,6 +805,25 @@ class VideoRepository: ).fetchone() return dict(row) if row else None + def list_users(self) -> List[Dict[str, Any]]: + with self._connect() as connection: + rows = connection.execute( + """ + SELECT u.id, u.username, u.nickname, u.phone, u.created_at, + COUNT(DISTINCT e.video_hash) AS enrollment_count + FROM users u + LEFT JOIN enrollments e ON e.user_id = u.id + GROUP BY u.id + ORDER BY u.created_at DESC + """ + ).fetchall() + return [dict(row) for row in rows] + + def delete_user(self, user_id: str) -> bool: + with self._connect() as connection: + cursor = connection.execute("DELETE FROM users WHERE id = ?", (user_id,)) + return cursor.rowcount > 0 + def authenticate_user(self, username: str, password: str) -> Optional[Dict[str, Any]]: with self._connect() as connection: row = connection.execute( diff --git a/sentence_api/static/admin.html b/sentence_api/static/admin.html index 1281135..7317d61 100644 --- a/sentence_api/static/admin.html +++ b/sentence_api/static/admin.html @@ -71,6 +71,29 @@
| 用户名 | +姓名 | +手机号 | +选修课程 | +注册时间 | +操作 | +
|---|