""" Asset Tracker Utility Helper utility for modules to easily save generated content to the unified asset library. """ from typing import Dict, Any, Optional from sqlalchemy.orm import Session from services.content_asset_service import ContentAssetService from models.content_asset_models import AssetType, AssetSource import logging import re from urllib.parse import urlparse logger = logging.getLogger(__name__) # Maximum file size (100MB) MAX_FILE_SIZE = 100 * 1024 * 1024 # Allowed URL schemes ALLOWED_URL_SCHEMES = ['http', 'https', '/'] # Allow relative paths starting with / def validate_file_url(file_url: str) -> bool: """Validate file URL format.""" if not file_url or not isinstance(file_url, str): return False # Allow relative paths if file_url.startswith('/'): return True # Validate absolute URLs try: parsed = urlparse(file_url) return parsed.scheme in ALLOWED_URL_SCHEMES and parsed.netloc except Exception: return False def save_asset_to_library( db: Session, user_id: str, asset_type: str, source_module: str, filename: str, file_url: str, file_path: Optional[str] = None, file_size: Optional[int] = None, mime_type: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, prompt: Optional[str] = None, tags: Optional[list] = None, asset_metadata: Optional[Dict[str, Any]] = None, provider: Optional[str] = None, model: Optional[str] = None, cost: Optional[float] = None, generation_time: Optional[float] = None, ) -> Optional[int]: """ Helper function to save a generated asset to the unified asset library. This can be called from any module (story writer, image studio, etc.) to automatically track generated content. Args: db: Database session user_id: Clerk user ID asset_type: 'text', 'image', 'video', or 'audio' source_module: 'story_writer', 'image_studio', 'main_text_generation', etc. filename: Original filename file_url: Public URL to access the asset file_path: Server file path (optional) file_size: File size in bytes (optional) mime_type: MIME type (optional) title: Asset title (optional) description: Asset description (optional) prompt: Generation prompt (optional) tags: List of tags (optional) asset_metadata: Additional metadata (optional) provider: AI provider used (optional) model: Model used (optional) cost: Generation cost (optional) generation_time: Generation time in seconds (optional) Returns: Asset ID if successful, None otherwise """ try: # Validate inputs if not user_id or not isinstance(user_id, str): logger.error("Invalid user_id provided") return None if not filename or not isinstance(filename, str): logger.error("Invalid filename provided") return None if not validate_file_url(file_url): logger.error(f"Invalid file_url format: {file_url}") return None if file_size and file_size > MAX_FILE_SIZE: logger.warning(f"File size {file_size} exceeds maximum {MAX_FILE_SIZE}") # Don't fail, just log warning # Convert string enums to enum types try: asset_type_enum = AssetType(asset_type.lower()) except ValueError: logger.warning(f"Invalid asset type: {asset_type}, defaulting to 'text'") asset_type_enum = AssetType.TEXT try: source_module_enum = AssetSource(source_module.lower()) except ValueError: logger.warning(f"Invalid source module: {source_module}, defaulting to 'story_writer'") source_module_enum = AssetSource.STORY_WRITER # Sanitize filename (remove path traversal attempts) filename = re.sub(r'[^\w\s\-_\.]', '', filename.split('/')[-1]) if not filename: filename = f"asset_{asset_type}_{source_module}.{asset_type}" # Generate title from filename if not provided if not title: title = filename.replace('_', ' ').replace('-', ' ').title() # Limit title length if len(title) > 200: title = title[:197] + '...' service = ContentAssetService(db) asset = service.create_asset( user_id=user_id, asset_type=asset_type_enum, source_module=source_module_enum, filename=filename, file_url=file_url, file_path=file_path, file_size=file_size, mime_type=mime_type, title=title, description=description, prompt=prompt, tags=tags or [], asset_metadata=asset_metadata or {}, provider=provider, model=model, cost=cost, generation_time=generation_time, ) logger.info(f"✅ Asset saved to library: {asset.id} ({asset_type} from {source_module})") return asset.id except Exception as e: logger.error(f"❌ Error saving asset to library: {str(e)}", exc_info=True) return None