All checks were successful
continuous-integration/drone/push Build is passing
安全修复: - 创建 UserSelfUpdate schema,禁止用户修改自己的 role 和 is_active - /users/me 端点现在使用 UserSelfUpdate 而非 UserUpdate 安全增强: - 添加 SecurityHeadersMiddleware 中间件 - X-Content-Type-Options: nosniff - X-Frame-Options: DENY - X-XSS-Protection: 1; mode=block - Referrer-Policy: strict-origin-when-cross-origin - Permissions-Policy: 禁用敏感功能 - Cache-Control: API响应不缓存
168 lines
4.2 KiB
Python
168 lines
4.2 KiB
Python
"""
|
||
用户相关 Schema
|
||
"""
|
||
|
||
from datetime import datetime
|
||
from typing import List, Optional
|
||
|
||
from pydantic import EmailStr, Field, field_validator
|
||
|
||
from .base import BaseSchema
|
||
|
||
|
||
class UserBase(BaseSchema):
|
||
"""用户基础信息"""
|
||
|
||
username: str = Field(..., min_length=3, max_length=50)
|
||
email: Optional[EmailStr] = None
|
||
phone: Optional[str] = Field(None, pattern=r"^1[3-9]\d{9}$")
|
||
full_name: Optional[str] = Field(None, max_length=100)
|
||
avatar_url: Optional[str] = None
|
||
bio: Optional[str] = None
|
||
role: str = Field(default="trainee", pattern="^(admin|manager|trainee)$")
|
||
gender: Optional[str] = Field(None, pattern="^(male|female)$")
|
||
school: Optional[str] = Field(None, max_length=100)
|
||
major: Optional[str] = Field(None, max_length=100)
|
||
|
||
|
||
class UserCreate(UserBase):
|
||
"""创建用户"""
|
||
|
||
password: str = Field(..., min_length=6, max_length=100)
|
||
|
||
@field_validator("password")
|
||
def validate_password(cls, v):
|
||
if len(v) < 6:
|
||
raise ValueError("密码长度至少为6位")
|
||
return v
|
||
|
||
|
||
class UserUpdate(BaseSchema):
|
||
"""更新用户(管理员使用)"""
|
||
|
||
email: Optional[EmailStr] = None
|
||
phone: Optional[str] = Field(None, pattern=r"^1[3-9]\d{9}$")
|
||
full_name: Optional[str] = Field(None, max_length=100)
|
||
avatar_url: Optional[str] = None
|
||
bio: Optional[str] = None
|
||
role: Optional[str] = Field(None, pattern="^(admin|manager|trainee)$")
|
||
is_active: Optional[bool] = None
|
||
gender: Optional[str] = Field(None, pattern="^(male|female)$")
|
||
school: Optional[str] = Field(None, max_length=100)
|
||
major: Optional[str] = Field(None, max_length=100)
|
||
|
||
|
||
class UserSelfUpdate(BaseSchema):
|
||
"""用户自己更新个人信息(不允许修改role和is_active)"""
|
||
|
||
email: Optional[EmailStr] = None
|
||
phone: Optional[str] = Field(None, pattern=r"^1[3-9]\d{9}$")
|
||
full_name: Optional[str] = Field(None, max_length=100)
|
||
avatar_url: Optional[str] = None
|
||
bio: Optional[str] = None
|
||
gender: Optional[str] = Field(None, pattern="^(male|female)$")
|
||
school: Optional[str] = Field(None, max_length=100)
|
||
major: Optional[str] = Field(None, max_length=100)
|
||
|
||
|
||
class UserPasswordUpdate(BaseSchema):
|
||
"""更新密码"""
|
||
|
||
old_password: str
|
||
new_password: str = Field(..., min_length=6, max_length=100)
|
||
|
||
|
||
class UserInDBBase(UserBase):
|
||
"""数据库中的用户基础信息"""
|
||
|
||
id: int
|
||
is_active: bool
|
||
is_verified: bool
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
last_login_at: Optional[datetime] = None
|
||
|
||
|
||
class User(UserInDBBase):
|
||
"""用户信息(不含敏感数据)"""
|
||
|
||
teams: List["TeamBasic"] = []
|
||
|
||
|
||
class UserWithPassword(UserInDBBase):
|
||
"""用户信息(含密码)"""
|
||
|
||
hashed_password: str
|
||
|
||
|
||
# Team Schemas
|
||
class TeamBase(BaseSchema):
|
||
"""团队基础信息"""
|
||
|
||
name: str = Field(..., min_length=2, max_length=100)
|
||
code: str = Field(..., min_length=2, max_length=50)
|
||
description: Optional[str] = None
|
||
team_type: str = Field(
|
||
default="department", pattern="^(department|project|study_group)$"
|
||
)
|
||
|
||
|
||
class TeamCreate(TeamBase):
|
||
"""创建团队"""
|
||
|
||
leader_id: Optional[int] = None
|
||
parent_id: Optional[int] = None
|
||
|
||
|
||
class TeamUpdate(BaseSchema):
|
||
"""更新团队"""
|
||
|
||
name: Optional[str] = Field(None, min_length=2, max_length=100)
|
||
description: Optional[str] = None
|
||
leader_id: Optional[int] = None
|
||
is_active: Optional[bool] = None
|
||
|
||
|
||
class TeamBasic(BaseSchema):
|
||
"""团队基本信息"""
|
||
|
||
id: int
|
||
name: str
|
||
code: str
|
||
team_type: str
|
||
|
||
|
||
class Team(TeamBase):
|
||
"""团队完整信息"""
|
||
|
||
id: int
|
||
is_active: bool
|
||
leader_id: Optional[int] = None
|
||
parent_id: Optional[int] = None
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
member_count: Optional[int] = 0
|
||
|
||
|
||
class TeamWithMembers(Team):
|
||
"""团队信息(含成员)"""
|
||
|
||
members: List[User] = []
|
||
leader: Optional[User] = None
|
||
|
||
|
||
# 避免循环引用
|
||
UserBase.model_rebuild()
|
||
User.model_rebuild()
|
||
Team.model_rebuild()
|
||
|
||
|
||
# Filter schemas
|
||
class UserFilter(BaseSchema):
|
||
"""用户筛选条件"""
|
||
|
||
role: Optional[str] = Field(None, pattern="^(admin|manager|trainee)$")
|
||
is_active: Optional[bool] = None
|
||
team_id: Optional[int] = None
|
||
keyword: Optional[str] = None # 搜索用户名、邮箱、姓名
|