refactor: migrate templates and static files to self-contained modules

Templates Migration:
- Migrate admin templates to modules (tenancy, billing, monitoring, marketplace, etc.)
- Migrate vendor templates to modules (tenancy, billing, orders, messaging, etc.)
- Migrate storefront templates to modules (catalog, customers, orders, cart, checkout, cms)
- Migrate public templates to modules (billing, marketplace, cms)
- Keep shared templates in app/templates/ (base.html, errors/, partials/, macros/)
- Migrate letzshop partials to marketplace module

Static Files Migration:
- Migrate admin JS to modules: tenancy (23 files), core (5 files), monitoring (1 file)
- Migrate vendor JS to modules: tenancy (4 files), core (2 files)
- Migrate shared JS: vendor-selector.js to core, media-picker.js to cms
- Migrate storefront JS: storefront-layout.js to core
- Keep framework JS in static/ (api-client, utils, money, icons, log-config, lib/)
- Update all template references to use module_static paths

Naming Consistency:
- Rename static/platform/ to static/public/
- Rename app/templates/platform/ to app/templates/public/
- Update all extends and static references

Documentation:
- Update module-system.md with shared templates documentation
- Update frontend-structure.md with new module JS organization

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 14:34:16 +01:00
parent 843703258f
commit 4e28d91a78
542 changed files with 11603 additions and 9037 deletions

View File

@@ -1,11 +1,15 @@
# app/modules/cms/exceptions.py
"""
CMS Module Exceptions
CMS module exceptions.
These exceptions are raised by the CMS module service layer
and converted to HTTP responses by the global exception handler.
This module provides exception classes for CMS operations including:
- Content page management
- Media file handling
- Vendor theme customization
"""
from typing import Any
from app.exceptions.base import (
AuthorizationException,
BusinessLogicException,
@@ -14,6 +18,39 @@ from app.exceptions.base import (
ValidationException,
)
__all__ = [
# Content page exceptions
"ContentPageNotFoundException",
"ContentPageAlreadyExistsException",
"ContentPageSlugReservedException",
"ContentPageNotPublishedException",
"UnauthorizedContentPageAccessException",
"VendorNotAssociatedException",
"ContentPageValidationException",
# Media exceptions
"MediaNotFoundException",
"MediaUploadException",
"MediaValidationException",
"UnsupportedMediaTypeException",
"MediaFileTooLargeException",
"MediaOptimizationException",
"MediaDeleteException",
# Theme exceptions
"VendorThemeNotFoundException",
"InvalidThemeDataException",
"ThemePresetNotFoundException",
"ThemeValidationException",
"ThemePresetAlreadyAppliedException",
"InvalidColorFormatException",
"InvalidFontFamilyException",
"ThemeOperationException",
]
# =============================================================================
# Content Page Exceptions
# =============================================================================
class ContentPageNotFoundException(ResourceNotFoundException):
"""Raised when a content page is not found."""
@@ -25,8 +62,9 @@ class ContentPageNotFoundException(ResourceNotFoundException):
message = "Content page not found"
super().__init__(
message=message,
resource_type="content_page",
resource_type="ContentPage",
identifier=str(identifier) if identifier else "unknown",
error_code="CONTENT_PAGE_NOT_FOUND",
)
@@ -38,7 +76,11 @@ class ContentPageAlreadyExistsException(ConflictException):
message = f"Content page with slug '{slug}' already exists for this vendor"
else:
message = f"Platform content page with slug '{slug}' already exists"
super().__init__(message=message)
super().__init__(
message=message,
error_code="CONTENT_PAGE_ALREADY_EXISTS",
details={"slug": slug, "vendor_id": vendor_id} if vendor_id else {"slug": slug},
)
class ContentPageSlugReservedException(ValidationException):
@@ -48,15 +90,20 @@ class ContentPageSlugReservedException(ValidationException):
super().__init__(
message=f"Content page slug '{slug}' is reserved",
field="slug",
value=slug,
details={"slug": slug},
)
self.error_code = "CONTENT_PAGE_SLUG_RESERVED"
class ContentPageNotPublishedException(BusinessLogicException):
"""Raised when trying to access an unpublished content page."""
def __init__(self, slug: str):
super().__init__(message=f"Content page '{slug}' is not published")
super().__init__(
message=f"Content page '{slug}' is not published",
error_code="CONTENT_PAGE_NOT_PUBLISHED",
details={"slug": slug},
)
class UnauthorizedContentPageAccessException(AuthorizationException):
@@ -85,26 +132,225 @@ class ContentPageValidationException(ValidationException):
"""Raised when content page data validation fails."""
def __init__(self, field: str, message: str, value: str | None = None):
super().__init__(message=message, field=field, value=value)
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 item is not found."""
"""Raised when a media file is not found."""
def __init__(self, identifier: str | int | None = None):
if identifier:
message = f"Media item not found: {identifier}"
def __init__(self, media_id: int | str | None = None):
if media_id:
message = f"Media file '{media_id}' not found"
else:
message = "Media item not found"
message = "Media file not found"
super().__init__(
resource_type="MediaFile",
identifier=str(media_id) if media_id else "unknown",
message=message,
resource_type="media",
identifier=str(identifier) if identifier else "unknown",
error_code="MEDIA_NOT_FOUND",
)
class MediaUploadException(BusinessLogicException):
"""Raised when a media upload fails."""
"""Raised when media upload fails."""
def __init__(self, reason: str):
super().__init__(message=f"Media upload failed: {reason}")
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):
"""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 VendorThemeNotFoundException(ResourceNotFoundException):
"""Raised when a vendor theme is not found."""
def __init__(self, vendor_identifier: str):
super().__init__(
resource_type="VendorTheme",
identifier=vendor_identifier,
message=f"Theme for vendor '{vendor_identifier}' not found",
error_code="VENDOR_THEME_NOT_FOUND",
)
class InvalidThemeDataException(ValidationException):
"""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):
"""Raised when trying to apply the same preset that's already active."""
def __init__(self, preset_name: str, vendor_code: str):
super().__init__(
message=f"Preset '{preset_name}' is already applied to vendor '{vendor_code}'",
error_code="THEME_PRESET_ALREADY_APPLIED",
details={"preset_name": preset_name, "vendor_code": vendor_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, vendor_code: str, reason: str):
super().__init__(
message=f"Theme operation '{operation}' failed for vendor '{vendor_code}': {reason}",
error_code="THEME_OPERATION_FAILED",
details={
"operation": operation,
"vendor_code": vendor_code,
"reason": reason,
},
)