This commit completes the migration to a fully module-driven architecture: ## Models Migration - Moved all domain models from models/database/ to their respective modules: - tenancy: User, Admin, Vendor, Company, Platform, VendorDomain, etc. - cms: MediaFile, VendorTheme - messaging: Email, VendorEmailSettings, VendorEmailTemplate - core: AdminMenuConfig - models/database/ now only contains Base and TimestampMixin (infrastructure) ## Schemas Migration - Moved all domain schemas from models/schema/ to their respective modules: - tenancy: company, vendor, admin, team, vendor_domain - cms: media, image, vendor_theme - messaging: email - models/schema/ now only contains base.py and auth.py (infrastructure) ## Routes Migration - Moved admin routes from app/api/v1/admin/ to modules: - menu_config.py -> core module - modules.py -> tenancy module - module_config.py -> tenancy module - app/api/v1/admin/ now only aggregates auto-discovered module routes ## Menu System - Implemented module-driven menu system with MenuDiscoveryService - Extended FrontendType enum: PLATFORM, ADMIN, VENDOR, STOREFRONT - Added MenuItemDefinition and MenuSectionDefinition dataclasses - Each module now defines its own menu items in definition.py - MenuService integrates with MenuDiscoveryService for template rendering ## Documentation - Updated docs/architecture/models-structure.md - Updated docs/architecture/menu-management.md - Updated architecture validation rules for new exceptions ## Architecture Validation - Updated MOD-019 rule to allow base.py in models/schema/ - Created core module exceptions.py and schemas/ directory - All validation errors resolved (only warnings remain) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
139 lines
4.2 KiB
Python
139 lines
4.2 KiB
Python
# app/modules/cms/routes/api/admin_media.py
|
|
"""
|
|
Admin media management endpoints for vendor media libraries.
|
|
|
|
Allows admins to manage media files on behalf of vendors.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends, File, Query, UploadFile
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.api.deps import get_current_admin_api
|
|
from app.core.database import get_db
|
|
from app.modules.cms.services.media_service import media_service
|
|
from models.schema.auth import UserContext
|
|
from app.modules.cms.schemas.media import (
|
|
MediaDetailResponse,
|
|
MediaItemResponse,
|
|
MediaListResponse,
|
|
MediaUploadResponse,
|
|
)
|
|
|
|
admin_media_router = APIRouter(prefix="/media")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@admin_media_router.get("/vendors/{vendor_id}", response_model=MediaListResponse)
|
|
def get_vendor_media_library(
|
|
vendor_id: int,
|
|
skip: int = Query(0, ge=0),
|
|
limit: int = Query(100, ge=1, le=1000),
|
|
media_type: str | None = Query(None, description="image, video, document"),
|
|
folder: str | None = Query(None, description="Filter by folder"),
|
|
search: str | None = Query(None),
|
|
current_admin: UserContext = Depends(get_current_admin_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Get media library for a specific vendor.
|
|
|
|
Admin can browse any vendor's media library.
|
|
"""
|
|
media_files, total = media_service.get_media_library(
|
|
db=db,
|
|
vendor_id=vendor_id,
|
|
skip=skip,
|
|
limit=limit,
|
|
media_type=media_type,
|
|
folder=folder,
|
|
search=search,
|
|
)
|
|
|
|
return MediaListResponse(
|
|
media=[MediaItemResponse.model_validate(m) for m in media_files],
|
|
total=total,
|
|
skip=skip,
|
|
limit=limit,
|
|
)
|
|
|
|
|
|
@admin_media_router.post("/vendors/{vendor_id}/upload", response_model=MediaUploadResponse)
|
|
async def upload_vendor_media(
|
|
vendor_id: int,
|
|
file: UploadFile = File(...),
|
|
folder: str | None = Query("products", description="products, general, etc."),
|
|
current_admin: UserContext = Depends(get_current_admin_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Upload media file for a specific vendor.
|
|
|
|
Admin can upload media on behalf of any vendor.
|
|
Files are stored in vendor-specific directories.
|
|
"""
|
|
# Read file content
|
|
file_content = await file.read()
|
|
|
|
# Upload using service
|
|
media_file = await media_service.upload_file(
|
|
db=db,
|
|
vendor_id=vendor_id,
|
|
file_content=file_content,
|
|
filename=file.filename or "unnamed",
|
|
folder=folder or "products",
|
|
)
|
|
|
|
logger.info(f"Admin uploaded media for vendor {vendor_id}: {media_file.id}")
|
|
|
|
return MediaUploadResponse(
|
|
success=True,
|
|
message="File uploaded successfully",
|
|
media=MediaItemResponse.model_validate(media_file),
|
|
)
|
|
|
|
|
|
@admin_media_router.get("/vendors/{vendor_id}/{media_id}", response_model=MediaDetailResponse)
|
|
def get_vendor_media_detail(
|
|
vendor_id: int,
|
|
media_id: int,
|
|
current_admin: UserContext = Depends(get_current_admin_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Get detailed info about a specific media file.
|
|
"""
|
|
media_file = media_service.get_media_by_id(db=db, media_id=media_id)
|
|
|
|
# Verify media belongs to the specified vendor
|
|
if media_file.vendor_id != vendor_id:
|
|
from app.modules.cms.exceptions import MediaNotFoundException
|
|
raise MediaNotFoundException(media_id)
|
|
|
|
return MediaDetailResponse.model_validate(media_file)
|
|
|
|
|
|
@admin_media_router.delete("/vendors/{vendor_id}/{media_id}")
|
|
def delete_vendor_media(
|
|
vendor_id: int,
|
|
media_id: int,
|
|
current_admin: UserContext = Depends(get_current_admin_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Delete a media file for a vendor.
|
|
"""
|
|
media_file = media_service.get_media_by_id(db=db, media_id=media_id)
|
|
|
|
# Verify media belongs to the specified vendor
|
|
if media_file.vendor_id != vendor_id:
|
|
from app.modules.cms.exceptions import MediaNotFoundException
|
|
raise MediaNotFoundException(media_id)
|
|
|
|
media_service.delete_media(db=db, media_id=media_id)
|
|
|
|
logger.info(f"Admin deleted media {media_id} for vendor {vendor_id}")
|
|
|
|
return {"success": True, "message": "Media deleted successfully"}
|