Files
HartOMat/backend/app/domains/media/models.py
T
Hartmut c74e118b98 feat(E): add MediaAsset catalog — model, CRUD API, MediaBrowser UI
Migration 040: media_assets table with RLS (tenant_isolation + admin_bypass).
domains/media/: MediaAsset model, schemas, service, router with ZIP-download.
publish_asset Celery task in rendering/tasks.py.
core/storage.py: download_bytes() method for MinIO + LocalStorage.
frontend: MediaBrowser.tsx (grid/list, multi-select, zip-download, pagination) + api/media.ts.
Route /media (AdminRoute) + sidebar link with Image icon for admin+pm.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 17:11:17 +01:00

53 lines
2.4 KiB
Python

import uuid
import enum
from datetime import datetime
from sqlalchemy import String, DateTime, Boolean, Text, BigInteger, Float, Integer, ForeignKey
from sqlalchemy import Enum as SAEnum
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.dialects.postgresql import UUID, JSONB
from app.database import Base
class MediaAssetType(str, enum.Enum):
thumbnail = "thumbnail"
still = "still"
turntable = "turntable"
stl_low = "stl_low"
stl_high = "stl_high"
gltf_geometry = "gltf_geometry"
gltf_production = "gltf_production"
blend_production = "blend_production"
class MediaAsset(Base):
__tablename__ = "media_assets"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=True, index=True
)
product_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), ForeignKey("products.id", ondelete="CASCADE"), nullable=True, index=True
)
cad_file_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), ForeignKey("cad_files.id", ondelete="SET NULL"), nullable=True
)
order_line_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), ForeignKey("order_lines.id", ondelete="SET NULL"), nullable=True, index=True
)
workflow_run_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), ForeignKey("workflow_runs.id", ondelete="SET NULL"), nullable=True
)
asset_type: Mapped[MediaAssetType] = mapped_column(
SAEnum(MediaAssetType, name="media_asset_type"), nullable=False
)
storage_key: Mapped[str] = mapped_column(Text, nullable=False)
file_size_bytes: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
mime_type: Mapped[str | None] = mapped_column(String(100), nullable=True)
width: Mapped[int | None] = mapped_column(Integer, nullable=True)
height: Mapped[int | None] = mapped_column(Integer, nullable=True)
duration_s: Mapped[float | None] = mapped_column(Float, nullable=True)
render_config: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
is_archived: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)