feat: add database session dependency for FastAPI routes

- Introduced a new dependency in `middleware/dependencies.py` to provide an asynchronous database session using SQLModel.
- This dependency can be utilized in route functions to facilitate database operations.
This commit is contained in:
2025-11-27 22:18:50 +08:00
parent b364b740ca
commit b02a4638da
25 changed files with 909 additions and 748 deletions

View File

@@ -1,5 +1,7 @@
from fastapi import APIRouter, Depends
from middleware.auth import AdminRequired
from middleware.dependencies import SessionDep
from models import User
from models.response import ResponseModel
@@ -273,31 +275,30 @@ def router_admin_get_users(
dependencies=[Depends(AdminRequired)],
)
async def router_admin_create_user(
user: User
session: SessionDep,
user: User,
) -> ResponseModel:
"""
创建一个新的用户,设置用户名、密码等信息。
Returns:
ResponseModel: 包含创建结果的响应模型。
"""
try:
existing_user = await User.get(email=user.email)
existing_user = await User.get(session, User.username == user.username)
if existing_user:
return ResponseModel(
code=400,
message="User with this email already exists."
code=400,
message="User with this username already exists."
)
await user.create(**user.model_dump())
await user.save(session)
except Exception as e:
return ResponseModel(
code=500,
message=str(e)
)
else:
return ResponseModel(
data=user.model_dump()
)
return ResponseModel(data=user.model_dump())
@admin_user_router.patch(
path='/{user_id}',

View File

@@ -1,26 +1,45 @@
from fastapi import APIRouter
from sqlalchemy import and_
from middleware.dependencies import SessionDep
from models.response import ResponseModel
from models.setting import Setting
site_router = APIRouter(
prefix="/site",
tags=["site"],
)
async def _get_setting(session: SessionDep, type_: str, name: str) -> str | None:
"""获取设置值"""
setting = await Setting.get(session, and_(Setting.type == type_, Setting.name == name))
return setting.value if setting else None
async def _get_setting_bool(session: SessionDep, type_: str, name: str) -> bool:
"""获取布尔类型设置值"""
value = await _get_setting(session, type_, name)
return value == "1" if value else False
@site_router.get(
path="/ping",
summary="测试用路由",
description="A simple endpoint to check if the site is up and running.",
response_model=ResponseModel,)
response_model=ResponseModel,
)
def router_site_ping():
"""
Ping the site to check if it is up and running.
Returns:
str: A message indicating the site is running.
"""
from pkg.conf.appmeta import BackendVersion
return ResponseModel(data=BackendVersion)
@site_router.get(
path='/captcha',
summary='验证码',
@@ -30,49 +49,48 @@ def router_site_ping():
def router_site_captcha():
"""
Get a Base64 captcha image.
Returns:
str: A Base64 encoded string of the captcha image.
"""
pass
@site_router.get(
path='/config',
summary='站点全局配置',
description='Get the configuration file.',
response_model=ResponseModel,
)
async def router_site_config():
async def router_site_config(session: SessionDep):
"""
Get the configuration file.
Returns:
dict: The site configuration.
"""
from models.setting import Setting
return ResponseModel(
data={
"title": await Setting.get(type='basic', name='siteName'),
"loginCaptcha": await Setting.get(type='login', name='login_captcha', format='bool'),
"regCaptcha": await Setting.get(type='login', name='reg_captcha', format='bool'),
"forgetCaptcha": await Setting.get(type='login', name='forget_captcha', format='bool'),
"emailActive": await Setting.get(type='login', name='email_active', format='bool'),
"title": await _get_setting(session, "basic", "siteName"),
"loginCaptcha": await _get_setting_bool(session, "login", "login_captcha"),
"regCaptcha": await _get_setting_bool(session, "login", "reg_captcha"),
"forgetCaptcha": await _get_setting_bool(session, "login", "forget_captcha"),
"emailActive": await _get_setting_bool(session, "login", "email_active"),
"QQLogin": None,
"themes": await Setting.get(type='basic', name='themes'),
"defaultTheme": await Setting.get(type='basic', name='defaultTheme'),
"themes": await _get_setting(session, "basic", "themes"),
"defaultTheme": await _get_setting(session, "basic", "defaultTheme"),
"score_enabled": None,
"share_score_rate": None,
"home_view_method": await Setting.get(type='view', name='home_view_method'),
"share_view_method": await Setting.get(type='view', name='share_view_method'),
"authn": await Setting.get(type='authn', name='authn_enabled', format='bool'),
"home_view_method": await _get_setting(session, "view", "home_view_method"),
"share_view_method": await _get_setting(session, "view", "share_view_method"),
"authn": await _get_setting_bool(session, "authn", "authn_enabled"),
"user": {},
"captcha_type": None,
"captcha_ReCaptchaKey": await Setting.get(type='captcha', name='captcha_ReCaptchaKey'),
"captcha_CloudflareKey": await Setting.get(type='captcha', name='captcha_CloudflareKey'),
"captcha_ReCaptchaKey": await _get_setting(session, "captcha", "captcha_ReCaptchaKey"),
"captcha_CloudflareKey": await _get_setting(session, "captcha", "captcha_CloudflareKey"),
"captcha_tcaptcha_appid": None,
"site_notice": None,
"registerEnabled": await Setting.get(type='register', name='register_enabled', format='bool'),
"registerEnabled": await _get_setting_bool(session, "register", "register_enabled"),
"app_promotion": None,
"wopi_exts": None,
"app_feedback": None,

View File

@@ -1,24 +1,15 @@
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm
from middleware.auth import AuthRequired, AuthRequired
import models
from loguru import logger as log
import service
from webauthn import (
generate_registration_options,
verify_authentication_response,
options_to_json,
base64url_to_bytes,
)
from sqlalchemy import and_
from webauthn import generate_registration_options
from webauthn.helpers import options_to_json_dict
from webauthn.helpers.structs import (
PublicKeyCredentialDescriptor,
UserVerificationRequirement,
)
import models
import service
from middleware.auth import AuthRequired
from middleware.dependencies import SessionDep
user_router = APIRouter(
prefix="/user",
@@ -37,22 +28,22 @@ user_settings_router = APIRouter(
description='User login endpoint.',
)
async def router_user_session(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
session: SessionDep,
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
) -> models.response.TokenModel:
username = form_data.username
password = form_data.password
is_login, detail = await service.user.Login(
models.request.LoginRequest(
username=username, password=password
)
result = await service.user.Login(
session,
models.request.LoginRequest(username=username, password=password),
)
if isinstance(detail, models.response.TokenModel):
return detail
elif detail is None:
if isinstance(result, models.response.TokenModel):
return result
elif result is None:
raise HTTPException(status_code=401, detail="Invalid username or password")
elif detail is False:
elif result is False:
raise HTTPException(status_code=403, detail="User account is banned or not fully registered")
else:
raise HTTPException(status_code=500, detail="Internal server error during login")
@@ -201,38 +192,34 @@ def router_user_avatar(id: str, size: int = 128) -> models.response.ResponseMode
response_model=models.response.ResponseModel,
)
async def router_user_me(
session: SessionDep,
user: Annotated[models.user.User, Depends(AuthRequired)],
) -> models.response.ResponseModel:
"""
获取用户信息.
:return: response.ResponseModel containing user information.
:rtype: response.ResponseModel
"""
group = await models.Group.get(id=user.group_id)
user_group = models.response.groupModel(
group = await models.Group.get(session, models.Group.id == user.group_id)
user_group = models.response.GroupModel(
id=group.id,
name=group.name,
allowShare=group.share_enabled,
)
users = models.response.userModel(
id=user.id,
username=user.email,
nickname=user.nick,
status=user.status,
created_at=user.created_at,
score=user.score,
group=user_group,
).model_dump()
return models.response.ResponseModel(
data=users
)
users = models.response.UserModel(
id=user.id,
username=user.username,
nickname=user.nick,
status=user.status,
created_at=user.created_at,
score=user.score,
group=user_group,
).model_dump()
return models.response.ResponseModel(data=users)
@user_router.get(
path='/storage',
@@ -264,30 +251,41 @@ def router_user_storage(
dependencies=[Depends(AuthRequired)],
)
async def router_user_authn_start(
user: Annotated[models.user.User, Depends(AuthRequired)]
session: SessionDep,
user: Annotated[models.user.User, Depends(AuthRequired)],
) -> models.response.ResponseModel:
"""
Initialize WebAuthn login for a user.
Returns:
dict: A dictionary containing WebAuthn initialization information.
"""
# [TODO] 检查 WebAuthn 是否开启,用户是否有注册过 WebAuthn 设备等
if not await models.Setting.get(type="authn", name="authn_enabled", format="bool"):
# TODO: 检查 WebAuthn 是否开启,用户是否有注册过 WebAuthn 设备等
authn_setting = await models.Setting.get(
session,
and_(models.Setting.type == "authn", models.Setting.name == "authn_enabled")
)
if not authn_setting or authn_setting.value != "1":
raise HTTPException(status_code=400, detail="WebAuthn is not enabled")
site_url_setting = await models.Setting.get(
session,
and_(models.Setting.type == "basic", models.Setting.name == "siteURL")
)
site_title_setting = await models.Setting.get(
session,
and_(models.Setting.type == "basic", models.Setting.name == "siteTitle")
)
options = generate_registration_options(
rp_id=await models.Setting.get(type="basic", name="siteURL"),
rp_name=await models.Setting.get(type="basic", name="siteTitle"),
user_name=user.email,
user_display_name=user.nick or user.email,
)
return models.response.ResponseModel(
data=options_to_json_dict(options)
rp_id=site_url_setting.value if site_url_setting else "",
rp_name=site_title_setting.value if site_title_setting else "",
user_name=user.username,
user_display_name=user.nick or user.username,
)
return models.response.ResponseModel(data=options_to_json_dict(options))
@user_router.put(
path='/authn/finish',
summary='WebAuthn登录',