114 lines
4.2 KiB
Python
114 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Sequence
|
|
from urllib.parse import quote
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from govoplan_core.core.records import (
|
|
RecordContractError,
|
|
RecordSourceLocator,
|
|
RecordSourceReference,
|
|
)
|
|
from govoplan_files.backend.db.models import FileBlob, FileVersion
|
|
from govoplan_files.backend.storage.common import FileStorageError
|
|
from govoplan_files.backend.storage.files import get_asset_for_user
|
|
|
|
|
|
CAPABILITY_RECORD_SOURCE_FILES = "records.source.files"
|
|
|
|
|
|
class FilesRecordSource:
|
|
provider_id = "files"
|
|
|
|
def resource_types(self) -> Sequence[str]:
|
|
return ("file_version",)
|
|
|
|
def resolve(
|
|
self,
|
|
session: object,
|
|
principal: object,
|
|
*,
|
|
locator: RecordSourceLocator,
|
|
purpose: str,
|
|
) -> RecordSourceReference:
|
|
if not isinstance(session, Session):
|
|
raise RecordContractError(
|
|
"Files record references require a database session."
|
|
)
|
|
tenant_id = str(getattr(principal, "tenant_id", "") or "").strip()
|
|
if not tenant_id or locator.tenant_id != tenant_id:
|
|
raise RecordContractError("Files record references cannot cross tenants.")
|
|
if locator.source_module != "files" or locator.resource_type != "file_version":
|
|
raise RecordContractError("Unsupported Files record source type.")
|
|
if not str(purpose or "").strip():
|
|
raise RecordContractError("Files record references require a purpose.")
|
|
if not hasattr(principal, "has") or not (
|
|
principal.has("files:file:read") or principal.has("files:file:admin")
|
|
):
|
|
raise RecordContractError("Current Files read permission is required.")
|
|
user = getattr(principal, "user", None)
|
|
user_id = str(
|
|
getattr(user, "id", "") or getattr(principal, "membership_id", "") or ""
|
|
).strip()
|
|
if not user_id:
|
|
raise RecordContractError(
|
|
"Files record references require a tenant user principal."
|
|
)
|
|
try:
|
|
asset = get_asset_for_user(
|
|
session,
|
|
tenant_id=tenant_id,
|
|
user_id=user_id,
|
|
asset_id=locator.resource_id,
|
|
is_admin=principal.has("files:file:admin"),
|
|
)
|
|
except FileStorageError as exc:
|
|
raise RecordContractError(str(exc)) from exc
|
|
version_query = session.query(FileVersion).filter(
|
|
FileVersion.tenant_id == tenant_id,
|
|
FileVersion.file_asset_id == asset.id,
|
|
)
|
|
revision = locator.source_revision.strip()
|
|
version = version_query.filter(FileVersion.id == revision).one_or_none()
|
|
if version is None:
|
|
raise RecordContractError("The exact file version does not exist.")
|
|
blob = session.get(FileBlob, version.blob_id)
|
|
if blob is None or blob.tenant_id != tenant_id:
|
|
raise RecordContractError(
|
|
"The exact file version has no managed content object."
|
|
)
|
|
if blob.quarantined_at is not None or blob.integrity_status == "failed":
|
|
raise RecordContractError(
|
|
"The exact file version failed the current integrity gate."
|
|
)
|
|
return RecordSourceReference(
|
|
locator=locator,
|
|
label=version.filename_at_upload,
|
|
authority_mode="external_authoritative",
|
|
content_sha256=version.checksum_sha256,
|
|
content_type=version.content_type,
|
|
size_bytes=version.size_bytes,
|
|
recorded_at=version.created_at,
|
|
launch_url=(
|
|
f"/files?fileId={quote(asset.id, safe='')}&versionId={quote(version.id, safe='')}"
|
|
),
|
|
metadata={
|
|
"display_path": version.display_path_at_upload,
|
|
"version_number": version.version_number,
|
|
"integrity_status": blob.integrity_status,
|
|
"protection": blob.protection_discriminator,
|
|
},
|
|
)
|
|
|
|
|
|
def create_files_record_source(_context: object) -> FilesRecordSource:
|
|
return FilesRecordSource()
|
|
|
|
|
|
__all__ = [
|
|
"CAPABILITY_RECORD_SOURCE_FILES",
|
|
"FilesRecordSource",
|
|
"create_files_record_source",
|
|
]
|