Updated conditions to check 'uploads/' without a leading slash across preview, background, foreground, and asset image URLs. This ensures consistent handling of URLs during the relocation process and avoids potential mismatches in the path string comparison.
228 lines
8.4 KiB
Python
228 lines
8.4 KiB
Python
# app/api/v1/themes/router.py
|
|
from typing import List
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.api.dependencies.common import get_storage_provider
|
|
from app.api.dependencies.auth import get_current_user
|
|
from app.core.database import get_db
|
|
from app.crud.event_theme import event_theme as event_theme_crud
|
|
from app.models import User
|
|
from app.schemas.event_themes import EventThemeCreate, EventThemeResponse, EventThemeUpdate
|
|
from app.core.storage import StorageProvider
|
|
from app.utils.files import _relocate_theme_file
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.post("/", response_model=EventThemeResponse, operation_id="create_event_theme")
|
|
def create_theme(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
theme_in: EventThemeCreate,
|
|
current_user: User = Depends(get_current_user),
|
|
storage: StorageProvider = Depends(get_storage_provider)
|
|
) -> EventThemeResponse:
|
|
|
|
if current_user is None or not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid authentication credentials",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
"""Create new event theme with proper file organization."""
|
|
# First create the theme to get an ID
|
|
theme = event_theme_crud.create(db, obj_in=theme_in)
|
|
|
|
# Keep track of files to move and URLs to update
|
|
url_updates = {}
|
|
|
|
# Move preview image if it exists
|
|
if theme.preview_image_url and 'uploads/' in theme.preview_image_url:
|
|
new_url = _relocate_theme_file(theme.id, theme.preview_image_url, 'preview', storage)
|
|
if new_url:
|
|
url_updates['preview_image_url'] = new_url
|
|
|
|
# Move background image if it exists
|
|
if theme.background_image_url and 'uploads/' in theme.background_image_url:
|
|
new_url = _relocate_theme_file(theme.id, theme.background_image_url, 'background', storage)
|
|
if new_url:
|
|
url_updates['background_image_url'] = new_url
|
|
|
|
# Move foreground image if it exists
|
|
if theme.foreground_image_url and 'uploads/' in theme.foreground_image_url:
|
|
new_url = _relocate_theme_file(theme.id, theme.foreground_image_url, 'foreground', storage)
|
|
if new_url:
|
|
url_updates['foreground_image_url'] = new_url
|
|
|
|
# Handle asset images if they exist
|
|
if theme.asset_image_urls:
|
|
new_assets = {}
|
|
for key, url in theme.asset_image_urls.items():
|
|
if url and 'uploads/' in url:
|
|
new_url = _relocate_theme_file(theme.id, url, f'assets/{key}', storage)
|
|
if new_url:
|
|
new_assets[key] = new_url
|
|
else:
|
|
new_assets[key] = url
|
|
else:
|
|
new_assets[key] = url
|
|
|
|
if new_assets:
|
|
url_updates['asset_image_urls'] = new_assets
|
|
|
|
# Update the theme if we relocated any files
|
|
if url_updates:
|
|
theme = event_theme_crud.update(db, db_obj=theme, obj_in=url_updates)
|
|
|
|
return theme
|
|
|
|
@router.get("/", response_model=List[EventThemeResponse], operation_id="list_event_themes")
|
|
def list_themes(
|
|
db: Session = Depends(get_db),
|
|
skip: int = 0,
|
|
limit: int = 100
|
|
) -> List[EventThemeResponse]:
|
|
"""List event themes."""
|
|
themes = event_theme_crud.get_multi(db, skip=skip, limit=limit)
|
|
return themes
|
|
|
|
|
|
@router.get("/{theme_id}", response_model=EventThemeResponse, operation_id="get_event_theme")
|
|
def get_theme(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
theme_id: UUID
|
|
) -> EventThemeResponse:
|
|
"""Get specific theme by ID."""
|
|
theme = event_theme_crud.get(db, id=theme_id)
|
|
if not theme:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Theme not found"
|
|
)
|
|
return theme
|
|
|
|
|
|
@router.patch("/{theme_id}", response_model=EventThemeResponse, operation_id="update_event_theme")
|
|
def update_theme(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
theme_id: UUID,
|
|
theme_in: EventThemeUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
storage: StorageProvider = Depends(get_storage_provider)
|
|
) -> EventThemeResponse:
|
|
|
|
if current_user is None or not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid authentication credentials",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
"""Update specific theme by ID with proper file organization."""
|
|
# Get the existing theme
|
|
theme = event_theme_crud.get(db, id=theme_id)
|
|
if not theme:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Theme not found"
|
|
)
|
|
|
|
# Create a mutable copy of theme_in data
|
|
update_data = theme_in.model_dump(exclude_unset=True)
|
|
|
|
# Relocate any image files and update URLs
|
|
if 'preview_image_url' in update_data and update_data['preview_image_url'] and 'uploads/' in update_data['preview_image_url']:
|
|
new_url = _relocate_theme_file(theme_id, update_data['preview_image_url'], 'preview', storage)
|
|
if new_url:
|
|
update_data['preview_image_url'] = new_url
|
|
|
|
if 'background_image_url' in update_data and update_data['background_image_url'] and 'uploads/' in update_data['background_image_url']:
|
|
new_url = _relocate_theme_file(theme_id, update_data['background_image_url'], 'background', storage)
|
|
if new_url:
|
|
update_data['background_image_url'] = new_url
|
|
|
|
if 'foreground_image_url' in update_data and update_data['foreground_image_url'] and 'uploads/' in update_data['foreground_image_url']:
|
|
new_url = _relocate_theme_file(theme_id, update_data['foreground_image_url'], 'foreground', storage)
|
|
if new_url:
|
|
update_data['foreground_image_url'] = new_url
|
|
|
|
if 'asset_image_urls' in update_data and update_data['asset_image_urls']:
|
|
new_assets = {}
|
|
for key, url in update_data['asset_image_urls'].items():
|
|
if url and 'uploads/' in url:
|
|
new_url = _relocate_theme_file(theme_id, url, f'assets/{key}', storage)
|
|
if new_url:
|
|
new_assets[key] = new_url
|
|
else:
|
|
new_assets[key] = url
|
|
else:
|
|
new_assets[key] = url
|
|
|
|
if new_assets:
|
|
update_data['asset_image_urls'] = new_assets
|
|
|
|
# Update the theme with the modified data
|
|
theme = event_theme_crud.update(db, db_obj=theme, obj_in=update_data)
|
|
return theme
|
|
|
|
|
|
|
|
@router.delete("/{theme_id}", operation_id="delete_event_theme")
|
|
def delete_theme(
|
|
*,
|
|
db: Session = Depends(get_db),
|
|
theme_id: UUID,
|
|
current_user: User = Depends(get_current_user),
|
|
hard_delete: bool = Query(False, description="Perform hard delete instead of soft delete")
|
|
|
|
):
|
|
"""Delete specific theme by ID."""
|
|
if current_user is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid authentication credentials",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
try:
|
|
event_theme_obj = event_theme_crud.get(db=db, id=theme_id)
|
|
if not event_theme_obj:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Event theme not found"
|
|
)
|
|
|
|
# Only creator or superuser can delete
|
|
if not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Not enough permissions to delete this event theme"
|
|
)
|
|
|
|
if hard_delete:
|
|
# Hard delete - only for superusers
|
|
if not current_user.is_superuser:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Only administrators can perform hard delete"
|
|
)
|
|
event_theme_crud.remove(db=db, id=theme_id)
|
|
else:
|
|
# Soft delete - set is_active to False
|
|
event_theme_crud.update(db=db, db_obj=event_theme_obj, obj_in={"is_active": False})
|
|
|
|
return None # 204 No Content
|
|
except SQLAlchemyError:
|
|
db.rollback()
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Error deleting event"
|
|
)
|