from __future__ import annotations 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" 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 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 target_type: str target_id: str permission: str created_at: str revoked_at: str | 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 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] 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 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 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"] = "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 BulkDeleteRequest(BaseModel): file_ids: list[str] class BulkDeleteResponse(BaseModel): deleted_count: int 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" 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" 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)