|
|
from fastapi import APIRouter, File, UploadFile, HTTPException, status, Depends, Query |
|
|
from fastapi.responses import FileResponse |
|
|
from typing import List, Optional |
|
|
import os |
|
|
from pathlib import Path |
|
|
from supabase import create_client, Client |
|
|
from gotrue.errors import AuthApiError |
|
|
from pydantic import BaseModel |
|
|
|
|
|
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 |
|
|
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) |
|
|
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') |
|
|
|
|
|
if search: |
|
|
query = query.ilike('email', f"%{search}%") |
|
|
|
|
|
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() |
|
|
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'] |
|
|
|
|
|
|
|
|
if 'disabled' in update_data and update_data['disabled'] is not None: |
|
|
|
|
|
pass |
|
|
|
|
|
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="用户未找到或更新失败") |
|
|
|
|
|
|
|
|
updated_user_res = supabase_client.table('sp_users').select('id, email, email_verified, created_at, is_admin, disabled').eq('id', user_id).single().execute() |
|
|
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) |
|
|
): |
|
|
""" |
|
|
删除用户(仅限管理员)。 |
|
|
""" |
|
|
|
|
|
supabase_client.table('sp_user_api_keys').delete().eq('user_id', user_id).execute() |
|
|
|
|
|
|
|
|
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 |
|
|
|