Refactor and enhance OAuth2.0 implementation; update models and routes
- Refactored AdminSummaryData and AdminSummaryResponse classes for better clarity. - Added OAUTH type to SettingsType enum. - Cleaned up imports in webdav.py. - Updated admin router to improve summary data retrieval and response handling. - Enhanced file management routes with better condition handling and user storage updates. - Improved group management routes by optimizing data retrieval. - Refined task management routes for better condition handling. - Updated user management routes to streamline access token retrieval. - Implemented a new captcha verification structure with abstract base class. - Removed deprecated env.md file and replaced with a new structured version. - Introduced a unified OAuth2.0 client base class for GitHub and QQ integrations. - Enhanced password management with improved hashing strategies. - Added detailed comments and documentation throughout the codebase for clarity.
This commit is contained in:
@@ -2,14 +2,12 @@ from datetime import datetime, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from loguru import logger as l
|
||||
from sqlalchemy import and_
|
||||
|
||||
from middleware.auth import admin_required
|
||||
from middleware.dependencies import SessionDep
|
||||
from models import (
|
||||
User, ResponseBase,
|
||||
Setting, Object, ObjectType, Share, AdminSummaryResponse, MetricsSummary, LicenseInfo, VersionInfo,
|
||||
AdminSummaryData,
|
||||
)
|
||||
from models.base import SQLModelBase
|
||||
from models.setting import (
|
||||
@@ -75,8 +73,8 @@ async def router_admin_get_summary(session: SessionDep) -> AdminSummaryResponse:
|
||||
Returns:
|
||||
AdminSummaryResponse: 包含站点概况信息的响应模型。
|
||||
"""
|
||||
# 统计最近 12 天的数据
|
||||
days_count = 12
|
||||
# 统计最近 14 天的数据
|
||||
days_count = 14
|
||||
now = datetime.now()
|
||||
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
@@ -135,7 +133,7 @@ async def router_admin_get_summary(session: SessionDep) -> AdminSummaryResponse:
|
||||
site_urls: list[str] = []
|
||||
site_url_setting = await Setting.get(
|
||||
session,
|
||||
and_(Setting.type == SettingsType.BASIC, Setting.name == "siteURL"),
|
||||
(Setting.type == SettingsType.BASIC) & (Setting.name == "siteURL"),
|
||||
)
|
||||
if site_url_setting and site_url_setting.value:
|
||||
site_urls.append(site_url_setting.value)
|
||||
@@ -156,15 +154,13 @@ async def router_admin_get_summary(session: SessionDep) -> AdminSummaryResponse:
|
||||
commit="dev",
|
||||
)
|
||||
|
||||
data = AdminSummaryData(
|
||||
return AdminSummaryResponse(
|
||||
metrics_summary=metrics_summary,
|
||||
site_urls=site_urls,
|
||||
license=license_info,
|
||||
version=version_info,
|
||||
)
|
||||
|
||||
return AdminSummaryResponse(data=data)
|
||||
|
||||
@admin_router.get(
|
||||
path='/news',
|
||||
summary='获取社区新闻',
|
||||
@@ -203,7 +199,7 @@ async def router_admin_update_settings(
|
||||
for item in request.settings:
|
||||
existing = await Setting.get(
|
||||
session,
|
||||
and_(Setting.type == item.type, Setting.name == item.name)
|
||||
(Setting.type == item.type) & (Setting.name == item.name)
|
||||
)
|
||||
|
||||
if existing:
|
||||
@@ -245,7 +241,12 @@ async def router_admin_get_settings(
|
||||
if name:
|
||||
conditions.append(Setting.name == name)
|
||||
|
||||
condition = and_(*conditions) if conditions else None
|
||||
if conditions:
|
||||
condition = conditions[0]
|
||||
for c in conditions[1:]:
|
||||
condition = condition & c
|
||||
else:
|
||||
condition = None
|
||||
|
||||
settings = await Setting.get(session, condition, fetch_mode="all")
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ from uuid import UUID
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from loguru import logger as l
|
||||
from sqlalchemy import and_
|
||||
|
||||
from middleware.auth import admin_required
|
||||
from middleware.dependencies import SessionDep, TableViewRequestDep
|
||||
@@ -51,7 +50,12 @@ async def router_admin_get_file_list(
|
||||
if keyword:
|
||||
conditions.append(Object.name.ilike(f"%{keyword}%"))
|
||||
|
||||
condition = and_(*conditions) if len(conditions) > 1 else conditions[0]
|
||||
if len(conditions) > 1:
|
||||
condition = conditions[0]
|
||||
for c in conditions[1:]:
|
||||
condition = condition & c
|
||||
else:
|
||||
condition = conditions[0]
|
||||
result = await Object.get_with_count(session, condition, table_view=table_view, load=Object.owner)
|
||||
|
||||
# 构建响应
|
||||
@@ -197,13 +201,15 @@ async def router_admin_delete_file(
|
||||
except Exception as e:
|
||||
l.warning(f"删除物理文件失败: {e}")
|
||||
|
||||
# 更新用户存储量
|
||||
owner = await User.get(session, User.id == owner_id)
|
||||
if owner:
|
||||
owner.storage = max(0, owner.storage - file_size)
|
||||
await owner.save(session)
|
||||
# 更新用户存储量(使用 SQL UPDATE 直接更新,无需加载实例)
|
||||
from sqlmodel import update as sql_update
|
||||
stmt = sql_update(User).where(User.id == owner_id).values(
|
||||
storage=max(0, User.storage - file_size)
|
||||
)
|
||||
await session.exec(stmt)
|
||||
|
||||
await Object.delete(session, file_obj)
|
||||
# 使用条件删除
|
||||
await Object.delete(session, condition=Object.id == file_obj.id)
|
||||
|
||||
l.info(f"管理员删除了文件: {file_name}")
|
||||
return ResponseBase(data={"deleted": True})
|
||||
@@ -63,12 +63,13 @@ async def router_admin_get_group(
|
||||
:param group_id: 用户组UUID
|
||||
:return: 用户组详情
|
||||
"""
|
||||
group = await Group.get(session, Group.id == group_id, load=Group.options)
|
||||
group = await Group.get(session, Group.id == group_id, load=[Group.options, Group.policies])
|
||||
|
||||
if not group:
|
||||
raise HTTPException(status_code=404, detail="用户组不存在")
|
||||
|
||||
policies = await group.awaitable_attrs.policies
|
||||
# 直接访问已加载的关系,无需额外查询
|
||||
policies = group.policies
|
||||
user_count = await User.count(session, User.group_id == group_id)
|
||||
response = GroupDetailResponse.from_group(group, user_count, policies)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from loguru import logger as l
|
||||
from sqlalchemy import and_
|
||||
|
||||
from middleware.auth import admin_required
|
||||
from middleware.dependencies import SessionDep, TableViewRequestDep
|
||||
@@ -43,7 +42,12 @@ async def router_admin_get_task_list(
|
||||
if status:
|
||||
conditions.append(Task.status == status)
|
||||
|
||||
condition = and_(*conditions) if conditions else None
|
||||
if conditions:
|
||||
condition = conditions[0]
|
||||
for c in conditions[1:]:
|
||||
condition = condition & c
|
||||
else:
|
||||
condition = None
|
||||
result = await Task.get_with_count(session, condition, table_view=table_view, load=Task.user)
|
||||
|
||||
items: list[TaskSummary] = []
|
||||
|
||||
@@ -2,7 +2,7 @@ from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from loguru import logger as l
|
||||
from sqlalchemy import func, and_
|
||||
from sqlalchemy import func
|
||||
|
||||
from middleware.auth import admin_required
|
||||
from middleware.dependencies import SessionDep, TableViewRequestDep
|
||||
@@ -198,7 +198,7 @@ async def router_admin_calibrate_storage(
|
||||
from sqlmodel import select
|
||||
result = await session.execute(
|
||||
select(func.sum(Object.size), func.count(Object.id)).where(
|
||||
and_(Object.owner_id == user_id, Object.type == ObjectType.FILE)
|
||||
(Object.owner_id == user_id) & (Object.type == ObjectType.FILE)
|
||||
)
|
||||
)
|
||||
row = result.one()
|
||||
|
||||
@@ -233,7 +233,7 @@ async def upload_chunk(
|
||||
policy_id=upload_session.policy_id,
|
||||
reference_count=1,
|
||||
)
|
||||
physical_file = await physical_file.save(session)
|
||||
physical_file = await physical_file.save(session, commit=False)
|
||||
|
||||
# 创建 Object 记录
|
||||
file_object = Object(
|
||||
@@ -246,11 +246,18 @@ async def upload_chunk(
|
||||
owner_id=user_id,
|
||||
policy_id=upload_session.policy_id,
|
||||
)
|
||||
file_object = await file_object.save(session)
|
||||
file_object = await file_object.save(session, commit=False)
|
||||
file_object_id = file_object.id
|
||||
|
||||
# 删除上传会话
|
||||
await UploadSession.delete(session, upload_session)
|
||||
# 删除上传会话(使用条件删除)
|
||||
await UploadSession.delete(
|
||||
session,
|
||||
condition=UploadSession.id == upload_session.id,
|
||||
commit=False
|
||||
)
|
||||
|
||||
# 统一提交所有更改
|
||||
await session.commit()
|
||||
|
||||
l.info(f"文件上传完成: {file_object.name}, size={file_object.size}, id={file_object.id}")
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from fastapi import APIRouter
|
||||
from sqlalchemy import and_
|
||||
|
||||
from middleware.dependencies import SessionDep
|
||||
from models import ResponseBase, Setting, SettingsType, SiteConfigResponse
|
||||
@@ -55,5 +54,5 @@ async def router_site_config(session: SessionDep) -> SiteConfigResponse:
|
||||
dict: The site configuration.
|
||||
"""
|
||||
return SiteConfigResponse(
|
||||
title=await Setting.get(session, and_(Setting.type == SettingsType.BASIC, Setting.name == "siteName")),
|
||||
title=await Setting.get(session, (Setting.type == SettingsType.BASIC) & (Setting.name == "siteName")),
|
||||
)
|
||||
@@ -3,7 +3,6 @@ from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy import and_
|
||||
from webauthn import generate_registration_options
|
||||
from webauthn.helpers import options_to_json_dict
|
||||
from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired
|
||||
@@ -115,7 +114,7 @@ async def router_user_register(
|
||||
# 2. 获取默认用户组(从设置中读取 UUID)
|
||||
default_group_setting: models.Setting | None = await models.Setting.get(
|
||||
session,
|
||||
and_(models.Setting.type == models.SettingsType.REGISTER, models.Setting.name == "default_group")
|
||||
(models.Setting.type == models.SettingsType.REGISTER) & (models.Setting.name == "default_group")
|
||||
)
|
||||
if default_group_setting is None or not default_group_setting.value:
|
||||
logger.error("默认用户组不存在")
|
||||
@@ -352,18 +351,18 @@ async def router_user_authn_start(
|
||||
# TODO: 检查 WebAuthn 是否开启,用户是否有注册过 WebAuthn 设备等
|
||||
authn_setting = await models.Setting.get(
|
||||
session,
|
||||
and_(models.Setting.type == "authn", models.Setting.name == "authn_enabled")
|
||||
(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")
|
||||
(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")
|
||||
(models.Setting.type == "basic") & (models.Setting.name == "siteTitle")
|
||||
)
|
||||
|
||||
options = generate_registration_options(
|
||||
|
||||
Reference in New Issue
Block a user