feat: migrate ORM base to sqlmodel-ext, add file viewers and WOPI integration

- Migrate SQLModel base classes, mixins, and database management to
  external sqlmodel-ext package; remove sqlmodels/base/, sqlmodels/mixin/,
  and sqlmodels/database.py
- Add file viewer/editor system with WOPI protocol support for
  collaborative editing (OnlyOffice, Collabora)
- Add enterprise edition license verification module (ee/)
- Add Dockerfile multi-stage build with Cython compilation support
- Add new dependencies: sqlmodel-ext, cryptography, whatthepatch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 14:23:17 +08:00
parent 53b757de7a
commit eac0766e79
74 changed files with 4819 additions and 4837 deletions

View File

@@ -0,0 +1,106 @@
"""
文件查看器查询端点
提供按文件扩展名查询可用查看器的功能,包含用户组访问控制过滤。
"""
from typing import Annotated
from uuid import UUID
from fastapi import APIRouter, Depends, Query
from sqlalchemy import and_
from sqlalchemy import select
from middleware.auth import auth_required
from middleware.dependencies import SessionDep
from sqlmodels import (
FileApp,
FileAppExtension,
FileAppGroupLink,
FileAppSummary,
FileViewersResponse,
User,
UserFileAppDefault,
)
viewers_router = APIRouter(prefix="/viewers", tags=["file", "viewers"])
@viewers_router.get(
path='',
summary='查询可用文件查看器',
description='根据文件扩展名查询可用的查看器应用列表。',
)
async def get_viewers(
session: SessionDep,
user: Annotated[User, Depends(auth_required)],
ext: Annotated[str, Query(max_length=20, description="文件扩展名")],
) -> FileViewersResponse:
"""
查询可用文件查看器端点
流程:
1. 规范化扩展名(小写,去点号)
2. 查询匹配的已启用应用
3. 按用户组权限过滤
4. 按 priority 排序
5. 查询用户默认偏好
认证JWT token 必填
错误处理:
- 401: 未授权
"""
# 规范化扩展名
normalized_ext = ext.lower().strip().lstrip('.')
# 查询匹配扩展名的应用(已启用的)
ext_records: list[FileAppExtension] = await FileAppExtension.get(
session,
and_(
FileAppExtension.extension == normalized_ext,
),
fetch_mode="all",
load=FileAppExtension.app,
)
# 过滤和收集可用应用
user_group_id = user.group_id
viewers: list[tuple[FileAppSummary, int]] = []
for ext_record in ext_records:
app: FileApp = ext_record.app
if not app.is_enabled:
continue
if app.is_restricted:
# 检查用户组权限FileAppGroupLink 是纯关联表,使用 session 查询)
stmt = select(FileAppGroupLink).where(
and_(
FileAppGroupLink.app_id == app.id,
FileAppGroupLink.group_id == user_group_id,
)
)
result = await session.exec(stmt)
group_link = result.first()
if not group_link:
continue
viewers.append((app.to_summary(), ext_record.priority))
# 按 priority 排序
viewers.sort(key=lambda x: x[1])
# 查询用户默认偏好
user_default: UserFileAppDefault | None = await UserFileAppDefault.get(
session,
and_(
UserFileAppDefault.user_id == user.id,
UserFileAppDefault.extension == normalized_ext,
),
)
return FileViewersResponse(
viewers=[v[0] for v in viewers],
default_viewer_id=user_default.app_id if user_default else None,
)