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", ]