spwebsite / routes /admin.py
geqintan's picture
update
8d7d15e
from fastapi import APIRouter, File, UploadFile, HTTPException, status, Depends, Query
from fastapi.responses import FileResponse # Import FileResponse
from typing import List, Optional
import os
from pathlib import Path # Import Path
from supabase import create_client, Client
from gotrue.errors import AuthApiError
from pydantic import BaseModel # Import BaseModel for UserListResponse
from core.config import SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY
from core.dependencies import get_current_admin_user, get_supabase_client
from core.models import User, AdminUser, UserUpdate
from core.utils import get_password_hash
router = APIRouter()
UPLOAD_DIRECTORY = "static/images"
@router.post("/upload-image")
async def upload_image(file: UploadFile = File(...), current_user: User = Depends(get_current_admin_user)):
"""
上传图片到 /static/images 目录。
"""
if not os.path.exists(UPLOAD_DIRECTORY):
os.makedirs(UPLOAD_DIRECTORY)
file_location = os.path.join(UPLOAD_DIRECTORY, file.filename)
try:
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"filename": file.filename, "path": f"/{UPLOAD_DIRECTORY}/{file.filename}", "message": "图片上传成功"}
except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"图片上传失败: {e}")
@router.get("/images")
async def list_images(current_user: User = Depends(get_current_admin_user)):
"""
列出 /static/images 目录下的所有图片。
"""
if not os.path.exists(UPLOAD_DIRECTORY):
return []
image_files = []
for filename in os.listdir(UPLOAD_DIRECTORY):
if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.svg')):
image_files.append({
"filename": filename,
"path": f"/{UPLOAD_DIRECTORY}/{filename}"
})
return image_files
@router.delete("/images/{filename}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_image(filename: str, current_user: User = Depends(get_current_admin_user)):
"""
删除 /static/images 目录下的指定图片。
"""
file_path = Path(UPLOAD_DIRECTORY) / filename
if not file_path.is_file():
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="图片未找到")
try:
os.remove(file_path)
return # No content for 204
except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"图片删除失败: {e}")
class UserListResponse(BaseModel):
users: List[AdminUser]
total_count: int
@router.get("/users", response_model=UserListResponse) # Update response_model
async def get_all_users(
current_user: User = Depends(get_current_admin_user),
supabase_client: Client = Depends(get_supabase_client),
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=100),
search: Optional[str] = Query(None)
):
"""
获取所有用户列表(仅限管理员)。
"""
offset = (page - 1) * page_size
query = supabase_client.table('sp_users').select('id, email, email_verified, created_at, is_admin, disabled', count='exact') # Include disabled field
if search:
query = query.ilike('email', f"%{search}%") # Case-insensitive search by email
res = query.order('created_at', desc=True).range(offset, offset + page_size - 1).execute()
users = [AdminUser(**user) for user in res.data]
total_count = res.count
return {"users": users, "total_count": total_count}
@router.get("/users/{user_id}", response_model=AdminUser)
async def get_user_by_id(
user_id: str,
current_user: User = Depends(get_current_admin_user),
supabase_client: Client = Depends(get_supabase_client)
):
"""
根据用户ID获取单个用户信息(仅限管理员)。
"""
res = supabase_client.table('sp_users').select('id, email, email_verified, created_at, is_admin, disabled').eq('id', user_id).single().execute() # Include disabled field
if not res.data:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户未找到")
return AdminUser(**res.data)
@router.put("/users/{user_id}", response_model=AdminUser)
async def update_user(
user_id: str,
user_update: UserUpdate,
current_user: User = Depends(get_current_admin_user),
supabase_client: Client = Depends(get_supabase_client)
):
"""
更新用户信息(仅限管理员)。
"""
update_data = user_update.dict(exclude_unset=True)
if 'password' in update_data and update_data['password']:
update_data['password_hash'] = get_password_hash(update_data['password'])
del update_data['password'] # Remove plain password from update_data
# Handle disabled field update
if 'disabled' in update_data and update_data['disabled'] is not None:
# Supabase update for 'disabled' field
pass # The update_data dictionary already contains 'disabled' if it was set
if not update_data:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="没有提供更新数据")
res = supabase_client.table('sp_users').update(update_data).eq('id', user_id).execute()
if not res.data:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户未找到或更新失败")
# Fetch the updated user to return
updated_user_res = supabase_client.table('sp_users').select('id, email, email_verified, created_at, is_admin, disabled').eq('id', user_id).single().execute() # Include disabled field
return AdminUser(**updated_user_res.data)
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(
user_id: str,
current_user: User = Depends(get_current_admin_user),
supabase_client: Client = Depends(get_supabase_client)
):
"""
删除用户(仅限管理员)。
"""
# First, delete associated API keys
supabase_client.table('sp_user_api_keys').delete().eq('user_id', user_id).execute()
# Then delete the user from sp_users table
res = supabase_client.table('sp_users').delete().eq('id', user_id).execute()
if not res.data:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户未找到或删除失败")
return