Initial commit

This commit is contained in:
Ke Sun
2025-11-30 18:22:17 +08:00
commit aea2fe391e
449 changed files with 83030 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
"""
Storage strategy interface and concrete implementations for file upload system.
"""
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Dict, Any
import uuid
from app.core.upload_enums import UploadContext
from app.core.upload_policies import UploadPolicy, get_upload_policy
from app.core.config import settings
class StorageStrategy(ABC):
"""Abstract base class for storage strategies."""
@abstractmethod
def get_storage_path(
self,
tenant_id: uuid.UUID,
file_id: uuid.UUID,
file_extension: str,
metadata: Dict[str, Any]
) -> Path:
"""
Generate the storage path for a file.
Args:
tenant_id: The tenant ID
file_id: The unique file ID
file_extension: The file extension (e.g., ".jpg")
metadata: Additional metadata that may influence path generation
Returns:
Path object representing the file storage location
"""
pass
@abstractmethod
def get_upload_policy(self) -> UploadPolicy:
"""
Get the upload policy for this storage strategy.
Returns:
UploadPolicy object with constraints and rules
"""
pass
class AvatarStorageStrategy(StorageStrategy):
"""Storage strategy for user avatar files."""
def get_storage_path(
self,
tenant_id: uuid.UUID,
file_id: uuid.UUID,
file_extension: str,
metadata: Dict[str, Any]
) -> Path:
"""
Generate storage path for avatar files.
Path format: {GENERIC_FILE_PATH}/avatars/{tenant_id}/{file_id}{extension}
"""
base_path = Path(settings.GENERIC_FILE_PATH)
return base_path / "avatars" / str(tenant_id) / f"{file_id}{file_extension}"
def get_upload_policy(self) -> UploadPolicy:
"""Get upload policy for avatar context."""
return get_upload_policy(UploadContext.AVATAR)
class AppIconStorageStrategy(StorageStrategy):
"""Storage strategy for application icon files."""
def get_storage_path(
self,
tenant_id: uuid.UUID,
file_id: uuid.UUID,
file_extension: str,
metadata: Dict[str, Any]
) -> Path:
"""
Generate storage path for app icon files.
Path format: {GENERIC_FILE_PATH}/app_icons/{tenant_id}/{file_id}{extension}
"""
base_path = Path(settings.GENERIC_FILE_PATH)
return base_path / "app_icons" / str(tenant_id) / f"{file_id}{file_extension}"
def get_upload_policy(self) -> UploadPolicy:
"""Get upload policy for app_icon context."""
return get_upload_policy(UploadContext.APP_ICON)
class KnowledgeBaseStorageStrategy(StorageStrategy):
"""Storage strategy for knowledge base files."""
def get_storage_path(
self,
tenant_id: uuid.UUID,
file_id: uuid.UUID,
file_extension: str,
metadata: Dict[str, Any]
) -> Path:
"""
Generate storage path for knowledge base files.
Path format: {GENERIC_FILE_PATH}/knowledge_base/{tenant_id}/{kb_id}/{file_id}{extension}
If kb_id is provided in metadata, it will be included in the path for compatibility
with existing knowledge base file structure.
"""
base_path = Path(settings.GENERIC_FILE_PATH)
kb_id = metadata.get("kb_id")
if kb_id:
# Include kb_id in path for compatibility with existing structure
return base_path / "knowledge_base" / str(tenant_id) / str(kb_id) / f"{file_id}{file_extension}"
else:
# Default path without kb_id
return base_path / "knowledge_base" / str(tenant_id) / f"{file_id}{file_extension}"
def get_upload_policy(self) -> UploadPolicy:
"""Get upload policy for knowledge_base context."""
return get_upload_policy(UploadContext.KNOWLEDGE_BASE)
class TempStorageStrategy(StorageStrategy):
"""Storage strategy for temporary files."""
def get_storage_path(
self,
tenant_id: uuid.UUID,
file_id: uuid.UUID,
file_extension: str,
metadata: Dict[str, Any]
) -> Path:
"""
Generate storage path for temporary files.
Path format: {GENERIC_FILE_PATH}/temp/{tenant_id}/{file_id}{extension}
"""
base_path = Path(settings.GENERIC_FILE_PATH)
return base_path / "temp" / str(tenant_id) / f"{file_id}{file_extension}"
def get_upload_policy(self) -> UploadPolicy:
"""Get upload policy for temp context."""
return get_upload_policy(UploadContext.TEMP)
class AttachmentStorageStrategy(StorageStrategy):
"""Storage strategy for attachment files."""
def get_storage_path(
self,
tenant_id: uuid.UUID,
file_id: uuid.UUID,
file_extension: str,
metadata: Dict[str, Any]
) -> Path:
"""
Generate storage path for attachment files.
Path format: {GENERIC_FILE_PATH}/attachments/{tenant_id}/{file_id}{extension}
"""
base_path = Path(settings.GENERIC_FILE_PATH)
return base_path / "attachments" / str(tenant_id) / f"{file_id}{file_extension}"
def get_upload_policy(self) -> UploadPolicy:
"""Get upload policy for attachment context."""
return get_upload_policy(UploadContext.ATTACHMENT)
class StrategyFactory:
"""Factory class for creating storage strategies based on upload context."""
_strategies = {
UploadContext.AVATAR: AvatarStorageStrategy,
UploadContext.APP_ICON: AppIconStorageStrategy,
UploadContext.KNOWLEDGE_BASE: KnowledgeBaseStorageStrategy,
UploadContext.TEMP: TempStorageStrategy,
UploadContext.ATTACHMENT: AttachmentStorageStrategy,
}
@classmethod
def get_strategy(cls, context: UploadContext) -> StorageStrategy:
"""
Get the appropriate storage strategy for the given context.
Args:
context: The upload context
Returns:
An instance of the appropriate StorageStrategy
Raises:
ValueError: If no strategy is defined for the given context
"""
strategy_class = cls._strategies.get(context)
if strategy_class is None:
raise ValueError(f"No storage strategy defined for context: {context}")
return strategy_class()