File size: 6,428 Bytes
133609a 8d7d15e 133609a 8d7d15e 133609a 8d7d15e 133609a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | 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
|