feat: Enhance models and README with additional features and improvements
- Updated README to include KodBox in project vision. - Added model descriptions for clarity in Download, File, Folder, Group, Node, Order, Policy, Redeem, Report, Request, Response, Setting, Share, SourceLink, StoragePack, Tag, Task, User, and WebDAV. - Changed optional fields from Optional[...] to the new union type syntax (e.g., str | None). - Improved foreign key references in models for consistency. - Refactored relationships in models to use singular forms where appropriate. - Updated login service to reflect changes in request model types.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
|
||||
本来此项目并没有这么快开始,但由于我曾经使用过的某个类似产品的作者吃相实在难看,故决定自己实现一个。
|
||||
|
||||
此项目的愿景是集百家之长(Cloudreve + Alist + FnOS)。你也可以考虑付费支持我们的发展 -> `DiskNext Pro`.
|
||||
此项目的愿景是集百家之长(Cloudreve + Alist + FnOS + KodBox)。你也可以考虑付费支持我们的发展 -> `DiskNext Pro`.
|
||||
|
||||
目前正处于 `OMEGA` 实验阶段,比 `Alpha` 版还更早期,仅供测试。
|
||||
|
||||
@@ -25,12 +25,12 @@
|
||||
- 在线预览/编辑多种文件,包括但不限于视频、图片、音频、PDF、ePub、Office、Markdown、图表等
|
||||
- 自定义主题色、深浅色主题、PWA、i18n
|
||||
|
||||
## :alembic: 技术栈
|
||||
## ⚗️ 技术栈
|
||||
|
||||
* [Python](https://www.python.org/) + [FastAPI](https://fastapi.tiangolo.com/)
|
||||
|
||||
<!-- * [React](https://github.com/facebook/react) + [Redux](https://github.com/reduxjs/redux) + [Material-UI](https://github.com/mui-org/material-ui) -->
|
||||
|
||||
## :scroll: 许可证
|
||||
## 📜 许可证
|
||||
|
||||
GPL V3
|
||||
GPL V3
|
||||
|
||||
@@ -11,7 +11,8 @@ class DownloadBase(SQLModelBase):
|
||||
pass
|
||||
|
||||
class Download(DownloadBase, UUIDTableBase, table=True):
|
||||
__tablename__ = 'downloads'
|
||||
"""离线下载任务模型"""
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("node_id", "g_id", name="uq_download_node_gid"),
|
||||
)
|
||||
@@ -21,18 +22,18 @@ class Download(DownloadBase, UUIDTableBase, table=True):
|
||||
source: str = Field(description="来源URL或标识")
|
||||
total_size: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="总大小(字节)")
|
||||
downloaded_size: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="已下载大小(字节)")
|
||||
g_id: Optional[str] = Field(default=None, index=True, description="Aria2 GID")
|
||||
g_id: str | None = Field(default=None, index=True, description="Aria2 GID")
|
||||
speed: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="下载速度 (bytes/s)")
|
||||
parent: Optional[str] = Field(default=None, description="父任务标识")
|
||||
attrs: Optional[str] = Field(default=None, description="额外属性 (JSON格式)")
|
||||
parent: str | None = Field(default=None, description="父任务标识")
|
||||
attrs: str | None = Field(default=None, description="额外属性 (JSON格式)")
|
||||
# attrs 示例: {"gid":"65c5faf38374cc63","status":"removed","totalLength":"0","completedLength":"0","uploadLength":"0","bitfield":"","downloadSpeed":"0","uploadSpeed":"0","infoHash":"ca159db2b1e78f6e95fd972be72251f967f639d4","numSeeders":"0","seeder":"","pieceLength":"16384","numPieces":"0","connections":"0","errorCode":"31","errorMessage":"","followedBy":null,"belongsTo":"","dir":"/data/ccaaDown/aria2/7a208304-9126-46d2-ba47-a6959f236a07","files":[{"index":"1","path":"[METADATA]zh-cn_windows_11_consumer_editions_version_21h2_updated_aug_2022_x64_dvd_a29983d5.iso","length":"0","completedLength":"0","selected":"true","uris":[]}],"bittorrent":{"announceList":[["udp://tracker.opentrackr.org:1337/announce"],["udp://9.rarbg.com:2810/announce"],["udp://tracker.openbittorrent.com:6969/announce"],["https://opentracker.i2p.rocks:443/announce"],["http://tracker.openbittorrent.com:80/announce"],["udp://open.stealth.si:80/announce"],["udp://tracker.torrent.eu.org:451/announce"],["udp://exodus.desync.com:6969/announce"],["udp://tracker.tiny-vps.com:6969/announce"],["udp://tracker.pomf.se:80/announce"],["udp://tracker.moeking.me:6969/announce"],["udp://tracker.dler.org:6969/announce"],["udp://open.demonii.com:1337/announce"],["udp://explodie.org:6969/announce"],["udp://chouchou.top:8080/announce"],["udp://bt.oiyo.tk:6969/announce"],["https://tracker.nanoha.org:443/announce"],["https://tracker.lilithraws.org:443/announce"],["http://tracker3.ctix.cn:8080/announce"],["http://tracker.nucozer-tracker.ml:2710/announce"]],"comment":"","creationDate":0,"mode":"","info":{"name":""}}}
|
||||
error: Optional[str] = Field(default=None, description="错误信息")
|
||||
error: str | None = Field(default=None, description="错误信息")
|
||||
dst: str = Field(description="目标存储路径")
|
||||
|
||||
# 外键
|
||||
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")
|
||||
task_id: Optional[int] = Field(default=None, foreign_key="tasks.id", index=True, description="关联的任务ID")
|
||||
node_id: int = Field(foreign_key="nodes.id", index=True, description="执行下载的节点ID")
|
||||
user_id: int = Field(foreign_key="user.id", index=True, description="所属用户ID")
|
||||
task_id: int | None = Field(default=None, foreign_key="task.id", index=True, description="关联的任务ID")
|
||||
node_id: int = Field(foreign_key="node.id", index=True, description="执行下载的节点ID")
|
||||
|
||||
# 关系
|
||||
user: "User" = Relationship(back_populates="downloads")
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlmodel import Field, Relationship, UniqueConstraint, CheckConstraint, Index
|
||||
from .base import TableBase
|
||||
from datetime import datetime
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .user import User
|
||||
@@ -11,7 +10,6 @@ if TYPE_CHECKING:
|
||||
from .source_link import SourceLink
|
||||
|
||||
class File(TableBase, table=True):
|
||||
__tablename__ = 'files'
|
||||
__table_args__ = (
|
||||
UniqueConstraint("folder_id", "name", name="uq_file_folder_name_active"),
|
||||
CheckConstraint("name NOT LIKE '%/%' AND name NOT LIKE '%\\%'", name="ck_file_name_no_slash"),
|
||||
@@ -21,18 +19,18 @@ class File(TableBase, table=True):
|
||||
)
|
||||
|
||||
name: str = Field(max_length=255, description="文件名")
|
||||
source_name: Optional[str] = Field(default=None, description="源文件名")
|
||||
source_name: str | None = Field(default=None, description="源文件名")
|
||||
size: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="文件大小(字节)")
|
||||
upload_session_id: Optional[str] = Field(default=None, max_length=255, unique=True, index=True, description="分块上传会话ID")
|
||||
file_metadata: Optional[str] = Field(default=None, description="文件元数据 (JSON格式)") # 后续可以考虑模型继承?
|
||||
upload_session_id: str | None = Field(default=None, max_length=255, unique=True, index=True, description="分块上传会话ID")
|
||||
file_metadata: str | None = Field(default=None, description="文件元数据 (JSON格式)") # 后续可以考虑模型继承?
|
||||
|
||||
# 外键
|
||||
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")
|
||||
folder_id: int = Field(foreign_key="folders.id", index=True, description="所在目录ID")
|
||||
policy_id: int = Field(foreign_key="policies.id", index=True, description="所属存储策略ID")
|
||||
user_id: int = Field(foreign_key="user.id", index=True, description="所属用户ID")
|
||||
folder_id: int = Field(foreign_key="folder.id", index=True, description="所在目录ID")
|
||||
policy_id: int = Field(foreign_key="policy.id", index=True, description="所属存储策略ID")
|
||||
|
||||
# 关系
|
||||
user: list["User"] = Relationship(back_populates="files")
|
||||
folder: list["Folder"] = Relationship(back_populates="files")
|
||||
policy: list["Policy"] = Relationship(back_populates="files")
|
||||
user: "User" = Relationship(back_populates="files")
|
||||
folder: "Folder" = Relationship(back_populates="files")
|
||||
policy: "Policy" = Relationship(back_populates="files")
|
||||
source_links: list["SourceLink"] = Relationship(back_populates="file")
|
||||
@@ -10,7 +10,6 @@ if TYPE_CHECKING:
|
||||
from .file import File
|
||||
|
||||
class Folder(TableBase, table=True):
|
||||
__tablename__ = 'folders'
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"owner_id",
|
||||
@@ -27,9 +26,9 @@ class Folder(TableBase, table=True):
|
||||
name: str = Field(max_length=255, nullable=False, description="目录名")
|
||||
|
||||
# 外键
|
||||
parent_id: Optional[int] = Field(default=None, foreign_key="folders.id", index=True, description="父目录ID")
|
||||
owner_id: int = Field(foreign_key="users.id", index=True, description="所有者用户ID")
|
||||
policy_id: int = Field(foreign_key="policies.id", index=True, description="所属存储策略ID")
|
||||
parent_id: int | None = Field(default=None, foreign_key="folder.id", index=True, description="父目录ID")
|
||||
owner_id: int = Field(foreign_key="user.id", index=True, description="所有者用户ID")
|
||||
policy_id: int = Field(foreign_key="policy.id", index=True, description="所属存储策略ID")
|
||||
|
||||
# 关系
|
||||
owner: "User" = Relationship(back_populates="folders")
|
||||
|
||||
@@ -8,24 +8,24 @@ if TYPE_CHECKING:
|
||||
from .user import User
|
||||
|
||||
class GroupOptions(SQLModel):
|
||||
archive_download: Optional[bool] = False
|
||||
archive_task: Optional[bool] = False
|
||||
share_download: Optional[bool] = False
|
||||
share_free: Optional[bool] = False
|
||||
webdav_proxy: Optional[bool] = False
|
||||
aria2: Optional[bool] = False
|
||||
relocate: Optional[bool] = False
|
||||
source_batch: Optional[int] = 10
|
||||
redirected_source: Optional[bool] = False
|
||||
available_nodes: Optional[List[int]] = []
|
||||
select_node: Optional[bool] = False
|
||||
advance_delete: Optional[bool] = False
|
||||
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 Group(TableBase, table=True):
|
||||
__tablename__ = 'groups'
|
||||
"""用户组模型"""
|
||||
|
||||
name: str = Field(max_length=255, unique=True, description="用户组名")
|
||||
policies: Optional[str] = Field(default=None, max_length=255, description="允许的策略ID列表,逗号分隔")
|
||||
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")
|
||||
@@ -34,11 +34,11 @@ class Group(TableBase, table=True):
|
||||
options: GroupOptions = Field(default=GroupOptions, sa_column=Column(JSON), description="其他选项")
|
||||
|
||||
# 关系:一个组可以有多个用户
|
||||
users: List["User"] = Relationship(
|
||||
user: List["User"] = Relationship(
|
||||
back_populates="group",
|
||||
sa_relationship_kwargs={"foreign_keys": "User.group_id"}
|
||||
)
|
||||
previous_users: List["User"] = Relationship(
|
||||
previous_user: List["User"] = Relationship(
|
||||
back_populates="previous_group",
|
||||
sa_relationship_kwargs={"foreign_keys": "User.previous_group_id"}
|
||||
)
|
||||
@@ -46,13 +46,13 @@ class Group(TableBase, table=True):
|
||||
@staticmethod
|
||||
async def create(
|
||||
group: Optional["Group"] = None,
|
||||
name: Optional[str] = None,
|
||||
policies: Optional[str] = None,
|
||||
name: str | None = None,
|
||||
policies: str | None = None,
|
||||
max_storage: int = 0,
|
||||
share_enabled: bool = False,
|
||||
web_dav_enabled: bool = False,
|
||||
speed_limit: int = 0,
|
||||
options: Optional[dict] = None,
|
||||
options: dict | None = None,
|
||||
) -> "Group":
|
||||
"""
|
||||
向数据库内添加用户组。如果提供了 `group` 参数,则使用该对象,否则创建一个新的用户组对象。
|
||||
@@ -128,13 +128,13 @@ class Group(TableBase, table=True):
|
||||
@staticmethod
|
||||
async def set(
|
||||
id: int,
|
||||
name: Optional[str] = None,
|
||||
policies: Optional[str] = None,
|
||||
max_storage: Optional[int] = None,
|
||||
share_enabled: Optional[bool] = None,
|
||||
web_dav_enabled: Optional[bool] = None,
|
||||
speed_limit: Optional[int] = None,
|
||||
options: Optional[str] = None
|
||||
name: str | None = None,
|
||||
policies: str | None = None,
|
||||
max_storage: int | None = None,
|
||||
share_enabled: bool | None = None,
|
||||
web_dav_enabled: bool | None = None,
|
||||
speed_limit: int | None = None,
|
||||
options: str | None = None
|
||||
) -> Optional["Group"]:
|
||||
"""
|
||||
更新用户组信息。
|
||||
@@ -142,19 +142,19 @@ class Group(TableBase, table=True):
|
||||
:param id: 用户组ID
|
||||
:type id: int
|
||||
:param name: 用户组名
|
||||
:type name: Optional[str]
|
||||
:type name: str | None
|
||||
:param policies: 允许的策略ID列表,逗号分隔
|
||||
:type policies: Optional[str]
|
||||
:type policies: str | None
|
||||
:param max_storage: 最大存储空间(字节)
|
||||
:type max_storage: Optional[int]
|
||||
:type max_storage: int | None
|
||||
:param share_enabled: 是否允许创建分享
|
||||
:type share_enabled: Optional[bool]
|
||||
:type share_enabled: bool | None
|
||||
:param web_dav_enabled: 是否允许使用WebDAV
|
||||
:type web_dav_enabled: Optional[bool]
|
||||
:type web_dav_enabled: bool | None
|
||||
:param speed_limit: 速度限制 (KB/s), 0为不限制
|
||||
:type speed_limit: Optional[int]
|
||||
:type speed_limit: int | None
|
||||
:param options: 其他选项 (JSON格式)
|
||||
:type options: Optional[str]
|
||||
:type options: str | None
|
||||
:return: 更新后的用户组对象或 None
|
||||
:rtype: Optional[Group]
|
||||
"""
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from sqlmodel import Field, Relationship, text, Column, func, DateTime
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlmodel import Field, Relationship, text
|
||||
from .base import TableBase
|
||||
from datetime import datetime
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .download import Download
|
||||
|
||||
class Node(TableBase, table=True):
|
||||
__tablename__ = 'nodes'
|
||||
"""节点模型"""
|
||||
|
||||
status: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="节点状态: 0=正常, 1=离线")
|
||||
name: str = Field(max_length=255, unique=True, description="节点名称")
|
||||
type: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="节点类型")
|
||||
server: str = Field(max_length=255, description="节点地址(IP或域名)")
|
||||
slave_key: Optional[str] = Field(default=None, description="从机通讯密钥")
|
||||
master_key: Optional[str] = Field(default=None, description="主机通讯密钥")
|
||||
slave_key: str | None = Field(default=None, description="从机通讯密钥")
|
||||
master_key: str | None = Field(default=None, description="主机通讯密钥")
|
||||
aria2_enabled: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否启用Aria2")
|
||||
aria2_options: Optional[str] = Field(default=None, description="Aria2配置 (JSON格式)")
|
||||
aria2_options: str | None = Field(default=None, description="Aria2配置 (JSON格式)")
|
||||
rank: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="节点排序权重")
|
||||
|
||||
# 关系
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from sqlmodel import Field, Relationship, Column, func, DateTime
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlmodel import Field, Relationship
|
||||
from .base import TableBase
|
||||
from datetime import datetime
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .user import User
|
||||
|
||||
class Order(TableBase, table=True):
|
||||
__tablename__ = 'orders'
|
||||
"""订单模型"""
|
||||
|
||||
order_no: str = Field(max_length=255, unique=True, index=True, description="订单号,唯一")
|
||||
type: int = Field(description="订单类型")
|
||||
method: Optional[str] = Field(default=None, max_length=255, description="支付方式")
|
||||
product_id: Optional[int] = Field(default=None, description="商品ID")
|
||||
method: str | None = Field(default=None, max_length=255, description="支付方式")
|
||||
product_id: int | None = Field(default=None, description="商品ID")
|
||||
num: int = Field(default=1, sa_column_kwargs={"server_default": "1"}, description="购买数量")
|
||||
name: str = Field(max_length=255, description="商品名称")
|
||||
price: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="订单价格(分)")
|
||||
status: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="订单状态: 0=待支付, 1=已完成, 2=已取消")
|
||||
|
||||
# 外键
|
||||
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")
|
||||
user_id: int = Field(foreign_key="user.id", index=True, description="所属用户ID")
|
||||
|
||||
# 关系
|
||||
user: "User" = Relationship(back_populates="orders")
|
||||
@@ -1,30 +1,29 @@
|
||||
|
||||
from typing import Optional, List, TYPE_CHECKING
|
||||
from sqlmodel import Field, Relationship, text, Column, func, DateTime
|
||||
from sqlmodel import Field, Relationship, text
|
||||
from .base import TableBase
|
||||
from datetime import datetime
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .file import File
|
||||
from .folder import Folder
|
||||
|
||||
class Policy(TableBase, table=True):
|
||||
__tablename__ = 'policies'
|
||||
"""存储策略模型"""
|
||||
|
||||
name: str = Field(max_length=255, unique=True, description="策略名称")
|
||||
type: str = Field(max_length=255, description="存储类型 (e.g. 'local', 's3')")
|
||||
server: Optional[str] = Field(default=None, max_length=255, description="服务器地址(本地策略为路径)")
|
||||
bucket_name: Optional[str] = Field(default=None, max_length=255, description="存储桶名称")
|
||||
server: str | None = Field(default=None, max_length=255, description="服务器地址(本地策略为路径)")
|
||||
bucket_name: str | None = Field(default=None, max_length=255, description="存储桶名称")
|
||||
is_private: bool = Field(default=True, sa_column_kwargs={"server_default": text("true")}, description="是否为私有空间")
|
||||
base_url: Optional[str] = Field(default=None, max_length=255, description="访问文件的基础URL")
|
||||
access_key: Optional[str] = Field(default=None, description="Access Key")
|
||||
secret_key: Optional[str] = Field(default=None, description="Secret Key")
|
||||
base_url: str | None = Field(default=None, max_length=255, description="访问文件的基础URL")
|
||||
access_key: str | None = Field(default=None, description="Access Key")
|
||||
secret_key: str | None = Field(default=None, description="Secret Key")
|
||||
max_size: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="允许上传的最大文件尺寸(字节)")
|
||||
auto_rename: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否自动重命名")
|
||||
dir_name_rule: Optional[str] = Field(default=None, max_length=255, description="目录命名规则")
|
||||
file_name_rule: Optional[str] = Field(default=None, max_length=255, description="文件命名规则")
|
||||
dir_name_rule: str | None = Field(default=None, max_length=255, description="目录命名规则")
|
||||
file_name_rule: str | None = Field(default=None, max_length=255, description="文件命名规则")
|
||||
is_origin_link_enable: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否开启源链接访问")
|
||||
options: Optional[str] = Field(default=None, description="其他选项 (JSON格式)")
|
||||
options: str | None = Field(default=None, description="其他选项 (JSON格式)")
|
||||
# options 示例: {"token":"","file_type":null,"mimetype":"","od_redirect":"http://127.0.0.1:8000/...","chunk_size":52428800,"s3_path_style":false}
|
||||
|
||||
# 关系
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
|
||||
from typing import Optional
|
||||
from sqlmodel import Field, text, Column, func, DateTime
|
||||
from sqlmodel import Field, text
|
||||
from .base import TableBase
|
||||
from datetime import datetime
|
||||
|
||||
class Redeem(TableBase, table=True):
|
||||
__tablename__ = 'redeems'
|
||||
"""兑换码模型"""
|
||||
|
||||
type: int = Field(description="兑换码类型")
|
||||
product_id: Optional[int] = Field(default=None, description="关联的商品/权益ID")
|
||||
product_id: int | None = Field(default=None, description="关联的商品/权益ID")
|
||||
num: int = Field(default=1, sa_column_kwargs={"server_default": "1"}, description="可兑换数量/时长等")
|
||||
code: str = Field(unique=True, index=True, description="兑换码,唯一")
|
||||
used: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否已使用")
|
||||
@@ -1,20 +1,19 @@
|
||||
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from sqlmodel import Field, Relationship, Column, func, DateTime
|
||||
from typing import TYPE_CHECKING
|
||||
from sqlmodel import Field, Relationship
|
||||
from .base import TableBase
|
||||
from datetime import datetime
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .share import Share
|
||||
|
||||
class Report(TableBase, table=True):
|
||||
__tablename__ = 'reports'
|
||||
"""举报模型"""
|
||||
|
||||
reason: int = Field(description="举报原因代码")
|
||||
description: Optional[str] = Field(default=None, max_length=255, description="补充描述")
|
||||
description: str | None = Field(default=None, max_length=255, description="补充描述")
|
||||
|
||||
# 外键
|
||||
share_id: int = Field(foreign_key="shares.id", index=True, description="被举报的分享ID")
|
||||
share_id: int = Field(foreign_key="share.id", index=True, description="被举报的分享ID")
|
||||
|
||||
# 关系
|
||||
share: "Share" = Relationship(back_populates="reports")
|
||||
@@ -13,5 +13,5 @@ class LoginRequest(BaseModel):
|
||||
"""
|
||||
username: str = Field(..., description="用户名或邮箱")
|
||||
password: str = Field(..., description="用户密码")
|
||||
captcha: Optional[str] = Field(None, description="验证码")
|
||||
twoFaCode: Optional[str] = Field(None, description="两步验证代码")
|
||||
captcha: str | None = Field(None, description="验证码")
|
||||
twoFaCode: str | None = Field(None, description="两步验证代码")
|
||||
@@ -13,7 +13,7 @@ class ResponseModel(BaseModel):
|
||||
'''
|
||||
code: int = Field(default=0, description="系统内部状态码, 0表示成功,其他表示失败", lt=60000, gt=0)
|
||||
data: Union[dict, list, str, int, float, None] = Field(None, description="响应数据")
|
||||
msg: Optional[str] = Field(default=None, description="响应消息,可以是错误消息或信息提示")
|
||||
msg: str | None = Field(default=None, description="响应消息,可以是错误消息或信息提示")
|
||||
instance_id: str = Field(default_factory=lambda: str(uuid4()), description="实例ID,用于标识请求的唯一性")
|
||||
|
||||
class ThemeModel(BaseModel):
|
||||
@@ -81,12 +81,12 @@ class SiteConfigModel(ResponseModel):
|
||||
title: str = Field(default="DiskNext", description="网站标题")
|
||||
themes: dict = Field(default_factory=dict, description="网站主题配置")
|
||||
default_theme: str = Field(default="default", description="默认主题RGB色号")
|
||||
site_notice: Optional[str] = Field(default=None, description="网站公告")
|
||||
site_notice: str | None = Field(default=None, description="网站公告")
|
||||
user: dict = Field(default_factory=dict, description="用户信息")
|
||||
logo_light: Optional[str] = Field(default=None, description="网站Logo URL")
|
||||
logo_dark: Optional[str] = Field(default=None, description="网站Logo URL(深色模式)")
|
||||
logo_light: str | None = Field(default=None, description="网站Logo URL")
|
||||
logo_dark: str | None = Field(default=None, description="网站Logo URL(深色模式)")
|
||||
captcha_type: Literal['none', 'default', 'gcaptcha', 'cloudflare turnstile'] = Field(default='none', description="验证码类型")
|
||||
captcha_key: Optional[str] = Field(default=None, description="验证码密钥")
|
||||
captcha_key: str | None = Field(default=None, description="验证码密钥")
|
||||
|
||||
class AuthnModel(BaseModel):
|
||||
'''
|
||||
@@ -100,7 +100,7 @@ class UserSettingModel(BaseModel):
|
||||
用户设置模型
|
||||
'''
|
||||
authn: Optional[AuthnModel] = Field(default=None, description="认证信息")
|
||||
group_expires: Optional[datetime] = Field(default=None, description="用户组过期时间")
|
||||
group_expires: datetime | None = Field(default=None, description="用户组过期时间")
|
||||
prefer_theme: str = Field(default="#5898d4", description="用户首选主题")
|
||||
qq: str | bool = Field(default=False, description="QQ号")
|
||||
themes: dict = Field(default_factory=dict, description="用户主题配置")
|
||||
|
||||
@@ -34,18 +34,19 @@ SETTINGS_TYPE = Literal[
|
||||
|
||||
# 数据库模型
|
||||
class Setting(TableBase, table=True):
|
||||
__tablename__ = 'settings'
|
||||
"""设置模型"""
|
||||
|
||||
__table_args__ = (UniqueConstraint("type", "name", name="uq_setting_type_name"),)
|
||||
|
||||
type: str = Field(max_length=255, description="设置类型/分组")
|
||||
name: str = Field(max_length=255, description="设置项名称")
|
||||
value: Optional[str] = Field(default=None, description="设置值")
|
||||
value: str | None = Field(default=None, description="设置值")
|
||||
|
||||
@staticmethod
|
||||
async def add(
|
||||
type: SETTINGS_TYPE = None,
|
||||
name: str = None,
|
||||
value: Optional[str] = None
|
||||
value: str | None = None
|
||||
) -> None:
|
||||
"""
|
||||
向数据库内添加设置项目。
|
||||
@@ -55,7 +56,7 @@ class Setting(TableBase, table=True):
|
||||
:param name: 设置项名称
|
||||
:type name: str
|
||||
:param value: 设置值,默认为 None
|
||||
:type value: Optional[str]
|
||||
:type value: str | None
|
||||
"""
|
||||
from .database import get_session
|
||||
|
||||
@@ -114,7 +115,7 @@ class Setting(TableBase, table=True):
|
||||
async def set(
|
||||
type: SETTINGS_TYPE,
|
||||
name: str,
|
||||
value: Optional[str] = None
|
||||
value: str | None = None
|
||||
) -> None:
|
||||
"""
|
||||
更新指定类型和名称的设置项的值。
|
||||
@@ -124,7 +125,7 @@ class Setting(TableBase, table=True):
|
||||
:param name: 设置项名称
|
||||
:type name: str
|
||||
:param value: 新的设置值,默认为 None
|
||||
:type value: Optional[str]
|
||||
:type value: str | None
|
||||
|
||||
:raises ValueError: 如果设置项不存在,则抛出异常
|
||||
"""
|
||||
|
||||
@@ -10,7 +10,8 @@ if TYPE_CHECKING:
|
||||
from .report import Report
|
||||
|
||||
class Share(TableBase, table=True):
|
||||
__tablename__ = 'shares'
|
||||
"""分享模型"""
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("code", name="uq_share_code"),
|
||||
CheckConstraint("(file_id IS NOT NULL) <> (folder_id IS NOT NULL)", name="ck_share_xor"),
|
||||
@@ -19,22 +20,22 @@ class Share(TableBase, table=True):
|
||||
)
|
||||
|
||||
code: str = Field(max_length=64, nullable=False, index=True, description="分享码")
|
||||
password: Optional[str] = Field(default=None, max_length=255, description="分享密码(加密后)")
|
||||
password: str | None = Field(default=None, max_length=255, description="分享密码(加密后)")
|
||||
|
||||
is_dir: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否为目录分享")
|
||||
file_id: Optional[int] = Field(default=None, foreign_key="files.id", index=True, description="文件ID(二选一)")
|
||||
folder_id: Optional[int] = Field(default=None, foreign_key="folders.id", index=True, description="目录ID(二选一)")
|
||||
file_id: int | None = Field(default=None, foreign_key="file.id", index=True, description="文件ID(二选一)")
|
||||
folder_id: int | None = Field(default=None, foreign_key="folder.id", index=True, description="目录ID(二选一)")
|
||||
|
||||
views: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="浏览次数")
|
||||
downloads: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="下载次数")
|
||||
remain_downloads: Optional[int] = Field(default=None, description="剩余下载次数 (NULL为不限制)")
|
||||
expires: Optional[datetime] = Field(default=None, description="过期时间 (NULL为永不过期)")
|
||||
remain_downloads: int | None = Field(default=None, description="剩余下载次数 (NULL为不限制)")
|
||||
expires: datetime | None = Field(default=None, description="过期时间 (NULL为永不过期)")
|
||||
preview_enabled: bool = Field(default=True, sa_column_kwargs={"server_default": text("true")}, description="是否允许预览")
|
||||
source_name: Optional[str] = Field(default=None, max_length=255, index=True, description="源名称(冗余字段,便于展示)")
|
||||
source_name: str | None = Field(default=None, max_length=255, index=True, description="源名称(冗余字段,便于展示)")
|
||||
score: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="兑换此分享所需的积分")
|
||||
|
||||
# 外键
|
||||
user_id: int = Field(foreign_key="users.id", index=True, description="创建分享的用户ID")
|
||||
user_id: int = Field(foreign_key="user.id", index=True, description="创建分享的用户ID")
|
||||
|
||||
# 关系
|
||||
user: "User" = Relationship(back_populates="shares")
|
||||
|
||||
@@ -8,7 +8,8 @@ if TYPE_CHECKING:
|
||||
from .file import File
|
||||
|
||||
class SourceLink(TableBase, table=True):
|
||||
__tablename__ = 'source_links'
|
||||
"""链接模型"""
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_sourcelink_file_name", "file_id", "name"),
|
||||
)
|
||||
@@ -17,7 +18,7 @@ class SourceLink(TableBase, table=True):
|
||||
downloads: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="通过此链接的下载次数")
|
||||
|
||||
# 外键
|
||||
file_id: int = Field(foreign_key="files.id", index=True, description="关联的文件ID")
|
||||
file_id: int = Field(foreign_key="file.id", index=True, description="关联的文件ID")
|
||||
|
||||
# 关系
|
||||
file: "File" = Relationship(back_populates="source_links")
|
||||
@@ -9,15 +9,15 @@ if TYPE_CHECKING:
|
||||
from .user import User
|
||||
|
||||
class StoragePack(TableBase, table=True):
|
||||
__tablename__ = 'storage_packs'
|
||||
"""容量包模型"""
|
||||
|
||||
name: str = Field(max_length=255, description="容量包名称")
|
||||
active_time: Optional[datetime] = Field(default=None, description="激活时间")
|
||||
expired_time: Optional[datetime] = Field(default=None, index=True, description="过期时间")
|
||||
active_time: datetime | None = Field(default=None, description="激活时间")
|
||||
expired_time: datetime | None = Field(default=None, index=True, description="过期时间")
|
||||
size: int = Field(description="容量包大小(字节)")
|
||||
|
||||
# 外键
|
||||
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")
|
||||
user_id: int = Field(foreign_key="user.id", index=True, description="所属用户ID")
|
||||
|
||||
# 关系
|
||||
user: "User" = Relationship(back_populates="storage_packs")
|
||||
@@ -8,17 +8,18 @@ if TYPE_CHECKING:
|
||||
from .user import User
|
||||
|
||||
class Tag(TableBase, table=True):
|
||||
__tablename__ = 'tags'
|
||||
"""标签模型"""
|
||||
|
||||
__table_args__ = (UniqueConstraint("name", "user_id", name="uq_tag_name_user"),)
|
||||
|
||||
name: str = Field(max_length=255, description="标签名称")
|
||||
icon: Optional[str] = Field(default=None, max_length=255, description="标签图标")
|
||||
color: Optional[str] = Field(default=None, max_length=255, description="标签颜色")
|
||||
icon: str | None = Field(default=None, max_length=255, description="标签图标")
|
||||
color: str | None = Field(default=None, max_length=255, description="标签颜色")
|
||||
type: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="标签类型: 0=手动, 1=自动")
|
||||
expression: Optional[str] = Field(default=None, description="自动标签的匹配表达式")
|
||||
expression: str | None = Field(default=None, description="自动标签的匹配表达式")
|
||||
|
||||
# 外键
|
||||
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")
|
||||
user_id: int = Field(foreign_key="user.id", index=True, description="所属用户ID")
|
||||
|
||||
# 关系
|
||||
user: "User" = Relationship(back_populates="tags")
|
||||
@@ -9,7 +9,8 @@ if TYPE_CHECKING:
|
||||
from .download import Download
|
||||
|
||||
class Task(TableBase, table=True):
|
||||
__tablename__ = 'tasks'
|
||||
"""任务模型"""
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("progress BETWEEN 0 AND 100", name="ck_task_progress_range"),
|
||||
)
|
||||
@@ -17,11 +18,11 @@ class Task(TableBase, table=True):
|
||||
status: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="任务状态: 0=排队中, 1=处理中, 2=完成, 3=错误")
|
||||
type: int = Field(description="任务类型")
|
||||
progress: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="任务进度 (0-100)")
|
||||
error: Optional[str] = Field(default=None, description="错误信息")
|
||||
props: Optional[str] = Field(default=None, description="任务属性 (JSON格式)")
|
||||
error: str | None = Field(default=None, description="错误信息")
|
||||
props: str | None = Field(default=None, description="任务属性 (JSON格式)")
|
||||
|
||||
# 外键
|
||||
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")
|
||||
user_id: int = Field(foreign_key="user.id", index=True, description="所属用户ID")
|
||||
|
||||
# 关系
|
||||
user: "User" = Relationship(back_populates="tasks")
|
||||
|
||||
@@ -15,7 +15,7 @@ from .task import Task
|
||||
from .webdav import WebDAV
|
||||
|
||||
class User(TableBase, table=True):
|
||||
__tablename__ = 'users'
|
||||
"""用户模型"""
|
||||
|
||||
email: str = Field(max_length=100, unique=True, index=True)
|
||||
"""用户邮箱,唯一"""
|
||||
@@ -25,44 +25,56 @@ class User(TableBase, table=True):
|
||||
|
||||
nick: str | None = Field(default=None, max_length=50)
|
||||
"""用户昵称"""
|
||||
|
||||
password: str = Field(max_length=255)
|
||||
"""用户密码(加密后)"""
|
||||
status: bool | None = Field(default=None, sa_column_kwargs={"server_default": "0"})
|
||||
"""用户状态: True=正常, None=未激活, False=封禁"""
|
||||
|
||||
status: bool = Field(default=True, sa_column_kwargs={"server_default": "true"})
|
||||
"""用户状态: True=正常, False=封禁"""
|
||||
|
||||
storage: int = Field(default=0, sa_column_kwargs={"server_default": "0"})
|
||||
"""已用存储空间(字节)"""
|
||||
|
||||
two_factor: str | None = Field(default=None, max_length=255)
|
||||
"""两步验证密钥"""
|
||||
|
||||
avatar: str | None = Field(default=None, max_length=255)
|
||||
"""头像地址"""
|
||||
|
||||
options: str | None = Field(default=None)
|
||||
"""用户个人设置 (JSON格式)"""
|
||||
|
||||
authn: str | None = Field(default=None)
|
||||
"""WebAuthn 凭证"""
|
||||
|
||||
open_id: str | None = Field(default=None, max_length=255, unique=True, index=True)
|
||||
"""第三方登录OpenID"""
|
||||
|
||||
score: int = Field(default=0, sa_column_kwargs={"server_default": "0"})
|
||||
"""用户积分"""
|
||||
|
||||
group_expires: datetime | None = Field(default=None)
|
||||
"""当前用户组过期时间"""
|
||||
|
||||
phone: str | None = Field(default=None, max_length=255, unique=True, index=True)
|
||||
"""手机号"""
|
||||
|
||||
# 外键
|
||||
group_id: int = Field(foreign_key="groups.id", index=True)
|
||||
group_id: int = Field(foreign_key="group.id", index=True)
|
||||
"""所属用户组ID"""
|
||||
previous_group_id: int | None = Field(default=None, foreign_key="groups.id")
|
||||
|
||||
previous_group_id: int | None = Field(default=None, foreign_key="group.id")
|
||||
"""之前的用户组ID(用于过期后恢复)"""
|
||||
|
||||
# 关系
|
||||
group: "Group" = Relationship(
|
||||
back_populates="users",
|
||||
back_populates="user",
|
||||
sa_relationship_kwargs={
|
||||
"foreign_keys": "User.group_id"
|
||||
}
|
||||
)
|
||||
previous_group: Optional["Group"] = Relationship(
|
||||
back_populates="previous_users",
|
||||
back_populates="previous_user",
|
||||
sa_relationship_kwargs={
|
||||
"foreign_keys": "User.previous_group_id"
|
||||
}
|
||||
|
||||
@@ -7,17 +7,18 @@ if TYPE_CHECKING:
|
||||
from .user import User
|
||||
|
||||
class WebDAV(TableBase, table=True):
|
||||
__tablename__ = 'webdavs'
|
||||
"""WebDAV账户模型"""
|
||||
|
||||
__table_args__ = (UniqueConstraint("name", "user_id", name="uq_webdav_name_user"),)
|
||||
|
||||
name: str = Field(max_length=255, description="WebDAV账户名")
|
||||
password: str = Field(max_length=255, description="WebDAV密码(加密后)")
|
||||
password: str = Field(max_length=255, description="WebDAV密码")
|
||||
root: str = Field(default="/", sa_column_kwargs={"server_default": "'/'"}, description="根目录路径")
|
||||
readonly: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否只读")
|
||||
use_proxy: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否使用代理下载")
|
||||
|
||||
# 外键
|
||||
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")
|
||||
user_id: int = Field(foreign_key="user.id", index=True, description="所属用户ID")
|
||||
|
||||
# 关系
|
||||
user: "User" = Relationship(back_populates="webdavs")
|
||||
@@ -18,9 +18,9 @@ async def Login(LoginRequest: LoginRequest) -> TokenModel | bool | None:
|
||||
:param password: 用户密码
|
||||
:type password: str
|
||||
:param captcha: 验证码
|
||||
:type captcha: Optional[str]
|
||||
:type captcha: str | None
|
||||
:param twoFaCode: 两步验证代码
|
||||
:type twoFaCode: Optional[str]
|
||||
:type twoFaCode: str | None
|
||||
|
||||
:return: TokenModel 对象或状态码或 None
|
||||
:rtype: TokenModel | int | None
|
||||
|
||||
Reference in New Issue
Block a user