数据库时间日志迁移至 BaseModel

This commit is contained in:
2025-07-18 00:50:19 +08:00
parent 6f83022a79
commit 4148e362b9
21 changed files with 32 additions and 474 deletions

View File

@@ -1,7 +1,27 @@
from typing import Optional from typing import Optional
from sqlmodel import SQLModel, Field from sqlmodel import SQLModel, Field
from sqlalchemy import DateTime
from datetime import datetime, timezone
from sqlalchemy.ext.asyncio import AsyncAttrs
class BaseModel(SQLModel): utcnow = lambda: datetime.now(tz=timezone.utc)
class BaseModel(SQLModel, AsyncAttrs):
__abstract__ = True __abstract__ = True
id: Optional[int] = Field(default=None, primary_key=True, description="主键ID") id: Optional[int] = Field(default=None, primary_key=True, description="主键ID")
created_at: datetime = Field(
default_factory=utcnow,
description="创建时间",
)
updated_at: datetime = Field(
sa_type=DateTime,
description="更新时间",
sa_column_kwargs={"default": utcnow, "onupdate": utcnow},
default_factory=utcnow
)
deleted_at: Optional[datetime] = Field(
default=None,
description="删除时间",
sa_column={"nullable": True}
)

View File

@@ -24,35 +24,6 @@ class Download(BaseModel, table=True):
attrs: Optional[str] = Field(default=None, description="额外属性 (JSON格式)") attrs: Optional[str] = Field(default=None, description="额外属性 (JSON格式)")
error: Optional[str] = Field(default=None, description="错误信息") error: Optional[str] = Field(default=None, description="错误信息")
dst: str = Field(description="目标存储路径") dst: str = Field(description="目标存储路径")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 外键 # 外键
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID") user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")

View File

@@ -20,35 +20,6 @@ class File(BaseModel, table=True):
pic_info: Optional[str] = Field(default=None, max_length=255, description="图片信息(如尺寸)") pic_info: Optional[str] = Field(default=None, max_length=255, description="图片信息(如尺寸)")
upload_session_id: Optional[str] = Field(default=None, max_length=255, unique=True, index=True, description="分块上传会话ID") 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格式)") file_metadata: Optional[str] = Field(default=None, description="文件元数据 (JSON格式)")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 外键 # 外键
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID") user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")

View File

@@ -15,26 +15,6 @@ class Folder(BaseModel, table=True):
__table_args__ = (UniqueConstraint("name", "parent_id", name="uq_folder_name_parent"),) __table_args__ = (UniqueConstraint("name", "parent_id", name="uq_folder_name_parent"),)
name: str = Field(max_length=255, description="目录名") name: str = Field(max_length=255, description="目录名")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
# 外键 # 外键
parent_id: Optional[int] = Field(default=None, foreign_key="folders.id", index=True, description="父目录ID") parent_id: Optional[int] = Field(default=None, foreign_key="folders.id", index=True, description="父目录ID")

View File

@@ -19,26 +19,6 @@ class Group(BaseModel, table=True):
web_dav_enabled: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否允许使用WebDAV") web_dav_enabled: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否允许使用WebDAV")
speed_limit: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="速度限制 (KB/s), 0为不限制") speed_limit: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="速度限制 (KB/s), 0为不限制")
options: Optional[str] = Field(default=None, description="其他选项 (JSON格式)") options: Optional[str] = Field(default=None, description="其他选项 (JSON格式)")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
# 关系:一个组可以有多个用户 # 关系:一个组可以有多个用户
users: List["User"] = Relationship( users: List["User"] = Relationship(

View File

@@ -20,35 +20,6 @@ class Node(BaseModel, table=True):
aria2_enabled: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否启用Aria2") 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: Optional[str] = Field(default=None, description="Aria2配置 (JSON格式)")
rank: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="节点排序权重") rank: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="节点排序权重")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 关系 # 关系
downloads: list["Download"] = Relationship(back_populates="node") downloads: list["Download"] = Relationship(back_populates="node")

View File

@@ -19,35 +19,6 @@ class Order(BaseModel, table=True):
name: str = Field(max_length=255, description="商品名称") name: str = Field(max_length=255, description="商品名称")
price: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, 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=已取消") status: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="订单状态: 0=待支付, 1=已完成, 2=已取消")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 外键 # 外键
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID") user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")

View File

@@ -26,35 +26,6 @@ class Policy(BaseModel, table=True):
file_name_rule: Optional[str] = Field(default=None, max_length=255, description="文件命名规则") file_name_rule: Optional[str] = Field(default=None, max_length=255, description="文件命名规则")
is_origin_link_enable: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, 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: Optional[str] = Field(default=None, description="其他选项 (JSON格式)")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 关系 # 关系
files: List["File"] = Relationship(back_populates="policy") files: List["File"] = Relationship(back_populates="policy")

View File

@@ -12,24 +12,4 @@ class Redeem(BaseModel, table=True):
product_id: Optional[int] = Field(default=None, description="关联的商品/权益ID") product_id: Optional[int] = Field(default=None, description="关联的商品/权益ID")
num: int = Field(default=1, sa_column_kwargs={"server_default": "1"}, description="可兑换数量/时长等") num: int = Field(default=1, sa_column_kwargs={"server_default": "1"}, description="可兑换数量/时长等")
code: str = Field(unique=True, index=True, description="兑换码,唯一") code: str = Field(unique=True, index=True, description="兑换码,唯一")
used: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否已使用") used: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否已使用")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)

View File

@@ -13,35 +13,6 @@ class Report(BaseModel, table=True):
reason: int = Field(description="举报原因代码") reason: int = Field(description="举报原因代码")
description: Optional[str] = Field(default=None, max_length=255, description="补充描述") description: Optional[str] = Field(default=None, max_length=255, description="补充描述")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 外键 # 外键
share_id: int = Field(foreign_key="shares.id", index=True, description="被举报的分享ID") share_id: int = Field(foreign_key="shares.id", index=True, description="被举报的分享ID")

View File

@@ -41,35 +41,6 @@ class Setting(BaseModel, table=True):
type: str = Field(max_length=255, description="设置类型/分组") type: str = Field(max_length=255, description="设置类型/分组")
name: str = Field(max_length=255, description="设置项名称") name: str = Field(max_length=255, description="设置项名称")
value: Optional[str] = Field(default=None, description="设置值") value: Optional[str] = Field(default=None, description="设置值")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
@staticmethod @staticmethod
async def add( async def add(

View File

@@ -23,35 +23,6 @@ class Share(BaseModel, table=True):
preview_enabled: bool = Field(default=True, sa_column_kwargs={"server_default": text("true")}, description="是否允许预览") 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: Optional[str] = Field(default=None, max_length=255, index=True, description="源名称(冗余字段,便于展示)")
score: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="兑换此分享所需的积分") score: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="兑换此分享所需的积分")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 外键 # 外键
user_id: int = Field(foreign_key="users.id", index=True, description="创建分享的用户ID") user_id: int = Field(foreign_key="users.id", index=True, description="创建分享的用户ID")

View File

@@ -13,35 +13,6 @@ class SourceLink(BaseModel, table=True):
name: str = Field(max_length=255, description="链接名称") name: str = Field(max_length=255, description="链接名称")
downloads: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="通过此链接的下载次数") downloads: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="通过此链接的下载次数")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 外键 # 外键
file_id: int = Field(foreign_key="files.id", index=True, description="关联的文件ID") file_id: int = Field(foreign_key="files.id", index=True, description="关联的文件ID")

View File

@@ -16,35 +16,7 @@ class StoragePack(BaseModel, table=True):
active_time: Optional[datetime] = Field(default=None, description="激活时间") active_time: Optional[datetime] = Field(default=None, description="激活时间")
expired_time: Optional[datetime] = Field(default=None, index=True, description="过期时间") expired_time: Optional[datetime] = Field(default=None, index=True, description="过期时间")
size: int = Field(description="容量包大小(字节)") size: int = Field(description="容量包大小(字节)")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 外键 # 外键
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID") user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")

View File

@@ -17,35 +17,6 @@ class Tag(BaseModel, table=True):
color: Optional[str] = Field(default=None, max_length=255, description="标签颜色") color: Optional[str] = Field(default=None, max_length=255, description="标签颜色")
type: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="标签类型: 0=手动, 1=自动") type: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="标签类型: 0=手动, 1=自动")
expression: Optional[str] = Field(default=None, description="自动标签的匹配表达式") expression: Optional[str] = Field(default=None, description="自动标签的匹配表达式")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 外键 # 外键
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID") user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")

View File

@@ -17,35 +17,6 @@ class Task(BaseModel, table=True):
progress: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="任务进度 (0-100)") progress: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="任务进度 (0-100)")
error: Optional[str] = Field(default=None, description="错误信息") error: Optional[str] = Field(default=None, description="错误信息")
props: Optional[str] = Field(default=None, description="任务属性 (JSON格式)") props: Optional[str] = Field(default=None, description="任务属性 (JSON格式)")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 外键 # 外键
user_id: "int" = Field(foreign_key="users.id", index=True, description="所属用户ID") user_id: "int" = Field(foreign_key="users.id", index=True, description="所属用户ID")

View File

@@ -36,35 +36,6 @@ class User(BaseModel, table=True):
score: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="用户积分") score: int = Field(default=0, sa_column_kwargs={"server_default": "0"}, description="用户积分")
group_expires: Optional[datetime] = Field(default=None, description="当前用户组过期时间") group_expires: Optional[datetime] = Field(default=None, description="当前用户组过期时间")
phone: Optional[str] = Field(default=None, max_length=255, unique=True, index=True, description="手机号") phone: Optional[str] = Field(default=None, max_length=255, unique=True, index=True, description="手机号")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 外键 # 外键
group_id: int = Field(foreign_key="groups.id", index=True, description="所属用户组ID") group_id: int = Field(foreign_key="groups.id", index=True, description="所属用户组ID")

View File

@@ -1,9 +1,8 @@
# my_project/models/webdav.py # my_project/models/webdav.py
from typing import Optional, TYPE_CHECKING from typing import TYPE_CHECKING
from sqlmodel import Field, Relationship, UniqueConstraint, text, Column, func, DateTime from sqlmodel import Field, Relationship, UniqueConstraint, text, Column, func, DateTime
from .base import BaseModel from .base import BaseModel
from datetime import datetime
if TYPE_CHECKING: if TYPE_CHECKING:
from .user import User from .user import User
@@ -17,35 +16,6 @@ class WebDAV(BaseModel, table=True):
root: str = Field(default="/", sa_column_kwargs={"server_default": "'/'"}, description="根目录路径") root: str = Field(default="/", sa_column_kwargs={"server_default": "'/'"}, description="根目录路径")
readonly: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, 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="是否使用代理下载") use_proxy: bool = Field(default=False, sa_column_kwargs={"server_default": text("false")}, description="是否使用代理下载")
created_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
comment="创建时间",
),
)
updated_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=False,
server_default=func.now(),
onupdate=func.now(),
comment="更新时间",
),
)
delete_at: Optional[datetime] = Field(
default=None,
sa_column=Column(
DateTime,
nullable=True,
comment="删除时间",
),
)
# 外键 # 外键
user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID") user_id: int = Field(foreign_key="users.id", index=True, description="所属用户ID")

View File

@@ -18,11 +18,11 @@ async def db_session():
yield session yield session
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_initialize_db(): async def test_migration():
"""测试数据库创建并初始化配置""" """测试数据库创建并初始化配置"""
from models import migration from models import migration
from models import database from models import database
await database.init_db(url='sqlite:///:memory:') await database.init_db(url='sqlite:///:memory:')
await migration.init_default_settings() await migration.migration()

View File

@@ -3,11 +3,13 @@ import pytest
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_group_curd(): async def test_group_curd():
"""测试数据库的增删改查""" """测试数据库的增删改查"""
from models import database from models import database, migration
from models.group import Group from models.group import Group
await database.init_db(url='sqlite:///:memory:') await database.init_db(url='sqlite:///:memory:')
await migration.migration()
# 测试增 Create # 测试增 Create
test_group = Group(name='test_group') test_group = Group(name='test_group')
created_group = await Group.create(test_group) created_group = await Group.create(test_group)

View File

@@ -3,12 +3,14 @@ import pytest
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_user_curd(): async def test_user_curd():
"""测试数据库的增删改查""" """测试数据库的增删改查"""
from models import database from models import database, migration
from models.group import Group from models.group import Group
from models.user import User from models.user import User
await database.init_db(url='sqlite:///:memory:') await database.init_db(url='sqlite:///:memory:')
await migration.migration()
# 新建一个测试用户组 # 新建一个测试用户组
test_user_group = Group(name='test_user_group') test_user_group = Group(name='test_user_group')
created_group = await Group.create(test_user_group) created_group = await Group.create(test_user_group)