- models: 删除 Profile 模型,字段合并到 User;User 新增云朵/公开/收藏计数器;Cloud 新增 favorite_count - schemas/serializers: 展平 UserOut/AuthOut/MeOut/AdminUserOut,移除嵌套 profile - deps: user.profile.role→user.role,移除 selectinload profile - auth/register: 注册时直接设置 user 字段,不再创建 Profile - clouds: 创建/删除云朵时同步 user.cloud_count 计数器,状态变化时同步 public_cloud_count - favorites: 点赞/取消时同步 cloud.favorite_count 计数器 - admin: 审批/隐藏/批量删时同步 public_cloud_count 计数器 - profiles/stats: 用计数器替代实时 COUNT 查询 - alembic: 新增 migration 合并 profiles 到 users,初始化 counters
78 lines
2.6 KiB
Python
78 lines
2.6 KiB
Python
from datetime import datetime, timezone
|
|
from typing import Annotated
|
|
|
|
from fastapi import Depends, HTTPException, status
|
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from app.database import get_db
|
|
from app.models import RefreshSession, User
|
|
from app.security import decode_access_token
|
|
|
|
|
|
DbSession = Annotated[AsyncSession, Depends(get_db)]
|
|
bearer = HTTPBearer(auto_error=False)
|
|
|
|
|
|
async def _resolve_user(
|
|
credentials: HTTPAuthorizationCredentials,
|
|
db: AsyncSession,
|
|
) -> User:
|
|
if credentials.scheme.lower() != "bearer":
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的认证方式")
|
|
try:
|
|
payload = decode_access_token(credentials.credentials)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc
|
|
|
|
result = await db.execute(
|
|
select(User)
|
|
.join(
|
|
RefreshSession,
|
|
(RefreshSession.id == payload.session_id)
|
|
& (RefreshSession.user_id == User.id),
|
|
)
|
|
.where(
|
|
User.id == payload.user_id,
|
|
RefreshSession.revoked_at.is_(None),
|
|
RefreshSession.expires_at > datetime.now(timezone.utc),
|
|
)
|
|
)
|
|
user = result.scalar_one_or_none()
|
|
if not user:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="登录状态已失效")
|
|
if user.is_disabled:
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="账号已被禁用")
|
|
return user
|
|
|
|
|
|
async def get_current_user(
|
|
db: DbSession,
|
|
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(bearer)],
|
|
) -> User:
|
|
if credentials is None:
|
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="请先登录")
|
|
return await _resolve_user(credentials, db)
|
|
|
|
|
|
async def get_optional_user(
|
|
db: DbSession,
|
|
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(bearer)],
|
|
) -> User | None:
|
|
if credentials is None:
|
|
return None
|
|
return await _resolve_user(credentials, db)
|
|
|
|
|
|
async def get_admin_user(user: Annotated[User, Depends(get_current_user)]) -> User:
|
|
if user.role != "admin":
|
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="需要管理员权限")
|
|
return user
|
|
|
|
|
|
CurrentUser = Annotated[User, Depends(get_current_user)]
|
|
OptionalUser = Annotated[User | None, Depends(get_optional_user)]
|
|
AdminUser = Annotated[User, Depends(get_admin_user)]
|