# app/modules/cms/exceptions.py """ CMS module exceptions. This module provides exception classes for CMS operations including: - Content page management - Media file handling - Store theme customization """ from typing import Any from app.exceptions.base import ( AuthorizationException, BusinessLogicException, ConflictException, ResourceNotFoundException, ValidationException, ) __all__ = [ # Content page exceptions "ContentPageNotFoundException", "ContentPageAlreadyExistsException", "ContentPageSlugReservedException", "ContentPageNotPublishedException", "UnauthorizedContentPageAccessException", "StoreNotAssociatedException", "NoPlatformSubscriptionException", "ContentPageValidationException", # Media exceptions "MediaNotFoundException", "MediaUploadException", "MediaValidationException", "UnsupportedMediaTypeException", "MediaFileTooLargeException", "MediaOptimizationException", "MediaDeleteException", # Theme exceptions "StoreThemeNotFoundException", "InvalidThemeDataException", "ThemePresetNotFoundException", "ThemeValidationException", "ThemePresetAlreadyAppliedException", "InvalidColorFormatException", "InvalidFontFamilyException", "ThemeOperationException", ] # ============================================================================= # Content Page Exceptions # ============================================================================= class ContentPageNotFoundException(ResourceNotFoundException): """Raised when a content page is not found.""" def __init__(self, identifier: str | int | None = None): if identifier: message = f"Content page not found: {identifier}" else: message = "Content page not found" super().__init__( message=message, resource_type="ContentPage", identifier=str(identifier) if identifier else "unknown", error_code="CONTENT_PAGE_NOT_FOUND", ) class ContentPageAlreadyExistsException(ConflictException): # noqa: MOD025 """Raised when a content page with the same slug already exists.""" def __init__(self, slug: str, store_id: int | None = None): if store_id: message = f"Content page with slug '{slug}' already exists for this store" else: message = f"Platform content page with slug '{slug}' already exists" super().__init__( message=message, error_code="CONTENT_PAGE_ALREADY_EXISTS", details={"slug": slug, "store_id": store_id} if store_id else {"slug": slug}, ) class ContentPageSlugReservedException(ValidationException): # noqa: MOD025 """Raised when trying to use a reserved slug.""" def __init__(self, slug: str): super().__init__( message=f"Content page slug '{slug}' is reserved", field="slug", details={"slug": slug}, ) self.error_code = "CONTENT_PAGE_SLUG_RESERVED" class ContentPageNotPublishedException(BusinessLogicException): # noqa: MOD025 """Raised when trying to access an unpublished content page.""" def __init__(self, slug: str): super().__init__( message=f"Content page '{slug}' is not published", error_code="CONTENT_PAGE_NOT_PUBLISHED", details={"slug": slug}, ) class UnauthorizedContentPageAccessException(AuthorizationException): """Raised when a user tries to access/modify a content page they don't own.""" def __init__(self, action: str = "access"): super().__init__( message=f"Cannot {action} content pages from other stores", error_code="CONTENT_PAGE_ACCESS_DENIED", details={"required_permission": f"content_page:{action}"}, ) class StoreNotAssociatedException(AuthorizationException): # noqa: MOD025 """Raised when a user is not associated with a store.""" def __init__(self): super().__init__( message="User is not associated with a store", error_code="STORE_NOT_ASSOCIATED", details={"required_permission": "store:member"}, ) class NoPlatformSubscriptionException(BusinessLogicException): """Raised when a store is not subscribed to any platform.""" def __init__(self, store_id: int | None = None): details = {} if store_id: details["store_id"] = store_id super().__init__( message="Store is not subscribed to any platform", error_code="NO_PLATFORM_SUBSCRIPTION", details=details if details else None, ) class ContentPageValidationException(ValidationException): # noqa: MOD025 """Raised when content page data validation fails.""" def __init__(self, field: str, message: str, value: str | None = None): details = {} if value: details["value"] = value super().__init__(message=message, field=field, details=details if details else None) self.error_code = "CONTENT_PAGE_VALIDATION_FAILED" # ============================================================================= # Media Exceptions # ============================================================================= class MediaNotFoundException(ResourceNotFoundException): """Raised when a media file is not found.""" def __init__(self, media_id: int | str | None = None): if media_id: message = f"Media file '{media_id}' not found" else: message = "Media file not found" super().__init__( resource_type="MediaFile", identifier=str(media_id) if media_id else "unknown", message=message, error_code="MEDIA_NOT_FOUND", ) class MediaUploadException(BusinessLogicException): # noqa: MOD025 """Raised when media upload fails.""" def __init__(self, message: str = "Media upload failed", details: dict[str, Any] | None = None): super().__init__( message=message, error_code="MEDIA_UPLOAD_FAILED", details=details, ) class MediaValidationException(ValidationException): """Raised when media validation fails (file type, size, etc.).""" def __init__( self, message: str = "Media validation failed", field: str | None = None, details: dict[str, Any] | None = None, ): super().__init__(message=message, field=field, details=details) self.error_code = "MEDIA_VALIDATION_FAILED" class UnsupportedMediaTypeException(ValidationException): """Raised when media file type is not supported.""" def __init__(self, file_type: str, allowed_types: list[str] | None = None): details = {"file_type": file_type} if allowed_types: details["allowed_types"] = allowed_types super().__init__( message=f"Unsupported media type: {file_type}", field="file", details=details, ) self.error_code = "UNSUPPORTED_MEDIA_TYPE" class MediaFileTooLargeException(ValidationException): """Raised when media file exceeds size limit.""" def __init__(self, file_size: int, max_size: int, media_type: str = "file"): super().__init__( message=f"File size ({file_size} bytes) exceeds maximum allowed ({max_size} bytes) for {media_type}", field="file", details={ "file_size": file_size, "max_size": max_size, "media_type": media_type, }, ) self.error_code = "MEDIA_FILE_TOO_LARGE" class MediaOptimizationException(BusinessLogicException): """Raised when media optimization fails.""" def __init__(self, message: str = "Only images can be optimized"): super().__init__( message=message, error_code="MEDIA_OPTIMIZATION_FAILED", ) class MediaDeleteException(BusinessLogicException): # noqa: MOD025 """Raised when media deletion fails.""" def __init__(self, message: str, details: dict[str, Any] | None = None): super().__init__( message=message, error_code="MEDIA_DELETE_FAILED", details=details, ) # ============================================================================= # Theme Exceptions # ============================================================================= class StoreThemeNotFoundException(ResourceNotFoundException): """Raised when a store theme is not found.""" def __init__(self, store_identifier: str): super().__init__( resource_type="StoreTheme", identifier=store_identifier, message=f"Theme for store '{store_identifier}' not found", error_code="STORE_THEME_NOT_FOUND", ) class InvalidThemeDataException(ValidationException): # noqa: MOD025 """Raised when theme data is invalid.""" def __init__( self, message: str = "Invalid theme data", field: str | None = None, details: dict[str, Any] | None = None, ): super().__init__( message=message, field=field, details=details, ) self.error_code = "INVALID_THEME_DATA" class ThemePresetNotFoundException(ResourceNotFoundException): """Raised when a theme preset is not found.""" def __init__(self, preset_name: str, available_presets: list | None = None): super().__init__( resource_type="ThemePreset", identifier=preset_name, message=f"Theme preset '{preset_name}' not found", error_code="THEME_PRESET_NOT_FOUND", ) if available_presets: self.details["available_presets"] = available_presets class ThemeValidationException(ValidationException): """Raised when theme validation fails.""" def __init__( self, message: str = "Theme validation failed", field: str | None = None, validation_errors: dict[str, str] | None = None, ): details = {} if validation_errors: details["validation_errors"] = validation_errors super().__init__( message=message, field=field, details=details, ) self.error_code = "THEME_VALIDATION_FAILED" class ThemePresetAlreadyAppliedException(BusinessLogicException): # noqa: MOD025 """Raised when trying to apply the same preset that's already active.""" def __init__(self, preset_name: str, store_code: str): super().__init__( message=f"Preset '{preset_name}' is already applied to store '{store_code}'", error_code="THEME_PRESET_ALREADY_APPLIED", details={"preset_name": preset_name, "store_code": store_code}, ) class InvalidColorFormatException(ValidationException): """Raised when color format is invalid.""" def __init__(self, color_value: str, field: str): super().__init__( message=f"Invalid color format: {color_value}", field=field, details={"color_value": color_value}, ) self.error_code = "INVALID_COLOR_FORMAT" class InvalidFontFamilyException(ValidationException): """Raised when font family is invalid.""" def __init__(self, font_value: str, field: str): super().__init__( message=f"Invalid font family: {font_value}", field=field, details={"font_value": font_value}, ) self.error_code = "INVALID_FONT_FAMILY" class ThemeOperationException(BusinessLogicException): """Raised when theme operation fails.""" def __init__(self, operation: str, store_code: str, reason: str): super().__init__( message=f"Theme operation '{operation}' failed for store '{store_code}': {reason}", error_code="THEME_OPERATION_FAILED", details={ "operation": operation, "store_code": store_code, "reason": reason, }, )