from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, Field from govoplan_core.api.v1.schemas import DeltaDeletedItem from govoplan_files.backend.storage.common import FileConflictResolution class FileSpaceResponse(BaseModel): id: str label: str owner_type: Literal["user", "group"] owner_id: str description: str | None = None space_type: Literal["managed", "connector"] = "managed" connector_space_id: str | None = None connector_profile_id: str | None = None provider: str | None = None library_id: str | None = None remote_path: str | None = None sync_mode: str | None = None read_only: bool = False class FileSpacesResponse(BaseModel): spaces: list[FileSpaceResponse] class FileConnectorSpaceCreateRequest(BaseModel): owner_type: Literal["user", "group"] = "user" owner_id: str | None = None label: str connector_profile_id: str library_id: str | None = None remote_path: str = "" sync_mode: Literal["manual"] = "manual" read_only: bool = True metadata: dict[str, Any] = Field(default_factory=dict) class FileConnectorSpaceUpdateRequest(BaseModel): label: str | None = None library_id: str | None = None remote_path: str | None = None sync_mode: Literal["manual"] | None = None read_only: bool | None = None is_active: bool | None = None metadata: dict[str, Any] | None = None class FileConnectorSpaceResponse(BaseModel): id: str tenant_id: str owner_type: Literal["user", "group"] owner_id: str label: str connector_profile_id: str provider: str library_id: str | None = None remote_path: str = "" sync_mode: str read_only: bool is_active: bool created_at: str updated_at: str deleted_at: str | None = None metadata: dict[str, Any] = Field(default_factory=dict) class FileConnectorSpacesResponse(BaseModel): spaces: list[FileConnectorSpaceResponse] = Field(default_factory=list) class FileShareResponse(BaseModel): id: str file_asset_id: str target_type: str target_id: str permission: str created_by_user_id: str | None = None created_at: str expires_at: str | None = None revoked_at: str | None = None revoked_by_user_id: str | None = None active: bool class FileSharesResponse(BaseModel): shares: list[FileShareResponse] = Field(default_factory=list) class FileIntegrityScanCreateRequest(BaseModel): verify_checksums: bool = True batch_size: int = Field(default=100, ge=1, le=1000) class FileIntegrityScanResponse(BaseModel): id: str tenant_id: str storage_backend: str storage_prefix: str status: str revision: int phase: str verify_checksums: bool batch_size: int scanned_blob_count: int verified_blob_count: int quarantined_blob_count: int scanned_object_count: int orphan_object_count: int created_by_user_id: str | None = None started_at: str | None = None completed_at: str | None = None last_error: str | None = None created_at: str updated_at: str class FileIntegrityScansResponse(BaseModel): scans: list[FileIntegrityScanResponse] = Field(default_factory=list) class FileIntegrityFindingResponse(BaseModel): id: str scan_id: str tenant_id: str kind: str state: str revision: int blob_id: str | None = None storage_key: str expected_size_bytes: int | None = None observed_size_bytes: int | None = None expected_checksum_sha256: str | None = None observed_checksum_sha256: str | None = None resolved_at: str | None = None resolved_by_user_id: str | None = None created_at: str updated_at: str class FileIntegrityFindingsResponse(BaseModel): findings: list[FileIntegrityFindingResponse] = Field(default_factory=list) class FileIntegrityActionRequest(BaseModel): dry_run: bool = True expected_revision: int = Field(ge=1) class FileIntegrityScanRunRequest(BaseModel): expected_revision: int = Field(ge=1) class FileIntegrityActionResponse(BaseModel): action: str changed: bool dry_run: bool finding: FileIntegrityFindingResponse inspection_kind: str | None = None inspection_valid: bool | None = None class FileSourceProvenance(BaseModel): source_type: str | None = None connector_id: str | None = None provider: str | None = None external_id: str | None = None external_path: str | None = None external_url: str | None = None revision: str | None = None revision_label: str | None = None observed_at: str | None = None imported_at: str | None = None metadata: dict[str, Any] = Field(default_factory=dict) class FileAssetResponse(BaseModel): id: str tenant_id: str owner_type: str owner_id: str display_path: str filename: str description: str | None = None size_bytes: int content_type: str | None = None checksum_sha256: str version_id: str created_at: str updated_at: str deleted_at: str | None = None retained_until: str | None = None legal_hold: bool = False lifecycle_revision: int = 1 lifecycle_reason: str | None = None audit_relevant: bool = False metadata: dict[str, Any] | None = None source_provenance: FileSourceProvenance | None = None source_revision: str | None = None shares: list[FileShareResponse] = Field(default_factory=list) class FileFolderResponse(BaseModel): id: str tenant_id: str owner_type: str owner_id: str path: str created_at: str updated_at: str deleted_at: str | None = None class FileFoldersResponse(BaseModel): folders: list[FileFolderResponse] cursor: str | None = None next_cursor: str | None = None watermark: str | None = None class FileFolderCreateRequest(BaseModel): owner_type: Literal["user", "group"] owner_id: str path: str class FileFolderDeleteRequest(BaseModel): owner_type: Literal["user", "group"] owner_id: str path: str recursive: bool = True class FileFolderDeleteResponse(BaseModel): deleted_folders: int deleted_files: int class FileListResponse(BaseModel): files: list[FileAssetResponse] total: int cursor: str | None = None next_cursor: str | None = None watermark: str | None = None class FileDeltaResponse(BaseModel): files: list[FileAssetResponse] = Field(default_factory=list) folders: list[FileFolderResponse] = Field(default_factory=list) deleted: list[DeltaDeletedItem] = Field(default_factory=list) watermark: str | None = None has_more: bool = False full: bool = False class FileUploadResponse(BaseModel): files: list[FileAssetResponse] class ArchiveEntryResponse(BaseModel): path: str kind: Literal["file", "directory"] size_bytes: int compressed_size_bytes: int | None = None encrypted: bool = False class ArchivePreviewResponse(BaseModel): preview_token: str archive_format: str entries: list[ArchiveEntryResponse] = Field(default_factory=list) file_count: int directory_count: int expanded_size_bytes: int compressed_size_bytes: int requires_password: bool password_verified: bool expires_at: str class FileConnectorPolicySource(BaseModel): scope_type: Literal["system", "tenant", "user", "group", "campaign"] = "system" scope_id: str | None = None label: str | None = None policy: dict[str, Any] = Field(default_factory=dict) class FileConnectorPolicyEvaluateRequest(BaseModel): source_provenance: FileSourceProvenance operation: str = "access" policy_sources: list[FileConnectorPolicySource] = Field(default_factory=list) class FileConnectorPolicyEvaluateResponse(BaseModel): decision: dict[str, Any] class FileConnectorPolicyUpdateRequest(BaseModel): policy: dict[str, Any] = Field(default_factory=dict) class FileConnectorPolicyStepResponse(BaseModel): scope_type: str scope_id: str | None = None path: str label: str applied_fields: list[str] = Field(default_factory=list) policy: dict[str, Any] = Field(default_factory=dict) class FileConnectorPolicyResponse(BaseModel): scope_type: Literal["system", "tenant", "user", "group", "campaign"] scope_id: str | None = None policy: dict[str, Any] = Field(default_factory=dict) effective_policy: dict[str, Any] | None = None parent_policy: dict[str, Any] | None = None effective_policy_sources: list[FileConnectorPolicyStepResponse] = Field(default_factory=list) parent_policy_sources: list[FileConnectorPolicyStepResponse] = Field(default_factory=list) class FileConnectorProfileResponse(BaseModel): id: str label: str provider: str endpoint_url: str | None = None base_path: str | None = None enabled: bool = True scope_type: Literal["system", "tenant", "user", "group", "campaign"] = "system" scope_id: str | None = None source_path: str credential_profile_id: str | None = None credential_profile_label: str | None = None credential_mode: str = "none" credential_source: str | None = None credentials_configured: bool = False username: str | None = None capabilities: list[str] = Field(default_factory=list) policy_sources: list[FileConnectorPolicySource] = Field(default_factory=list) metadata: dict[str, Any] = Field(default_factory=dict) source_kind: str = "settings" class FileConnectorProfilesResponse(BaseModel): profiles: list[FileConnectorProfileResponse] class FileConnectorCredentialResponse(BaseModel): id: str label: str provider: str | None = None enabled: bool = True scope_type: Literal["system", "tenant", "user", "group", "campaign"] = "system" scope_id: str | None = None source_path: str credential_mode: str = "none" credential_secret_source: str | None = None credentials_configured: bool = False username: str | None = None policy_sources: list[FileConnectorPolicySource] = Field(default_factory=list) metadata: dict[str, Any] = Field(default_factory=dict) source_kind: str = "database" class FileConnectorCredentialsResponse(BaseModel): credentials: list[FileConnectorCredentialResponse] class FileConnectorSettingsDeltaResponse(BaseModel): profiles: list[FileConnectorProfileResponse] = Field(default_factory=list) credentials: list[FileConnectorCredentialResponse] = Field(default_factory=list) spaces: list[FileConnectorSpaceResponse] = Field(default_factory=list) policy: FileConnectorPolicyResponse | None = None changed_sections: list[str] = Field(default_factory=list) deleted: list[DeltaDeletedItem] = Field(default_factory=list) watermark: str | None = None has_more: bool = False full: bool = False class FileConnectorProfileCredentialsRequest(BaseModel): username: str | None = None password: str | None = None token: str | None = None password_env: str | None = None token_env: str | None = None secret_ref: str | None = None class FileConnectorDiscoveryCandidate(BaseModel): endpoint_url: str status: Literal["failed", "usable", "found", "credentials_rejected"] message: str class FileConnectorDiscoveryRequest(BaseModel): provider: Literal["seafile", "nextcloud", "webdav", "smb", "s3", "sharepoint", "onedrive", "nfs", "dms", "generic"] endpoint_url: str base_path: str | None = None credential_mode: Literal["none", "anonymous", "basic", "token", "secret_ref"] = "none" credentials: FileConnectorProfileCredentialsRequest = Field(default_factory=FileConnectorProfileCredentialsRequest) metadata: dict[str, Any] = Field(default_factory=dict) require_valid_credentials: bool = False class FileConnectorDiscoveryResponse(BaseModel): provider: str endpoint_url: str | None = None base_path: str | None = None status: Literal["usable", "found", "credentials_rejected", "not_found", "unsupported"] message: str candidates: list[FileConnectorDiscoveryCandidate] = Field(default_factory=list) metadata: dict[str, Any] = Field(default_factory=dict) class FileConnectorProfileCreateRequest(BaseModel): id: str label: str provider: Literal["seafile", "nextcloud", "webdav", "smb", "s3", "sharepoint", "onedrive", "nfs", "dms", "generic"] endpoint_url: str | None = None base_path: str | None = None enabled: bool = True scope_type: Literal["system", "tenant", "user", "group", "campaign"] = "tenant" scope_id: str | None = None credential_profile_id: str | None = None credential_mode: Literal["none", "anonymous", "basic", "token", "secret_ref"] = "none" credentials: FileConnectorProfileCredentialsRequest = Field(default_factory=FileConnectorProfileCredentialsRequest) capabilities: list[str] = Field(default_factory=lambda: ["browse", "import", "sync"]) policy: dict[str, Any] = Field(default_factory=dict) metadata: dict[str, Any] = Field(default_factory=dict) class FileConnectorProfileUpdateRequest(BaseModel): label: str | None = None provider: Literal["seafile", "nextcloud", "webdav", "smb", "s3", "sharepoint", "onedrive", "nfs", "dms", "generic"] | None = None endpoint_url: str | None = None base_path: str | None = None enabled: bool | None = None credential_profile_id: str | None = None credential_mode: Literal["none", "anonymous", "basic", "token", "secret_ref"] | None = None credentials: FileConnectorProfileCredentialsRequest | None = None clear_password: bool = False clear_token: bool = False capabilities: list[str] | None = None policy: dict[str, Any] | None = None metadata: dict[str, Any] | None = None class FileConnectorCredentialCreateRequest(BaseModel): id: str label: str provider: Literal["seafile", "nextcloud", "webdav", "smb", "s3", "sharepoint", "onedrive", "nfs", "dms", "generic"] | None = None enabled: bool = True scope_type: Literal["system", "tenant", "user", "group", "campaign"] = "tenant" scope_id: str | None = None credential_mode: Literal["none", "anonymous", "basic", "token", "secret_ref"] = "none" credentials: FileConnectorProfileCredentialsRequest = Field(default_factory=FileConnectorProfileCredentialsRequest) policy: dict[str, Any] = Field(default_factory=dict) metadata: dict[str, Any] = Field(default_factory=dict) class FileConnectorCredentialUpdateRequest(BaseModel): label: str | None = None provider: Literal["seafile", "nextcloud", "webdav", "smb", "s3", "sharepoint", "onedrive", "nfs", "dms", "generic"] | None = None enabled: bool | None = None credential_mode: Literal["none", "anonymous", "basic", "token", "secret_ref"] | None = None credentials: FileConnectorProfileCredentialsRequest | None = None clear_password: bool = False clear_token: bool = False policy: dict[str, Any] | None = None metadata: dict[str, Any] | None = None class FileConnectorProviderResponse(BaseModel): provider: str label: str protocol: str implemented: bool installed: bool browse_supported: bool import_supported: bool write_supported: bool = False optional_dependency: str | None = None permission_model: str sync_strategy: str conflict_strategy: str preview_strategy: str audit_events: list[str] = Field(default_factory=list) notes: str | None = None class FileConnectorProvidersResponse(BaseModel): providers: list[FileConnectorProviderResponse] class FileConnectorBrowseItem(BaseModel): kind: Literal["library", "folder", "file"] name: str path: str external_id: str | None = None external_url: str | None = None size_bytes: int | None = None content_type: str | None = None modified_at: str | None = None etag: str | None = None metadata: dict[str, Any] = Field(default_factory=dict) class FileConnectorBrowseResponse(BaseModel): profile_id: str provider: str path: str = "" library_id: str | None = None read_only: bool = True next_continuation_token: str | None = None has_more: bool = False decision: dict[str, Any] items: list[FileConnectorBrowseItem] class FileConnectorImportRequest(BaseModel): library_id: str path: str owner_type: Literal["user", "group"] = "user" owner_id: str | None = None target_folder: str | None = None target_path: str | None = None campaign_id: str | None = None conflict_strategy: Literal["reject", "overwrite", "rename", "skip"] = "reject" source_revision: str | None = None metadata: dict[str, Any] = Field(default_factory=dict) class FileConnectorSyncResponse(BaseModel): file: FileAssetResponse action: Literal["created", "updated", "unchanged"] previous_version_id: str | None = None current_version_id: str class FileConnectorFolderSyncRequest(BaseModel): path: str = "" target_folder: str | None = None recursive: bool = True conflict_strategy: Literal["reject", "overwrite", "rename", "skip"] = "skip" max_files: int = Field(default=100, ge=1, le=500) max_depth: int = Field(default=12, ge=0, le=50) metadata: dict[str, Any] = Field(default_factory=dict) class FileConnectorFolderSyncItemResponse(BaseModel): source_path: str target_path: str | None = None action: Literal[ "created", "updated", "unchanged", "skipped", "conflict", "policy_denied", "failed", ] file: FileAssetResponse | None = None previous_version_id: str | None = None current_version_id: str | None = None source_revision: str | None = None detail: str | None = None policy_decision: dict[str, Any] | None = None class FileConnectorFolderSyncSummary(BaseModel): discovered: int = 0 created: int = 0 updated: int = 0 unchanged: int = 0 skipped: int = 0 conflicts: int = 0 policy_denied: int = 0 failed: int = 0 class FileConnectorFolderSyncResponse(BaseModel): connector_space_id: str connector_profile_id: str provider: str remote_path: str target_folder: str recursive: bool truncated: bool = False summary: FileConnectorFolderSyncSummary items: list[FileConnectorFolderSyncItemResponse] = Field(default_factory=list) class FileConnectorWriteRequest(BaseModel): file_id: str remote_path: str idempotency_key: str = Field(min_length=8, max_length=160) expected_revision: str | None = Field(default=None, max_length=500) class FileConnectorWriteResponse(BaseModel): recovery_operation_id: str status: str replayed: bool = False provider: str remote_path: str revision: str | None = None checksum_sha256: str size_bytes: int class BulkDeleteRequest(BaseModel): file_ids: list[str] class BulkDeleteResponse(BaseModel): deleted_count: int class FileLifecycleUpdateRequest(BaseModel): retained_until: datetime | None = None legal_hold: bool = False reason: str = Field(min_length=1, max_length=500) expected_revision: int = Field(ge=1) class FileRestoreResponse(BaseModel): restored_count: int class FileFolderRestoreResponse(BaseModel): restored_folders: int restored_files: int class FilePurgePreviewRequest(BaseModel): file_ids: list[str] = Field(min_length=1, max_length=100) class FilePurgePreviewItem(BaseModel): file_id: str filename: str lifecycle_revision: int deleted_at: str | None = None retained_until: str | None = None legal_hold: bool = False blockers: list[str] = Field(default_factory=list) blob_ids: list[str] = Field(default_factory=list) class FilePurgePreviewResponse(BaseModel): preview_sha256: str eligible: bool items: list[FilePurgePreviewItem] class FilePurgeExecuteRequest(FilePurgePreviewRequest): preview_sha256: str = Field(min_length=64, max_length=64) idempotency_key: str = Field(min_length=8, max_length=160) approval_reference: str = Field(min_length=3, max_length=1000) confirmation: Literal["PURGE"] class FilePurgeResponse(BaseModel): recovery_operation_id: str status: str replayed: bool = False purged_files: int = 0 released_blobs: int = 0 class FileBlobGcRequest(BaseModel): limit: int = Field(default=25, ge=1, le=100) approval_reference: str = Field(min_length=3, max_length=1000) class FileBlobGcResponse(BaseModel): inspected_blobs: int deleted_blobs: int unresolved_operation_ids: list[str] = Field(default_factory=list) class ConflictResolutionRequest(BaseModel): target_path: str action: Literal["overwrite", "rename", "skip"] new_path: str | None = None def _conflict_resolutions(items: list[ConflictResolutionRequest] | None) -> list[FileConflictResolution]: return [FileConflictResolution(target_path=item.target_path, action=item.action, new_path=item.new_path) for item in items or []] class FileShareRequest(BaseModel): target_type: Literal["user", "group", "campaign", "tenant"] target_id: str permission: Literal["read", "write", "manage"] = "read" expires_at: datetime | None = None class BulkFileShareRequest(BaseModel): file_ids: list[str] = Field(default_factory=list, max_length=1000) target_type: Literal["user", "group", "campaign", "tenant"] target_id: str permission: Literal["read", "write", "manage"] = "read" expires_at: datetime | None = None class BulkFileShareResponse(BaseModel): shares: list[FileShareResponse] shared_count: int class RenameRequest(BaseModel): file_ids: list[str] = Field(default_factory=list) folder_paths: list[str] = Field(default_factory=list) owner_type: Literal["user", "group"] owner_id: str mode: Literal["direct", "prefix", "suffix", "replace"] new_name: str | None = None find: str | None = None replacement: str = "" prefix: str = "" suffix: str = "" recursive: bool = False dry_run: bool = True class RenamePreviewItem(BaseModel): kind: Literal["file", "folder"] id: str file_id: str | None = None folder_path: str | None = None old_path: str new_path: str class RenameResponse(BaseModel): dry_run: bool items: list[RenamePreviewItem] class TransferRequest(BaseModel): operation: Literal["move", "copy"] file_ids: list[str] = Field(default_factory=list) folder_paths: list[str] = Field(default_factory=list) source_owner_type: Literal["user", "group"] source_owner_id: str target_owner_type: Literal["user", "group"] target_owner_id: str target_folder: str = "" conflict_strategy: Literal["reject", "overwrite", "rename"] = "reject" conflict_resolutions: list[ConflictResolutionRequest] = Field(default_factory=list) class TransferResponse(BaseModel): operation: str files: int folders: int class ArchiveRequest(BaseModel): file_ids: list[str] filename: str = "files.zip" class PatternResolveRequest(BaseModel): patterns: list[str] owner_type: Literal["user", "group"] | None = None owner_id: str | None = None campaign_id: str | None = None path_prefix: str | None = None include_unmatched: bool = True case_sensitive: bool = False class PatternMatchResponse(BaseModel): pattern: str matches: list[FileAssetResponse] class PatternResolveResponse(BaseModel): patterns: list[PatternMatchResponse] unmatched: list[FileAssetResponse] = Field(default_factory=list)