refactor(i-g): BUG-018 God-Object Split 2 — dms/routes.py von 1492 auf 650 Zeilen (-56%)
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
- common.py neu: alle Safety-/Storage-Helper und Konstanten (exakte Original-Implementierung) - folders_routes.py / sharing_routes.py / search_bulk_routes.py je eigener Router ohne Prefix - routes.py: File-Lifecycle-Kern bleibt physisch (MAX_FILE_SIZE-Test-Patch-Semantik), Rest als Re-Export-Fassade + include_router x3 - Beweis: DMS-Suite 129 Tests = 125 passed + 4 identische Vorbestand-Failures (Baseline-Referenz 1:1), 20/20 Routen via Router-Introspection, ruff clean
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
"""DMS Edit-Session/Collabora & Sharing Routen — extrahiert aus routes.py (BUG-018)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
Body,
|
||||
Depends,
|
||||
HTTPException,
|
||||
Response,
|
||||
status,
|
||||
)
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.db import get_db
|
||||
from app.core.visibility import check_single_entity_access
|
||||
from app.deps import get_current_user, require_permission
|
||||
from app.plugins.builtins.dms.common import (
|
||||
OFFICE_EXTENSIONS,
|
||||
_get_file_extension,
|
||||
_parse_uuid,
|
||||
)
|
||||
from app.plugins.builtins.dms.models import File as DmsFile
|
||||
from app.plugins.builtins.dms.schemas import ShareRemoveRequest, ShareRequest
|
||||
from app.plugins.builtins.permissions.contracts import get_contract as get_perms_contract
|
||||
|
||||
_perms_contract = get_perms_contract()
|
||||
Permission = _perms_contract.Permission
|
||||
|
||||
router = APIRouter(tags=["dms"])
|
||||
|
||||
@router.post("/files/{file_id}/edit-session", dependencies=[Depends(require_permission("dms:write"))])
|
||||
async def create_edit_session(
|
||||
file_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""AC11: POST /api/v1/dms/files/{id}/edit-session → 200 + Collabora config."""
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
user_id = current_user["user_id"]
|
||||
user_name = current_user.get("name", "Unknown")
|
||||
is_system_admin = current_user.get("role") == "admin"
|
||||
fid = _parse_uuid(file_id, "file_id")
|
||||
|
||||
result = await db.execute(
|
||||
select(DmsFile).where(
|
||||
DmsFile.id == fid,
|
||||
DmsFile.tenant_id == tenant_id,
|
||||
DmsFile.deleted_at.is_(None),
|
||||
)
|
||||
)
|
||||
dms_file = result.scalar_one_or_none()
|
||||
if dms_file is None:
|
||||
raise HTTPException(404, detail={"detail": "File not found", "code": "not_found"})
|
||||
|
||||
if not await check_single_entity_access(db, "dms_file", fid, user_id, tenant_id, "write", is_system_admin):
|
||||
raise HTTPException(403, detail={"detail": "Access denied", "code": "forbidden"})
|
||||
|
||||
ext = _get_file_extension(dms_file.name)
|
||||
if ext not in OFFICE_EXTENSIONS:
|
||||
raise HTTPException(
|
||||
400,
|
||||
detail={
|
||||
"detail": "Only Office files (docx, xlsx, pptx) are supported",
|
||||
"code": "not_office",
|
||||
},
|
||||
)
|
||||
|
||||
file_type = OFFICE_EXTENSIONS[ext]
|
||||
download_url = f"/api/v1/dms/files/{fid}/preview"
|
||||
callback_url = f"/api/v1/dms/files/{fid}/callback"
|
||||
|
||||
config = {
|
||||
"document": {
|
||||
"fileType": file_type,
|
||||
"key": str(uuid.uuid4()),
|
||||
"title": dms_file.name,
|
||||
"url": download_url,
|
||||
},
|
||||
"editorConfig": {
|
||||
"mode": "edit",
|
||||
"callbackUrl": callback_url,
|
||||
"user": {
|
||||
"id": user_id,
|
||||
"name": user_name,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
|
||||
# ─── Internal Sharing ───
|
||||
|
||||
|
||||
@router.post("/files/{file_id}/share", dependencies=[Depends(require_permission("dms:share"))])
|
||||
async def share_file(
|
||||
file_id: str,
|
||||
body: ShareRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""AC12: POST /api/v1/dms/files/{id}/share → 200, internal share created."""
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
user_id = uuid.UUID(current_user["user_id"])
|
||||
is_system_admin = current_user.get("role") == "admin"
|
||||
fid = _parse_uuid(file_id, "file_id")
|
||||
|
||||
# Verify file exists
|
||||
file_result = await db.execute(
|
||||
select(DmsFile).where(
|
||||
DmsFile.id == fid,
|
||||
DmsFile.tenant_id == tenant_id,
|
||||
DmsFile.deleted_at.is_(None),
|
||||
)
|
||||
)
|
||||
if file_result.scalar_one_or_none() is None:
|
||||
raise HTTPException(404, detail={"detail": "File not found", "code": "not_found"})
|
||||
|
||||
if not await check_single_entity_access(db, "dms_file", fid, user_id, tenant_id, "share", is_system_admin):
|
||||
raise HTTPException(403, detail={"detail": "Access denied", "code": "forbidden"})
|
||||
|
||||
created_perms: list[dict] = []
|
||||
|
||||
for uid_str in body.user_ids:
|
||||
uid = _parse_uuid(uid_str, "user_id")
|
||||
# Check if already exists
|
||||
existing = await db.execute(
|
||||
select(Permission).where(
|
||||
Permission.tenant_id == tenant_id,
|
||||
Permission.file_id == fid,
|
||||
Permission.user_id == uid,
|
||||
Permission.access_level == body.access_level,
|
||||
)
|
||||
)
|
||||
if existing.scalar_one_or_none() is None:
|
||||
perm = Permission(
|
||||
tenant_id=tenant_id,
|
||||
file_id=fid,
|
||||
user_id=uid,
|
||||
group_id=None,
|
||||
access_level=body.access_level,
|
||||
)
|
||||
db.add(perm)
|
||||
await db.flush()
|
||||
created_perms.append(
|
||||
{
|
||||
"id": str(perm.id),
|
||||
"file_id": str(fid),
|
||||
"user_id": str(uid),
|
||||
"group_id": None,
|
||||
"access_level": body.access_level,
|
||||
}
|
||||
)
|
||||
|
||||
for gid_str in body.group_ids:
|
||||
gid = _parse_uuid(gid_str, "group_id")
|
||||
existing = await db.execute(
|
||||
select(Permission).where(
|
||||
Permission.tenant_id == tenant_id,
|
||||
Permission.file_id == fid,
|
||||
Permission.group_id == gid,
|
||||
Permission.access_level == body.access_level,
|
||||
)
|
||||
)
|
||||
if existing.scalar_one_or_none() is None:
|
||||
perm = Permission(
|
||||
tenant_id=tenant_id,
|
||||
file_id=fid,
|
||||
user_id=uuid.UUID(current_user["user_id"]),
|
||||
group_id=gid,
|
||||
access_level=body.access_level,
|
||||
)
|
||||
db.add(perm)
|
||||
await db.flush()
|
||||
created_perms.append(
|
||||
{
|
||||
"id": str(perm.id),
|
||||
"file_id": str(fid),
|
||||
"user_id": str(perm.user_id),
|
||||
"group_id": str(gid),
|
||||
"access_level": body.access_level,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"file_id": str(fid),
|
||||
"shared_with": created_perms,
|
||||
"count": len(created_perms),
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/files/{file_id}/share", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(require_permission("dms:share"))])
|
||||
async def remove_share(
|
||||
file_id: str,
|
||||
body: ShareRemoveRequest = Body(...),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""AC13: DELETE /api/v1/dms/files/{id}/share → 204, share removed."""
|
||||
tenant_id = uuid.UUID(current_user["tenant_id"])
|
||||
user_id = uuid.UUID(current_user["user_id"])
|
||||
is_system_admin = current_user.get("role") == "admin"
|
||||
fid = _parse_uuid(file_id, "file_id")
|
||||
|
||||
if not await check_single_entity_access(db, "dms_file", fid, user_id, tenant_id, "share", is_system_admin):
|
||||
raise HTTPException(403, detail={"detail": "Access denied", "code": "forbidden"})
|
||||
|
||||
if body.user_id:
|
||||
uid = _parse_uuid(body.user_id, "user_id")
|
||||
result = await db.execute(
|
||||
select(Permission).where(
|
||||
Permission.tenant_id == tenant_id,
|
||||
Permission.file_id == fid,
|
||||
Permission.user_id == uid,
|
||||
)
|
||||
)
|
||||
perms = result.scalars().all()
|
||||
for p in perms:
|
||||
await db.delete(p)
|
||||
|
||||
if body.group_id:
|
||||
gid = _parse_uuid(body.group_id, "group_id")
|
||||
result = await db.execute(
|
||||
select(Permission).where(
|
||||
Permission.tenant_id == tenant_id,
|
||||
Permission.file_id == fid,
|
||||
Permission.group_id == gid,
|
||||
)
|
||||
)
|
||||
perms = result.scalars().all()
|
||||
for p in perms:
|
||||
await db.delete(p)
|
||||
|
||||
await db.flush()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
# ─── Search & Bulk ───
|
||||
Reference in New Issue
Block a user