From d271c81de7f8d7df03a3ea4d6ef21df44156dabb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E5=B0=8F=E4=B8=98?= Date: Thu, 18 Dec 2025 14:27:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=BB=84=E5=92=8C=E9=80=89=E9=A1=B9=E6=A8=A1=E5=9E=8B=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AD=98=E5=82=A8=E4=BF=A1=E6=81=AF=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/group.py | 104 ++++++++++++++++++++++++++---------- models/migration.py | 32 +++++++---- routers/controllers/user.py | 27 +++++++--- 3 files changed, 117 insertions(+), 46 deletions(-) diff --git a/models/group.py b/models/group.py index 453644a..71ffb63 100644 --- a/models/group.py +++ b/models/group.py @@ -1,44 +1,94 @@ -from typing import Optional, List, TYPE_CHECKING -from sqlmodel import Field, Relationship, text, Column, JSON +from typing import TYPE_CHECKING +from sqlmodel import Field, Relationship, text from .base import TableBase -from sqlmodel import SQLModel if TYPE_CHECKING: from .user import User -class GroupOptions(SQLModel): - archive_download: bool | None = False - archive_task: bool | None = False - share_download: bool | None = False - share_free: bool | None = False - webdav_proxy: bool | None = False - aria2: bool | None = False - relocate: bool | None = False - source_batch: int | None = 10 - redirected_source: bool | None = False - available_nodes: List[int] | None = [] - select_node: bool | None = False - advance_delete: bool | None = False + +class GroupOptions(TableBase, table=True): + """用户组选项模型""" + + group_id: int = Field(foreign_key="group.id", unique=True) + """关联的用户组ID""" + + archive_download: bool = False + """是否允许打包下载""" + + archive_task: bool = False + """是否允许创建打包任务""" + + share_download: bool = False + """是否允许分享下载""" + + share_free: bool = False + """是否免积分分享""" + + webdav_proxy: bool = False + """是否允许WebDAV代理""" + + aria2: bool = False + """是否允许使用aria2""" + + relocate: bool = False + """是否允许文件重定位""" + + source_batch: int = 10 + """批量获取源地址数量""" + + redirected_source: bool = False + """是否使用重定向源""" + + available_nodes: str = "[]" + """可用节点ID列表(JSON数组)""" + + select_node: bool = False + """是否允许选择节点""" + + advance_delete: bool = False + """是否允许高级删除""" + + # 反向关系 + group: "Group" = Relationship(back_populates="options") + class Group(TableBase, table=True): """用户组模型""" - name: str = Field(max_length=255, unique=True, description="用户组名") - policies: str | None = Field(default=None, max_length=255, description="允许的策略ID列表,逗号分隔") - max_storage: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="最大存储空间(字节)") - share_enabled: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否允许创建分享") - web_dav_enabled: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否允许使用WebDAV") - admin: bool = Field(default=False, description="是否为管理员组") - speed_limit: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="速度限制 (KB/s), 0为不限制") - options: GroupOptions = Field(default=GroupOptions, sa_column=Column(JSON), description="其他选项") + 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"}) + """最大存储空间(字节)""" + + share_enabled: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}) + """是否允许创建分享""" + + web_dav_enabled: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}) + """是否允许使用WebDAV""" + + admin: bool = False + """是否为管理员组""" + + speed_limit: int = Field(default=0, sa_column_kwargs={"server_default": "0"}) + """速度限制 (KB/s), 0为不限制""" + + # 一对一关系:用户组选项 + options: GroupOptions | None = Relationship( + back_populates="group", + sa_relationship_kwargs={"uselist": False} + ) # 关系:一个组可以有多个用户 - user: List["User"] = Relationship( + user: list["User"] = Relationship( back_populates="group", sa_relationship_kwargs={"foreign_keys": "User.group_id"} ) - previous_user: List["User"] = Relationship( + previous_user: list["User"] = Relationship( back_populates="previous_group", sa_relationship_kwargs={"foreign_keys": "User.previous_group_id"} - ) \ No newline at end of file + ) diff --git a/models/migration.py b/models/migration.py index 878a48a..4ff39b7 100644 --- a/models/migration.py +++ b/models/migration.py @@ -145,38 +145,48 @@ async def init_default_group() -> None: async for session in get_session(): # 未找到初始管理组时,则创建 if not await Group.get(session, Group.id == 1): - await Group( + admin_group = await Group( name="管理员", max_storage=1 * 1024 * 1024 * 1024, # 1GB share_enabled=True, web_dav_enabled=True, admin=True, - options=GroupOptions( - archive_download=True, - archive_task=True, - share_download=True, - aria2=True, - ).model_dump(), + ).save(session) + assert admin_group.id is not None + await GroupOptions( + group_id=admin_group.id, + archive_download=True, + archive_task=True, + share_download=True, + aria2=True, ).save(session) # 未找到初始注册会员时,则创建 if not await Group.get(session, Group.id == 2): - await Group( + member_group = await Group( name="注册会员", max_storage=1 * 1024 * 1024 * 1024, # 1GB share_enabled=True, web_dav_enabled=True, - options=GroupOptions(share_download=True).model_dump(), + ).save(session) + assert member_group.id is not None + await GroupOptions( + group_id=member_group.id, + share_download=True, ).save(session) # 未找到初始游客组时,则创建 if not await Group.get(session, Group.id == 3): - await Group( + guest_group = await Group( name="游客", policies="[]", share_enabled=False, web_dav_enabled=False, - options=GroupOptions(share_download=True).model_dump(), + ).save(session) + assert guest_group.id is not None + await GroupOptions( + group_id=guest_group.id, + share_download=True, ).save(session) async def init_default_user() -> None: diff --git a/routers/controllers/user.py b/routers/controllers/user.py index d078d75..3ee93bf 100644 --- a/routers/controllers/user.py +++ b/routers/controllers/user.py @@ -227,20 +227,31 @@ async def router_user_me( description='Get user storage information.', dependencies=[Depends(AuthRequired)], ) -def router_user_storage( +async def router_user_storage( + session: SessionDep, user: Annotated[models.user.User, Depends(AuthRequired)], ) -> models.response.ResponseModel: """ - Get user storage information. - - Returns: - dict: A dictionary containing user storage information. + 获取用户存储空间信息。 + + 返回值: + - used: 已使用空间(字节) + - free: 剩余空间(字节) + - total: 总容量(字节)= 用户组容量 """ + # 获取用户组的基础存储容量 + group = await models.Group.get(session, models.Group.id == user.group_id) + if not group: + raise HTTPException(status_code=500, detail="用户组不存在") + total: int = group.max_storage + used: int = user.storage + free: int = max(0, total - used) + return models.response.ResponseModel( data={ - "used": 0, - "free": 0, - "total": 0, + "used": used, + "free": free, + "total": total, } )