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