feat: Implement API routers for user, tag, vas, webdav, and slave functionalities

- Added user authentication and registration endpoints with JWT support.
- Created tag management routes for creating and deleting tags.
- Implemented value-added service (VAS) endpoints for managing storage packs and orders.
- Developed WebDAV account management routes for creating, updating, and deleting accounts.
- Introduced slave router for handling file uploads, downloads, and aria2 task management.
- Enhanced JWT utility functions for token creation and secret key management.
- Established lifespan management for FastAPI application startup and shutdown processes.
- Integrated password handling utilities with Argon2 hashing and two-factor authentication support.
This commit is contained in:
2025-12-19 18:04:34 +08:00
parent 11b67bde6d
commit 51b6de921b
30 changed files with 223 additions and 534 deletions

View File

@@ -1,7 +1,7 @@
from sqlmodel import SQLModel
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
from sqlmodel.ext.asyncio.session import AsyncSession
from pkg.conf import appmeta
from utils.conf import appmeta
from sqlalchemy.orm import sessionmaker
from typing import AsyncGenerator

View File

@@ -8,6 +8,7 @@ from .base import TableBase, SQLModelBase, UUIDTableBase
if TYPE_CHECKING:
from .user import User
from .policy import Policy
# ==================== Base 模型 ====================
@@ -70,6 +71,10 @@ class GroupResponse(GroupBase, GroupOptionsBase):
# ==================== 数据库模型 ====================
# GroupPolicyLink 定义在 policy.py 中以避免循环导入
from .policy import GroupPolicyLink
class GroupOptions(GroupOptionsBase, TableBase, table=True):
"""用户组选项模型"""
@@ -104,9 +109,6 @@ class Group(GroupBase, UUIDTableBase, table=True):
name: str = Field(max_length=255, unique=True)
"""用户组名"""
policies: str | None = Field(default=None, max_length=255)
"""允许的策略ID列表逗号分隔"""
max_storage: int = Field(default=0, sa_column_kwargs={"server_default": "0"})
"""最大存储空间(字节)"""
@@ -128,6 +130,12 @@ class Group(GroupBase, UUIDTableBase, table=True):
sa_relationship_kwargs={"uselist": False}
)
# 多对多关系:用户组可以关联多个存储策略
policies: list["Policy"] = Relationship(
back_populates="groups",
link_model=GroupPolicyLink,
)
# 关系:一个组可以有多个用户
user: list["User"] = Relationship(
back_populates="group",

View File

@@ -1,8 +1,8 @@
from .setting import Setting, SettingsType
from .color import ThemeResponse
from pkg.conf.appmeta import BackendVersion
from pkg.password.pwd import Password
from utils.conf.appmeta import BackendVersion
from utils.password.pwd import Password
from loguru import logger as log
async def migration() -> None:
@@ -138,12 +138,17 @@ async def init_default_settings() -> None:
async def init_default_group() -> None:
from .group import Group, GroupOptions
from .policy import Policy, GroupPolicyLink
from .setting import Setting
from .database import get_session
log.info('初始化用户组...')
async for session in get_session():
# 获取默认存储策略
default_policy = await Policy.get(session, Policy.name == "本地存储")
default_policy_id = default_policy.id if default_policy else None
# 未找到初始管理组时,则创建
if not await Group.get(session, Group.name == "管理员"):
admin_group = Group(
@@ -167,6 +172,14 @@ async def init_default_group() -> None:
advance_delete=True,
).save(session)
# 关联默认存储策略
if default_policy_id:
session.add(GroupPolicyLink(
group_id=admin_group_id,
policy_id=default_policy_id,
))
await session.commit()
# 未找到初始注册会员时,则创建
if not await Group.get(session, Group.name == "注册会员"):
member_group = Group(
@@ -183,6 +196,14 @@ async def init_default_group() -> None:
share_download=True,
).save(session)
# 关联默认存储策略
if default_policy_id:
session.add(GroupPolicyLink(
group_id=member_group_id,
policy_id=default_policy_id,
))
await session.commit()
# 更新 default_group 设置为注册会员组的 UUID
default_group_setting = await Setting.get(session, Setting.name == "default_group")
if default_group_setting:
@@ -204,6 +225,8 @@ async def init_default_group() -> None:
share_download=True,
).save(session)
# 游客组不关联存储策略(无法上传)
async def init_default_user() -> None:
from .user import User
from .group import Group

View File

@@ -1,12 +1,24 @@
from typing import TYPE_CHECKING
from uuid import UUID
from enum import StrEnum
from sqlmodel import Field, Relationship, text
from .base import UUIDTableBase
from .base import SQLModelBase, UUIDTableBase
if TYPE_CHECKING:
from .object import Object
from .group import Group
class GroupPolicyLink(SQLModelBase, table=True):
"""用户组与存储策略的多对多关联表"""
group_id: UUID = Field(foreign_key="group.id", primary_key=True)
"""用户组UUID"""
policy_id: UUID = Field(foreign_key="policy.id", primary_key=True)
"""存储策略UUID"""
class PolicyType(StrEnum):
LOCAL = "local"
@@ -60,6 +72,12 @@ class Policy(UUIDTableBase, table=True):
# 关系
objects: list["Object"] = Relationship(back_populates="policy")
"""策略下的所有对象"""
# 多对多关系:策略可以被多个用户组使用
groups: list["Group"] = Relationship(
back_populates="policies",
link_model=GroupPolicyLink,
)
@staticmethod
async def create(

View File

@@ -10,17 +10,8 @@ from sqlmodel import Field
from .base import SQLModelBase
# [TODO] 未来把这拆了,直接按需返回状态码
class ResponseModel(SQLModelBase):
class ResponseBase(SQLModelBase):
"""通用响应模型"""
code: int = Field(default=0, ge=0, lt=60000)
"""系统内部状态码0表示成功其他表示失败"""
data: Any = None
"""响应数据"""
msg: str | None = None
"""响应消息,可以是错误消息或信息提示"""
instance_id: uuid.UUID = Field(default_factory=uuid.uuid4)
"""实例ID用于标识请求的唯一性"""