feat(webui): complete files quick access

This commit is contained in:
2026-08-19 19:48:56 +02:00
parent ad55d47645
commit e1361427b8
9 changed files with 228 additions and 17 deletions
+10 -9
View File
@@ -686,10 +686,11 @@ manifest = ModuleManifest(
summary="Use managed files in the Records and documents area and keep a compact file surface beside current work.",
body=(
"Files contributes its authorized workspace to Records and documents. When Quick Access is enabled, the owner-rendered "
"recent-file selector appears in Files on the right rail and can return one exact file-version reference to the current task. "
"Opening the selector causes Files to re-run its own authorization; completion and cancellation are explicit, and the full Files "
"workspace remains available as a deep-link fallback. View and rail settings may recommend or focus this tool, but do not bypass "
"file ownership, shares, connector policy, integrity gates, or purpose-aware access."
"selector shows at most seven recently changed files and can return one exact file-version reference to the current task. "
"Accounts with upload permission can launch the full Files upload dialog into a freshly reauthorized managed space. Opening "
"either path causes Files to re-run its own provider, space, folder, object, and scope checks; completion and cancellation are "
"explicit, and the full Files workspace remains available as a deep-link fallback. View and rail settings may recommend or "
"focus this tool, but do not bypass file ownership, shares, connector policy, integrity gates, or purpose-aware access."
),
layer="configured",
documentation_types=("user", "admin"),
@@ -715,11 +716,11 @@ manifest = ModuleManifest(
"summary": "Verwaltete Dateien im Produktbereich Akten und Dokumente und optional neben der aktuellen Arbeit verwenden.",
"body": (
"Files ordnet seinen berechtigten Arbeitsbereich Akten und Dokumente zu. Ist der Schnellzugriff aktiviert, erscheint "
"die vom Modul gerenderte Auswahl zuletzt berechtigter Dateien rechts neben der aktuellen Seite und kann einen exakten "
"Dateiversionsverweis an die aktuelle Aufgabe zurückgeben. Files prüft die Berechtigung beim Öffnen erneut; Abschluss und "
"Abbruch sind ausdrücklich, und der vollständige Files-Arbeitsbereich bleibt als Ausweichziel erreichbar. Ansichts- und "
"Leistenkonfiguration dürfen das Werkzeug empfehlen oder fokussieren, umgehen aber weder Eigentum, Freigaben, Connector-"
"Richtlinien, Integritätsprüfungen noch zweckgebundenen Zugriff."
"die vom Modul gerenderte Auswahl von höchstens sieben zuletzt geänderten berechtigten Dateien rechts neben der aktuellen "
"Seite und kann einen exakten Dateiversionsverweis zurückgeben. Mit Upload-Berechtigung lässt sich der vollständige "
"Upload-Dialog in einem erneut berechtigungsgeprüften verwalteten Bereich öffnen. Files prüft Anbieter, Bereich, Ordner, "
"Objekt und Berechtigung bei jedem Pfad erneut. Abschluss und Abbruch sind ausdrücklich; Eigentum, Freigaben, Connector-"
"Richtlinien, Integritätsprüfungen und zweckgebundener Zugriff bleiben maßgeblich."
),
}
},
+28 -1
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from typing import Literal
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from govoplan_core.auth import ApiPrincipal, require_scope
@@ -14,6 +14,7 @@ from govoplan_files.backend.storage.files import (
count_assets_for_user,
list_assets_for_user,
list_assets_for_user_window,
list_recent_assets_for_user,
)
@@ -79,6 +80,7 @@ def list_files(
path_prefix: str | None = None,
campaign_usage: Literal["linked", "unlinked"] | None = None,
audit_relevant: bool | None = None,
sort: Literal["path", "recent"] = "path",
page_size: int | None = Query(default=None, ge=1, le=1000),
cursor: str | None = None,
session: Session = Depends(get_session),
@@ -99,6 +101,31 @@ def list_files(
audit_relevant=audit_relevant,
is_admin=_is_admin(principal),
)
if sort == "recent":
if cursor:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail="Recent file projections do not accept a path-order cursor.",
)
recent_limit = page_size or 25
assets = list_recent_assets_for_user(
session,
tenant_id=principal.tenant_id,
user_id=principal.user.id,
limit=recent_limit,
owner_type=owner_type,
owner_id=owner_id,
campaign_id=campaign_id,
path_prefix=path_prefix,
campaign_usage=campaign_usage,
audit_relevant=audit_relevant,
is_admin=_is_admin(principal),
)
return FileListResponse(
files=_asset_list_response(session, assets, include_shares=True),
total=total,
watermark=watermark,
)
effective_page_size = _cursor_page_size(FILES_LIST_CURSOR_SCOPE, cursor, page_size)
if effective_page_size is not None:
fingerprint = _files_list_fingerprint(
@@ -529,6 +529,41 @@ def list_assets_for_user(
return query.order_by(FileAsset.display_path.asc(), FileAsset.updated_at.desc(), FileAsset.id.asc()).all()
def list_recent_assets_for_user(
session: Session,
*,
tenant_id: str,
user_id: str,
limit: int,
owner_type: str | None = None,
owner_id: str | None = None,
campaign_id: str | None = None,
path_prefix: str | None = None,
campaign_usage: str | None = None,
audit_relevant: bool | None = None,
is_admin: bool = False,
) -> list[FileAsset]:
"""Return a bounded recent projection through the normal access query."""
query = _asset_visibility_query_for_user(
session,
tenant_id=tenant_id,
user_id=user_id,
owner_type=owner_type,
owner_id=owner_id,
campaign_id=campaign_id,
path_prefix=path_prefix,
campaign_usage=campaign_usage,
audit_relevant=audit_relevant,
is_admin=is_admin,
)
return (
query.order_by(FileAsset.updated_at.desc(), FileAsset.id.asc())
.limit(max(1, limit))
.all()
)
def list_assets_for_user_window(
session: Session,
*,