feat: migrate ORM base to sqlmodel-ext, add file viewers and WOPI integration
All checks were successful
Test / test (push) Successful in 1m45s
All checks were successful
Test / test (push) Successful in 1m45s
- 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:
@@ -17,12 +17,14 @@ from sqlmodels.color import ThemeColorsBase
|
||||
from sqlmodels.user_authn import UserAuthn
|
||||
from utils import JWT, Password, http_exceptions
|
||||
from utils.password.pwd import PasswordStatus, TwoFactorResponse, TwoFactorVerifyRequest
|
||||
from .file_viewers import file_viewers_router
|
||||
|
||||
user_settings_router = APIRouter(
|
||||
prefix='/settings',
|
||||
tags=["user", "user_settings"],
|
||||
dependencies=[Depends(auth_required)],
|
||||
)
|
||||
user_settings_router.include_router(file_viewers_router)
|
||||
|
||||
|
||||
@user_settings_router.get(
|
||||
|
||||
150
routers/api/v1/user/settings/file_viewers/__init__.py
Normal file
150
routers/api/v1/user/settings/file_viewers/__init__.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
用户文件查看器偏好设置端点
|
||||
|
||||
提供用户"始终使用"默认查看器的增删查功能。
|
||||
"""
|
||||
from typing import Annotated
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from sqlalchemy import and_
|
||||
|
||||
from middleware.auth import auth_required
|
||||
from middleware.dependencies import SessionDep
|
||||
from sqlmodels import (
|
||||
FileApp,
|
||||
FileAppExtension,
|
||||
SetDefaultViewerRequest,
|
||||
User,
|
||||
UserFileAppDefault,
|
||||
UserFileAppDefaultResponse,
|
||||
)
|
||||
from utils import http_exceptions
|
||||
|
||||
file_viewers_router = APIRouter(
|
||||
prefix='/file-viewers',
|
||||
tags=["user", "user_settings", "file_viewers"],
|
||||
dependencies=[Depends(auth_required)],
|
||||
)
|
||||
|
||||
|
||||
@file_viewers_router.put(
|
||||
path='/default',
|
||||
summary='设置默认查看器',
|
||||
description='为指定扩展名设置"始终使用"的查看器。',
|
||||
)
|
||||
async def set_default_viewer(
|
||||
session: SessionDep,
|
||||
user: Annotated[User, Depends(auth_required)],
|
||||
request: SetDefaultViewerRequest,
|
||||
) -> UserFileAppDefaultResponse:
|
||||
"""
|
||||
设置默认查看器端点
|
||||
|
||||
如果用户已有该扩展名的默认设置,则更新;否则创建新记录。
|
||||
|
||||
认证:JWT token 必填
|
||||
|
||||
错误处理:
|
||||
- 404: 应用不存在
|
||||
- 400: 应用不支持该扩展名
|
||||
"""
|
||||
# 规范化扩展名
|
||||
normalized_ext = request.extension.lower().strip().lstrip('.')
|
||||
|
||||
# 验证应用存在
|
||||
app: FileApp | None = await FileApp.get(session, FileApp.id == request.app_id)
|
||||
if not app:
|
||||
http_exceptions.raise_not_found("应用不存在")
|
||||
|
||||
# 验证应用支持该扩展名
|
||||
ext_record: FileAppExtension | None = await FileAppExtension.get(
|
||||
session,
|
||||
and_(
|
||||
FileAppExtension.app_id == app.id,
|
||||
FileAppExtension.extension == normalized_ext,
|
||||
),
|
||||
)
|
||||
if not ext_record:
|
||||
http_exceptions.raise_bad_request("该应用不支持此扩展名")
|
||||
|
||||
# 查找已有记录
|
||||
existing: UserFileAppDefault | None = await UserFileAppDefault.get(
|
||||
session,
|
||||
and_(
|
||||
UserFileAppDefault.user_id == user.id,
|
||||
UserFileAppDefault.extension == normalized_ext,
|
||||
),
|
||||
)
|
||||
|
||||
if existing:
|
||||
existing.app_id = request.app_id
|
||||
existing = await existing.save(session)
|
||||
# 重新加载 app 关系
|
||||
await session.refresh(existing, attribute_names=["app"])
|
||||
return existing.to_response()
|
||||
else:
|
||||
new_default = UserFileAppDefault(
|
||||
user_id=user.id,
|
||||
extension=normalized_ext,
|
||||
app_id=request.app_id,
|
||||
)
|
||||
new_default = await new_default.save(session)
|
||||
# 重新加载 app 关系
|
||||
await session.refresh(new_default, attribute_names=["app"])
|
||||
return new_default.to_response()
|
||||
|
||||
|
||||
@file_viewers_router.get(
|
||||
path='/defaults',
|
||||
summary='列出所有默认查看器设置',
|
||||
description='获取当前用户所有"始终使用"的查看器偏好。',
|
||||
)
|
||||
async def list_default_viewers(
|
||||
session: SessionDep,
|
||||
user: Annotated[User, Depends(auth_required)],
|
||||
) -> list[UserFileAppDefaultResponse]:
|
||||
"""
|
||||
列出所有默认查看器设置端点
|
||||
|
||||
认证:JWT token 必填
|
||||
"""
|
||||
defaults: list[UserFileAppDefault] = await UserFileAppDefault.get(
|
||||
session,
|
||||
UserFileAppDefault.user_id == user.id,
|
||||
fetch_mode="all",
|
||||
load=UserFileAppDefault.app,
|
||||
)
|
||||
return [d.to_response() for d in defaults]
|
||||
|
||||
|
||||
@file_viewers_router.delete(
|
||||
path='/default/{default_id}',
|
||||
summary='撤销默认查看器设置',
|
||||
description='删除指定的"始终使用"偏好。',
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
async def delete_default_viewer(
|
||||
session: SessionDep,
|
||||
user: Annotated[User, Depends(auth_required)],
|
||||
default_id: UUID,
|
||||
) -> None:
|
||||
"""
|
||||
撤销默认查看器设置端点
|
||||
|
||||
认证:JWT token 必填
|
||||
|
||||
错误处理:
|
||||
- 404: 记录不存在或不属于当前用户
|
||||
"""
|
||||
existing: UserFileAppDefault | None = await UserFileAppDefault.get(
|
||||
session,
|
||||
and_(
|
||||
UserFileAppDefault.id == default_id,
|
||||
UserFileAppDefault.user_id == user.id,
|
||||
),
|
||||
)
|
||||
if not existing:
|
||||
http_exceptions.raise_not_found("默认设置不存在")
|
||||
|
||||
await UserFileAppDefault.delete(session, existing)
|
||||
Reference in New Issue
Block a user