From d831c9c0d6b89051d38f5b351620c7b37fea69ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E5=B0=8F=E4=B8=98?= Date: Thu, 12 Feb 2026 20:15:35 +0800 Subject: [PATCH] feat: implement PATCH /user/settings/{option} and fix timezone range to UTC-12~+14 - Add SettingOption StrEnum (nickname/language/timezone) for path param validation - Add UserSettingUpdateRequest DTO with Pydantic constraints - Implement endpoint: extract value by option name, validate non-null for required fields - Fix timezone upper bound from 12 to 14 (UTC+14 exists, e.g. Line Islands) Co-Authored-By: Claude Opus 4.6 --- routers/api/v1/user/settings/__init__.py | 35 +++++++++++++++++------- sqlmodels/__init__.py | 2 ++ sqlmodels/user.py | 28 ++++++++++++++++++- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/routers/api/v1/user/settings/__init__.py b/routers/api/v1/user/settings/__init__.py index 23becd3..75b5738 100644 --- a/routers/api/v1/user/settings/__init__.py +++ b/routers/api/v1/user/settings/__init__.py @@ -8,6 +8,7 @@ from middleware.auth import auth_required from middleware.dependencies import SessionDep from sqlmodels import ( BUILTIN_DEFAULT_COLORS, ThemePreset, UserThemeUpdateRequest, + SettingOption, UserSettingUpdateRequest, ) from sqlmodels.color import ThemeColorsBase from utils import JWT, Password, http_exceptions @@ -209,21 +210,35 @@ async def router_user_settings_theme( @user_settings_router.patch( path='/{option}', summary='更新用户设定', - description='Update user settings.', - dependencies=[Depends(auth_required)], - status_code=204, + status_code=status.HTTP_204_NO_CONTENT, ) -def router_user_settings_patch(option: str) -> None: +async def router_user_settings_patch( + session: SessionDep, + user: Annotated[sqlmodels.user.User, Depends(auth_required)], + option: SettingOption, + request: UserSettingUpdateRequest, +) -> None: """ - Update user settings. + 更新单个用户设置项 - Args: - option (str): The setting option to update. + 路径参数: + - option: 设置项名称(nickname / language / timezone) - Returns: - dict: A dictionary containing the result of the settings update. + 请求体: + - 包含与 option 同名的字段及其新值 + + 错误处理: + - 422: 无效的 option 或字段值不符合约束 + - 400: 必填字段值缺失 """ - http_exceptions.raise_not_implemented() + value = getattr(request, option.value) + + # language / timezone 不允许设为 null + if value is None and option != SettingOption.NICKNAME: + http_exceptions.raise_bad_request(f"设置项 {option.value} 不允许为空") + + setattr(user, option.value, value) + await user.save(session) @user_settings_router.get( diff --git a/sqlmodels/__init__.py b/sqlmodels/__init__.py index 91787f7..fd5749d 100644 --- a/sqlmodels/__init__.py +++ b/sqlmodels/__init__.py @@ -14,6 +14,8 @@ from .user import ( UserResponse, UserSettingResponse, UserThemeUpdateRequest, + SettingOption, + UserSettingUpdateRequest, WebAuthnInfo, UserTwoFactorResponse, # 管理员DTO diff --git a/sqlmodels/user.py b/sqlmodels/user.py index 1dade14..437f9b1 100644 --- a/sqlmodels/user.py +++ b/sqlmodels/user.py @@ -308,6 +308,32 @@ class UserThemeUpdateRequest(SQLModelBase): """颜色配置""" +class SettingOption(StrEnum): + """用户可自助修改的设置选项""" + + NICKNAME = "nickname" + """昵称""" + + LANGUAGE = "language" + """语言偏好""" + + TIMEZONE = "timezone" + """时区""" + + +class UserSettingUpdateRequest(SQLModelBase): + """用户设置更新请求 DTO,根据 option 路径参数仅使用对应字段""" + + nickname: str | None = Field(default=None, max_length=50) + """昵称(传 null 可清除)""" + + language: str | None = Field(default=None, max_length=5) + """语言偏好""" + + timezone: int | None = Field(default=None, ge=-12, le=14) + """时区,UTC 偏移小时数""" + + class UserTwoFactorResponse(SQLModelBase): """用户两步验证信息 DTO""" @@ -474,7 +500,7 @@ class User(UserBase, UUIDTableBaseMixin): language: str = Field(default="zh-CN", max_length=5) """语言偏好""" - timezone: int = Field(default=8, ge=-12, le=12) + timezone: int = Field(default=8, ge=-12, le=14) """时区,UTC 偏移小时数""" # 外键