From 463cfdc5cf18db9b27c934e4ea1ed6b3bc7f52f6 Mon Sep 17 00:00:00 2001 From: ajaysi Date: Sat, 9 May 2026 09:52:32 +0530 Subject: [PATCH] refactor(phase3-session-b1): extract upscale, control, social, health into sub-routers Extracted 4 endpoint groups into separate sub-router modules: - health.py: 1 endpoint (GET /health) - upscale.py: 1 endpoint (POST /upscale) - control.py: 2 endpoints (POST /control/process, GET /control/operations) - social.py: 2 endpoints (POST /social/optimize, GET /social/platforms/{platform}/formats) __init__.py now composes these sub-routers into the legacy router. All 33 routes preserved. No functional changes. --- backend/routers/image_studio/__init__.py | 16 +- backend/routers/image_studio/control.py | 64 ++++++++ backend/routers/image_studio/health.py | 21 +++ backend/routers/image_studio/social.py | 88 +++++++++++ backend/routers/image_studio/upscale.py | 40 +++++ backend/routers/image_studio_router.py | 182 ----------------------- 6 files changed, 226 insertions(+), 185 deletions(-) create mode 100644 backend/routers/image_studio/control.py create mode 100644 backend/routers/image_studio/health.py create mode 100644 backend/routers/image_studio/social.py create mode 100644 backend/routers/image_studio/upscale.py diff --git a/backend/routers/image_studio/__init__.py b/backend/routers/image_studio/__init__.py index a04d2aef..667be214 100644 --- a/backend/routers/image_studio/__init__.py +++ b/backend/routers/image_studio/__init__.py @@ -1,9 +1,19 @@ """Image Studio API router package. -Assembled from modular sub-routers. Same prefix and tags as the original monolithic file. -Currently re-exports from the legacy router. Sub-routers will be added in subsequent sessions. +Composed from modular sub-routers. Same prefix and tags as the original monolithic file. """ -from ..image_studio_router import router +from ..image_studio_router import router as legacy_router +from .health import router as health_router +from .upscale import router as upscale_router +from .control import router as control_router +from .social import router as social_router + +legacy_router.include_router(health_router) +legacy_router.include_router(upscale_router) +legacy_router.include_router(control_router) +legacy_router.include_router(social_router) + +router = legacy_router __all__ = ["router"] diff --git a/backend/routers/image_studio/control.py b/backend/routers/image_studio/control.py new file mode 100644 index 00000000..49409748 --- /dev/null +++ b/backend/routers/image_studio/control.py @@ -0,0 +1,64 @@ +"""Control Studio endpoints.""" + +from typing import Dict, Any +from fastapi import APIRouter, Depends, HTTPException + +from .models import ControlImageRequest, ControlImageResponse, ControlOperationsResponse +from .deps import get_studio_manager, _require_user_id +from services.image_studio import ImageStudioManager, ControlStudioRequest +from middleware.auth_middleware import get_current_user +from utils.logger_utils import get_service_logger + +logger = get_service_logger("api.image_studio") +router = APIRouter(tags=["image-studio"]) + + +@router.post("/control/process", response_model=ControlImageResponse, summary="Process Control Studio request") +async def process_control_image( + request: ControlImageRequest, + current_user: Dict[str, Any] = Depends(get_current_user), + studio_manager: ImageStudioManager = Depends(get_studio_manager), +): + """Perform Control Studio operations such as sketch-to-image, structure control, style control, and style transfer.""" + try: + user_id = _require_user_id(current_user, "image control") + logger.info(f"[Control Image] Request from user {user_id}: operation={request.operation}") + + control_request = ControlStudioRequest( + operation=request.operation, + prompt=request.prompt, + control_image_base64=request.control_image_base64, + style_image_base64=request.style_image_base64, + negative_prompt=request.negative_prompt, + control_strength=request.control_strength, + fidelity=request.fidelity, + style_strength=request.style_strength, + composition_fidelity=request.composition_fidelity, + change_strength=request.change_strength, + aspect_ratio=request.aspect_ratio, + style_preset=request.style_preset, + seed=request.seed, + output_format=request.output_format, + ) + + result = await studio_manager.control_image(control_request, user_id=user_id) + return ControlImageResponse(**result) + except HTTPException: + raise + except Exception as e: + logger.error(f"[Control Image] ❌ Error: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Image control failed: {e}") + + +@router.get("/control/operations", response_model=ControlOperationsResponse, summary="List Control Studio operations") +async def get_control_operations( + current_user: Dict[str, Any] = Depends(get_current_user), + studio_manager: ImageStudioManager = Depends(get_studio_manager), +): + """Return metadata for supported Control Studio operations.""" + try: + operations = studio_manager.get_control_operations() + return ControlOperationsResponse(operations=operations) + except Exception as e: + logger.error(f"[Control Operations] ❌ Error: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail="Failed to load control operations") diff --git a/backend/routers/image_studio/health.py b/backend/routers/image_studio/health.py new file mode 100644 index 00000000..45922fec --- /dev/null +++ b/backend/routers/image_studio/health.py @@ -0,0 +1,21 @@ +"""Health check endpoint.""" + +from fastapi import APIRouter + +router = APIRouter(tags=["image-studio"]) + + +@router.get("/health", summary="Health Check") +async def health_check(): + """Health check endpoint for Image Studio.""" + return { + "status": "healthy", + "service": "image_studio", + "version": "1.0.0", + "modules": { + "create_studio": "available", + "templates": "available", + "providers": "available", + "compression": "available", + } + } diff --git a/backend/routers/image_studio/social.py b/backend/routers/image_studio/social.py new file mode 100644 index 00000000..4eddb975 --- /dev/null +++ b/backend/routers/image_studio/social.py @@ -0,0 +1,88 @@ +"""Social Optimizer endpoints.""" + +from typing import Dict, Any +from fastapi import APIRouter, Depends, HTTPException + +from .models import SocialOptimizeRequest, SocialOptimizeResponse, PlatformFormatsResponse +from .deps import get_studio_manager, _require_user_id +from services.image_studio import ImageStudioManager, SocialOptimizerRequest +from services.image_studio.templates import Platform +from middleware.auth_middleware import get_current_user +from utils.logger_utils import get_service_logger + +logger = get_service_logger("api.image_studio") +router = APIRouter(tags=["image-studio"]) + + +@router.post("/social/optimize", response_model=SocialOptimizeResponse, summary="Optimize image for social platforms") +async def optimize_for_social( + request: SocialOptimizeRequest, + current_user: Dict[str, Any] = Depends(get_current_user), + studio_manager: ImageStudioManager = Depends(get_studio_manager), +): + """Optimize an image for multiple social media platforms with smart cropping and safe zones.""" + try: + user_id = _require_user_id(current_user, "social optimization") + logger.info(f"[Social Optimizer] Request from user {user_id}: platforms={request.platforms}") + + platforms = [] + for platform_str in request.platforms: + try: + platforms.append(Platform(platform_str.lower())) + except ValueError: + logger.warning(f"[Social Optimizer] Invalid platform: {platform_str}") + continue + + if not platforms: + raise HTTPException(status_code=400, detail="No valid platforms provided") + + format_names = None + if request.format_names: + format_names = {} + for platform_str, format_name in request.format_names.items(): + try: + platform = Platform(platform_str.lower()) + format_names[platform] = format_name + except ValueError: + logger.warning(f"[Social Optimizer] Invalid platform in format_names: {platform_str}") + + social_request = SocialOptimizerRequest( + image_base64=request.image_base64, + platforms=platforms, + format_names=format_names, + show_safe_zones=request.show_safe_zones, + crop_mode=request.crop_mode, + focal_point=request.focal_point, + output_format=request.output_format, + options={}, + ) + + result = await studio_manager.optimize_for_social(social_request, user_id=user_id) + return SocialOptimizeResponse(**result) + except HTTPException: + raise + except Exception as e: + logger.error(f"[Social Optimizer] ❌ Error: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Social optimization failed: {e}") + + +@router.get("/social/platforms/{platform}/formats", response_model=PlatformFormatsResponse, summary="Get platform formats") +async def get_platform_formats( + platform: str, + current_user: Dict[str, Any] = Depends(get_current_user), + studio_manager: ImageStudioManager = Depends(get_studio_manager), +): + """Get available formats for a social media platform.""" + try: + try: + platform_enum = Platform(platform.lower()) + except ValueError: + raise HTTPException(status_code=400, detail=f"Invalid platform: {platform}") + + formats = studio_manager.get_social_platform_formats(platform_enum) + return PlatformFormatsResponse(formats=formats) + except HTTPException: + raise + except Exception as e: + logger.error(f"[Platform Formats] ❌ Error: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Failed to load platform formats: {e}") diff --git a/backend/routers/image_studio/upscale.py b/backend/routers/image_studio/upscale.py new file mode 100644 index 00000000..b3241816 --- /dev/null +++ b/backend/routers/image_studio/upscale.py @@ -0,0 +1,40 @@ +"""Upscale Studio endpoint.""" + +from typing import Dict, Any +from fastapi import APIRouter, Depends, HTTPException + +from .models import UpscaleImageRequest, UpscaleImageResponse +from .deps import get_studio_manager, _require_user_id +from services.image_studio import ImageStudioManager +from services.image_studio.upscale_service import UpscaleStudioRequest +from middleware.auth_middleware import get_current_user +from utils.logger_utils import get_service_logger + +logger = get_service_logger("api.image_studio") +router = APIRouter(tags=["image-studio"]) + + +@router.post("/upscale", response_model=UpscaleImageResponse, summary="Upscale Image") +async def upscale_image( + request: UpscaleImageRequest, + current_user: Dict[str, Any] = Depends(get_current_user), + studio_manager: ImageStudioManager = Depends(get_studio_manager), +): + """Upscale an image using Stability AI pipelines.""" + try: + user_id = _require_user_id(current_user, "image upscaling") + upscale_request = UpscaleStudioRequest( + image_base64=request.image_base64, + mode=request.mode, + target_width=request.target_width, + target_height=request.target_height, + preset=request.preset, + prompt=request.prompt, + ) + result = await studio_manager.upscale_image(upscale_request, user_id=user_id) + return UpscaleImageResponse(**result) + except HTTPException: + raise + except Exception as e: + logger.error(f"[Upscale Image] ❌ Error: {e}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Image upscaling failed: {e}") diff --git a/backend/routers/image_studio_router.py b/backend/routers/image_studio_router.py index 34cc51de..29c9b274 100644 --- a/backend/routers/image_studio_router.py +++ b/backend/routers/image_studio_router.py @@ -525,171 +525,10 @@ async def recommend_face_swap_model( raise HTTPException(status_code=500, detail=f"Failed to get recommendation: {e}") -# ==================== -# UPSCALE STUDIO ENDPOINTS -# ==================== - -@router.post("/upscale", response_model=UpscaleImageResponse, summary="Upscale Image") -async def upscale_image( - request: UpscaleImageRequest, - current_user: Dict[str, Any] = Depends(get_current_user), - studio_manager: ImageStudioManager = Depends(get_studio_manager), -): - """Upscale an image using Stability AI pipelines.""" - try: - user_id = _require_user_id(current_user, "image upscaling") - upscale_request = UpscaleStudioRequest( - image_base64=request.image_base64, - mode=request.mode, - target_width=request.target_width, - target_height=request.target_height, - preset=request.preset, - prompt=request.prompt, - ) - result = await studio_manager.upscale_image(upscale_request, user_id=user_id) - return UpscaleImageResponse(**result) - except HTTPException: - raise - except Exception as e: - logger.error(f"[Upscale Image] ❌ Error: {e}", exc_info=True) - raise HTTPException(status_code=500, detail=f"Image upscaling failed: {e}") -# ==================== -# CONTROL STUDIO ENDPOINTS -# ==================== - -@router.post("/control/process", response_model=ControlImageResponse, summary="Process Control Studio request") -async def process_control_image( - request: ControlImageRequest, - current_user: Dict[str, Any] = Depends(get_current_user), - studio_manager: ImageStudioManager = Depends(get_studio_manager), -): - """Perform Control Studio operations such as sketch-to-image, structure control, style control, and style transfer.""" - try: - user_id = _require_user_id(current_user, "image control") - logger.info(f"[Control Image] Request from user {user_id}: operation={request.operation}") - - control_request = ControlStudioRequest( - operation=request.operation, - prompt=request.prompt, - control_image_base64=request.control_image_base64, - style_image_base64=request.style_image_base64, - negative_prompt=request.negative_prompt, - control_strength=request.control_strength, - fidelity=request.fidelity, - style_strength=request.style_strength, - composition_fidelity=request.composition_fidelity, - change_strength=request.change_strength, - aspect_ratio=request.aspect_ratio, - style_preset=request.style_preset, - seed=request.seed, - output_format=request.output_format, - ) - - result = await studio_manager.control_image(control_request, user_id=user_id) - return ControlImageResponse(**result) - except HTTPException: - raise - except Exception as e: - logger.error(f"[Control Image] ❌ Error: {str(e)}", exc_info=True) - raise HTTPException(status_code=500, detail=f"Image control failed: {e}") -@router.get("/control/operations", response_model=ControlOperationsResponse, summary="List Control Studio operations") -async def get_control_operations( - current_user: Dict[str, Any] = Depends(get_current_user), - studio_manager: ImageStudioManager = Depends(get_studio_manager), -): - """Return metadata for supported Control Studio operations.""" - try: - operations = studio_manager.get_control_operations() - return ControlOperationsResponse(operations=operations) - except Exception as e: - logger.error(f"[Control Operations] ❌ Error: {str(e)}", exc_info=True) - raise HTTPException(status_code=500, detail="Failed to load control operations") - - -# ==================== -# SOCIAL OPTIMIZER ENDPOINTS -# ==================== - -@router.post("/social/optimize", response_model=SocialOptimizeResponse, summary="Optimize image for social platforms") -async def optimize_for_social( - request: SocialOptimizeRequest, - current_user: Dict[str, Any] = Depends(get_current_user), - studio_manager: ImageStudioManager = Depends(get_studio_manager), -): - """Optimize an image for multiple social media platforms with smart cropping and safe zones.""" - try: - user_id = _require_user_id(current_user, "social optimization") - logger.info(f"[Social Optimizer] Request from user {user_id}: platforms={request.platforms}") - - # Convert platform strings to Platform enum - from services.image_studio.templates import Platform - platforms = [] - for platform_str in request.platforms: - try: - platforms.append(Platform(platform_str.lower())) - except ValueError: - logger.warning(f"[Social Optimizer] Invalid platform: {platform_str}") - continue - - if not platforms: - raise HTTPException(status_code=400, detail="No valid platforms provided") - - # Convert format_names dict keys to Platform enum - format_names = None - if request.format_names: - format_names = {} - for platform_str, format_name in request.format_names.items(): - try: - platform = Platform(platform_str.lower()) - format_names[platform] = format_name - except ValueError: - logger.warning(f"[Social Optimizer] Invalid platform in format_names: {platform_str}") - - social_request = SocialOptimizerRequest( - image_base64=request.image_base64, - platforms=platforms, - format_names=format_names, - show_safe_zones=request.show_safe_zones, - crop_mode=request.crop_mode, - focal_point=request.focal_point, - output_format=request.output_format, - options={}, - ) - - result = await studio_manager.optimize_for_social(social_request, user_id=user_id) - return SocialOptimizeResponse(**result) - except HTTPException: - raise - except Exception as e: - logger.error(f"[Social Optimizer] ❌ Error: {str(e)}", exc_info=True) - raise HTTPException(status_code=500, detail=f"Social optimization failed: {e}") - - -@router.get("/social/platforms/{platform}/formats", response_model=PlatformFormatsResponse, summary="Get platform formats") -async def get_platform_formats( - platform: str, - current_user: Dict[str, Any] = Depends(get_current_user), - studio_manager: ImageStudioManager = Depends(get_studio_manager), -): - """Get available formats for a social media platform.""" - try: - from services.image_studio.templates import Platform - try: - platform_enum = Platform(platform.lower()) - except ValueError: - raise HTTPException(status_code=400, detail=f"Invalid platform: {platform}") - - formats = studio_manager.get_social_platform_formats(platform_enum) - return PlatformFormatsResponse(formats=formats) - except HTTPException: - raise - except Exception as e: - logger.error(f"[Platform Formats] ❌ Error: {str(e)}", exc_info=True) - raise HTTPException(status_code=500, detail=f"Failed to load platform formats: {e}") # ==================== @@ -1203,26 +1042,5 @@ async def get_format_recommendations( return FormatRecommendationsResponse(recommendations=recommendations) -# ==================== -# HEALTH CHECK -# ==================== -@router.get("/health", summary="Health Check") -async def health_check(): - """Health check endpoint for Image Studio. - - Returns: - Health status - """ - return { - "status": "healthy", - "service": "image_studio", - "version": "1.0.0", - "modules": { - "create_studio": "available", - "templates": "available", - "providers": "available", - "compression": "available", - } - }