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:
@@ -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}',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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登录',
|
||||
|
||||
Reference in New Issue
Block a user