# Media Management Architecture

This document describes the consumer-agnostic media architecture used in the platform.

## Overview

```
┌─────────────────────────────────────────────────────────────────────┐
│                        CONSUMER MODULES                              │
│                                                                      │
│   ┌─────────────┐    ┌─────────────┐    ┌─────────────┐            │
│   │   Catalog   │    │ Art Gallery │    │   Future    │            │
│   │             │    │  (future)   │    │   Module    │            │
│   │ ProductMedia│    │ GalleryMedia│    │ XxxMedia    │            │
│   └──────┬──────┘    └──────┬──────┘    └──────┬──────┘            │
│          │                  │                  │                    │
│          └──────────────────┼──────────────────┘                    │
│                             │                                       │
│                             ▼                                       │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │                      CMS Module                              │  │
│   │                                                              │  │
│   │   MediaFile (generic, consumer-agnostic storage)            │  │
│   │   MediaService (upload, download, metadata management)       │  │
│   └─────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘
```

## Design Principles

### 1. Consumer-Agnostic Storage

The CMS module provides **generic media storage** without knowing what entities will use the media files. This means:

- `MediaFile` stores file metadata (path, size, type, dimensions)
- `MediaService` handles upload, download, and file operations
- CMS has **no knowledge** of products, galleries, or any specific consumers

### 2. Consumer-Owned Associations

Each module that needs media defines its **own association table**:

- **Catalog**: `ProductMedia` links products to media files
- **Future Art Gallery**: Would define `GalleryMedia`
- **Future Blog**: Would define `PostMedia`

This follows the principle: **The consumer knows what it needs, the provider doesn't need to know who uses it.**

### 3. Correct Dependency Direction

```
WRONG (Hidden Dependency):
  CMS → Catalog (CMS knows about Product)

CORRECT:
  Catalog → CMS (Catalog uses MediaFile)
```

Optional modules (catalog) depend on core modules (cms), never the reverse.

## Key Components

### MediaFile Model (CMS)

```python
# app/modules/cms/models/media.py

class MediaFile(Base, TimestampMixin):
    """Generic store media file - consumer-agnostic."""

    __tablename__ = "media_files"

    id = Column(Integer, primary_key=True)
    store_id = Column(Integer, ForeignKey("stores.id"), nullable=False)

    # File identification
    filename = Column(String(255), nullable=False)  # UUID-based
    original_filename = Column(String(255))
    file_path = Column(String(500), nullable=False)

    # File properties
    media_type = Column(String(20), nullable=False)  # image, video, document
    mime_type = Column(String(100))
    file_size = Column(Integer)

    # Image/video dimensions
    width = Column(Integer)
    height = Column(Integer)

    # Metadata
    alt_text = Column(String(500))
    description = Column(Text)
    folder = Column(String(100), default="general")

    # Usage tracking (updated by consumers)
    usage_count = Column(Integer, default=0)

    # Note: Consumer-specific associations (ProductMedia, etc.) are defined
    # in their respective modules. CMS doesn't know about specific consumers.
```

### MediaService (CMS)

The `MediaService` provides generic operations:

```python
# app/modules/cms/services/media_service.py

class MediaService:
    """Generic media operations - consumer-agnostic."""

    async def upload_file(self, db, store_id, file_content, filename, folder="general"):
        """Upload a file to store's media library."""
        ...

    def get_media(self, db, store_id, media_id):
        """Get a media file by ID."""
        ...

    def get_media_library(self, db, store_id, skip=0, limit=100, **filters):
        """List store's media files with filtering."""
        ...

    def update_media_metadata(self, db, store_id, media_id, **metadata):
        """Update file metadata (alt_text, description, etc.)."""
        ...

    def delete_media(self, db, store_id, media_id):
        """Delete a media file."""
        ...
```

### ProductMedia Model (Catalog)

```python
# app/modules/catalog/models/product_media.py

class ProductMedia(Base, TimestampMixin):
    """Product-specific media association - owned by catalog."""

    __tablename__ = "product_media"

    id = Column(Integer, primary_key=True)
    product_id = Column(Integer, ForeignKey("products.id", ondelete="CASCADE"))
    media_id = Column(Integer, ForeignKey("media_files.id", ondelete="CASCADE"))

    # Usage type: main_image, gallery, variant, thumbnail, swatch
    usage_type = Column(String(50), nullable=False, default="gallery")
    display_order = Column(Integer, default=0)
    variant_id = Column(Integer)  # For variant-specific images

    # Relationships
    product = relationship("Product", back_populates="media_associations")
    media = relationship("MediaFile", lazy="joined")
```

### ProductMediaService (Catalog)

```python
# app/modules/catalog/services/product_media_service.py

class ProductMediaService:
    """Product-media association operations - catalog-specific."""

    def attach_media_to_product(self, db, store_id, product_id, media_id,
                                 usage_type="gallery", display_order=0):
        """Attach a media file to a product."""
        # Verify ownership, create ProductMedia association
        ...

    def detach_media_from_product(self, db, store_id, product_id, media_id,
                                   usage_type=None):
        """Detach media from a product."""
        ...

    def get_product_media(self, db, product_id, usage_type=None):
        """Get media associations for a product."""
        ...

    def set_main_image(self, db, store_id, product_id, media_id):
        """Set the main image for a product."""
        ...
```

## Adding Media Support to a New Module

When creating a new module that needs media (e.g., an art gallery module):

### Step 1: Create Your Association Model

```python
# app/modules/art_gallery/models/gallery_media.py

from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from app.core.database import Base

class GalleryMedia(Base):
    """Gallery-specific media association."""

    __tablename__ = "gallery_media"

    id = Column(Integer, primary_key=True)
    artwork_id = Column(Integer, ForeignKey("artworks.id", ondelete="CASCADE"))
    media_id = Column(Integer, ForeignKey("media_files.id", ondelete="CASCADE"))

    # Gallery-specific fields
    is_primary = Column(Boolean, default=False)
    caption = Column(String(500))

    # Relationships
    artwork = relationship("Artwork", back_populates="media_associations")
    media = relationship("MediaFile", lazy="joined")
```

### Step 2: Create Your Service

```python
# app/modules/art_gallery/services/gallery_media_service.py

from app.modules.cms.models import MediaFile
from app.modules.art_gallery.models import GalleryMedia, Artwork

class GalleryMediaService:
    def attach_media_to_artwork(self, db, artist_id, artwork_id, media_id, **kwargs):
        # Verify artwork belongs to artist
        # Verify media belongs to artist (store_id)
        # Create GalleryMedia association
        ...
```

### Step 3: Use CMS MediaService for File Operations

```python
from app.modules.cms.services.media_service import media_service

# Upload a new file
media_file = await media_service.upload_file(
    db=db,
    store_id=artist_id,
    file_content=file_bytes,
    filename="artwork.jpg",
    folder="artworks",
)

# Then attach it to your entity
gallery_media_service.attach_media_to_artwork(
    db=db,
    artist_id=artist_id,
    artwork_id=artwork.id,
    media_id=media_file.id,
    is_primary=True,
)
```

## Benefits of This Architecture

1. **Module Independence**: Catalog can be disabled without affecting CMS
2. **Extensibility**: New modules easily add media support
3. **No Hidden Dependencies**: Dependencies flow in one direction
4. **Clean Separation**: CMS handles storage, consumers handle associations
5. **Testability**: Can test CMS without any consumer modules
6. **Single Responsibility**: Each module owns its domain

## Anti-Patterns to Avoid

### Don't: Add Consumer References to MediaFile

```python
# BAD - Creates hidden dependency
class MediaFile(Base):
    # CMS now depends on catalog!
    product_associations = relationship("ProductMedia", ...)
```

### Don't: Put Consumer Logic in MediaService

```python
# BAD - CMS shouldn't know about products
class MediaService:
    def attach_to_product(self, db, product_id, media_id):
        from app.modules.catalog.models import ProductMedia  # Wrong!
```

### Don't: Query Consumer Models from CMS

```python
# BAD - Hidden dependency
def get_media_usage(self, db, media_id):
    from app.modules.catalog.models import ProductMedia  # Wrong!
    return db.query(ProductMedia).filter(...).all()
```

## Related Documentation

- [Module System Architecture](module-system.md) - Module structure and dependencies
- [Cross-Module Import Rules](cross-module-import-rules.md) - Import restrictions
- [Audit Provider Pattern](audit-provider-pattern.md) - Similar provider pattern for audit logging
