feat: add models for physical files, policies, and user management

- Implement PhysicalFile model to manage physical file references and reference counting.
- Create Policy model with associated options and group links for storage policies.
- Introduce Redeem and Report models for handling redeem codes and reports.
- Add Settings model for site configuration and user settings management.
- Develop Share model for sharing objects with unique codes and associated metadata.
- Implement SourceLink model for managing download links associated with objects.
- Create StoragePack model for managing user storage packages.
- Add Tag model for user-defined tags with manual and automatic types.
- Implement Task model for managing background tasks with status tracking.
- Develop User model with comprehensive user management features including authentication.
- Introduce UserAuthn model for managing WebAuthn credentials.
- Create WebDAV model for managing WebDAV accounts associated with users.
This commit is contained in:
2026-02-10 16:25:49 +08:00
parent 62c671e07b
commit 209cb24ab4
92 changed files with 3640 additions and 1444 deletions

View File

@@ -5,21 +5,21 @@ import pytest
from sqlalchemy.exc import IntegrityError
from sqlmodel.ext.asyncio.session import AsyncSession
from models.object import Object, ObjectType
from models.user import User
from models.group import Group
from sqlmodels.object import Object, ObjectType
from sqlmodels.user import User
from sqlmodels.group import Group
@pytest.mark.asyncio
async def test_object_create_folder(db_session: AsyncSession):
"""测试创建目录"""
# 创建必要的依赖数据
from models.policy import Policy, PolicyType
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(username="testuser", password="password", group_id=group.id)
user = User(email="testuser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(
@@ -48,12 +48,12 @@ async def test_object_create_folder(db_session: AsyncSession):
@pytest.mark.asyncio
async def test_object_create_file(db_session: AsyncSession):
"""测试创建文件"""
from models.policy import Policy, PolicyType
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(username="testuser", password="password", group_id=group.id)
user = User(email="testuser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(
@@ -65,7 +65,7 @@ async def test_object_create_file(db_session: AsyncSession):
# 创建根目录
root = Object(
name=user.username,
name="/",
type=ObjectType.FOLDER,
parent_id=None,
owner_id=user.id,
@@ -81,7 +81,6 @@ async def test_object_create_file(db_session: AsyncSession):
owner_id=user.id,
policy_id=policy.id,
size=1024,
source_name="test_source.txt"
)
file = await file.save(db_session)
@@ -89,18 +88,17 @@ async def test_object_create_file(db_session: AsyncSession):
assert file.name == "test.txt"
assert file.type == ObjectType.FILE
assert file.size == 1024
assert file.source_name == "test_source.txt"
@pytest.mark.asyncio
async def test_object_is_file_property(db_session: AsyncSession):
"""测试 is_file 属性"""
from models.policy import Policy, PolicyType
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(username="testuser", password="password", group_id=group.id)
user = User(email="testuser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(name="本地策略", type=PolicyType.LOCAL, server="/tmp/test")
@@ -122,12 +120,12 @@ async def test_object_is_file_property(db_session: AsyncSession):
@pytest.mark.asyncio
async def test_object_is_folder_property(db_session: AsyncSession):
"""测试 is_folder 属性"""
from models.policy import Policy, PolicyType
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(username="testuser", password="password", group_id=group.id)
user = User(email="testuser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(name="本地策略", type=PolicyType.LOCAL, server="/tmp/test")
@@ -148,12 +146,12 @@ async def test_object_is_folder_property(db_session: AsyncSession):
@pytest.mark.asyncio
async def test_object_get_root(db_session: AsyncSession):
"""测试 get_root() 方法"""
from models.policy import Policy, PolicyType
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(username="rootuser", password="password", group_id=group.id)
user = User(email="rootuser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(name="本地策略", type=PolicyType.LOCAL, server="/tmp/test")
@@ -161,7 +159,7 @@ async def test_object_get_root(db_session: AsyncSession):
# 创建根目录
root = Object(
name=user.username,
name="/",
type=ObjectType.FOLDER,
parent_id=None,
owner_id=user.id,
@@ -180,12 +178,12 @@ async def test_object_get_root(db_session: AsyncSession):
@pytest.mark.asyncio
async def test_object_get_by_path_root(db_session: AsyncSession):
"""测试获取根目录"""
from models.policy import Policy, PolicyType
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(username="pathuser", password="password", group_id=group.id)
user = User(email="pathuser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(name="本地策略", type=PolicyType.LOCAL, server="/tmp/test")
@@ -193,7 +191,7 @@ async def test_object_get_by_path_root(db_session: AsyncSession):
# 创建根目录
root = Object(
name=user.username,
name="/",
type=ObjectType.FOLDER,
parent_id=None,
owner_id=user.id,
@@ -202,7 +200,7 @@ async def test_object_get_by_path_root(db_session: AsyncSession):
root = await root.save(db_session)
# 通过路径获取根目录
result = await Object.get_by_path(db_session, user.id, "/pathuser", user.username)
result = await Object.get_by_path(db_session, user.id, "/")
assert result is not None
assert result.id == root.id
@@ -211,12 +209,12 @@ async def test_object_get_by_path_root(db_session: AsyncSession):
@pytest.mark.asyncio
async def test_object_get_by_path_nested(db_session: AsyncSession):
"""测试获取嵌套路径"""
from models.policy import Policy, PolicyType
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(username="nesteduser", password="password", group_id=group.id)
user = User(email="nesteduser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(name="本地策略", type=PolicyType.LOCAL, server="/tmp/test")
@@ -224,7 +222,7 @@ async def test_object_get_by_path_nested(db_session: AsyncSession):
# 创建目录结构: root -> docs -> work -> project
root = Object(
name=user.username,
name="/",
type=ObjectType.FOLDER,
parent_id=None,
owner_id=user.id,
@@ -263,8 +261,7 @@ async def test_object_get_by_path_nested(db_session: AsyncSession):
result = await Object.get_by_path(
db_session,
user.id,
"/nesteduser/docs/work/project",
user.username
"/docs/work/project",
)
assert result is not None
@@ -275,12 +272,12 @@ async def test_object_get_by_path_nested(db_session: AsyncSession):
@pytest.mark.asyncio
async def test_object_get_by_path_not_found(db_session: AsyncSession):
"""测试路径不存在"""
from models.policy import Policy, PolicyType
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(username="notfounduser", password="password", group_id=group.id)
user = User(email="notfounduser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(name="本地策略", type=PolicyType.LOCAL, server="/tmp/test")
@@ -288,7 +285,7 @@ async def test_object_get_by_path_not_found(db_session: AsyncSession):
# 创建根目录
root = Object(
name=user.username,
name="/",
type=ObjectType.FOLDER,
parent_id=None,
owner_id=user.id,
@@ -300,8 +297,7 @@ async def test_object_get_by_path_not_found(db_session: AsyncSession):
result = await Object.get_by_path(
db_session,
user.id,
"/notfounduser/nonexistent",
user.username
"/nonexistent",
)
assert result is None
@@ -310,12 +306,12 @@ async def test_object_get_by_path_not_found(db_session: AsyncSession):
@pytest.mark.asyncio
async def test_object_get_children(db_session: AsyncSession):
"""测试 get_children() 方法"""
from models.policy import Policy, PolicyType
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(username="childrenuser", password="password", group_id=group.id)
user = User(email="childrenuser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(name="本地策略", type=PolicyType.LOCAL, server="/tmp/test")
@@ -362,12 +358,12 @@ async def test_object_get_children(db_session: AsyncSession):
@pytest.mark.asyncio
async def test_object_parent_child_relationship(db_session: AsyncSession):
"""测试父子关系"""
from models.policy import Policy, PolicyType
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(username="reluser", password="password", group_id=group.id)
user = User(email="reluser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(name="本地策略", type=PolicyType.LOCAL, server="/tmp/test")
@@ -407,12 +403,12 @@ async def test_object_parent_child_relationship(db_session: AsyncSession):
@pytest.mark.asyncio
async def test_object_unique_constraint(db_session: AsyncSession):
"""测试同目录名称唯一约束"""
from models.policy import Policy, PolicyType
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(username="uniqueuser", password="password", group_id=group.id)
user = User(email="uniqueuser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(name="本地策略", type=PolicyType.LOCAL, server="/tmp/test")
@@ -450,3 +446,64 @@ async def test_object_unique_constraint(db_session: AsyncSession):
with pytest.raises(IntegrityError):
await file2.save(db_session)
@pytest.mark.asyncio
async def test_object_get_full_path(db_session: AsyncSession):
"""测试 get_full_path() 方法"""
from sqlmodels.policy import Policy, PolicyType
group = Group(name="测试组")
group = await group.save(db_session)
user = User(email="pathuser", password="password", group_id=group.id)
user = await user.save(db_session)
policy = Policy(name="本地策略", type=PolicyType.LOCAL, server="/tmp/test")
policy = await policy.save(db_session)
# 创建目录结构: root -> docs -> images -> photo.jpg
root = Object(
name="/",
type=ObjectType.FOLDER,
parent_id=None,
owner_id=user.id,
policy_id=policy.id
)
root = await root.save(db_session)
docs = Object(
name="docs",
type=ObjectType.FOLDER,
parent_id=root.id,
owner_id=user.id,
policy_id=policy.id
)
docs = await docs.save(db_session)
images = Object(
name="images",
type=ObjectType.FOLDER,
parent_id=docs.id,
owner_id=user.id,
policy_id=policy.id
)
images = await images.save(db_session)
photo = Object(
name="photo.jpg",
type=ObjectType.FILE,
parent_id=images.id,
owner_id=user.id,
policy_id=policy.id,
size=2048
)
photo = await photo.save(db_session)
# 测试完整路径
full_path = await photo.get_full_path(db_session)
assert full_path == "/docs/images/photo.jpg"
# 测试根目录的 full_path
root_path = await root.get_full_path(db_session)
assert root_path == "/"