Files
platform/services/media/app/models.py
Yusuf Suleman 69af4b84a5
All checks were successful
Security Checks / dependency-audit (push) Successful in 12s
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dockerfile-lint (push) Successful in 3s
feat: rebuild iOS app from API audit + new podcast/media service
iOS App (complete rebuild):
- Audited all fitness API endpoints against live responses
- Models match exact API field names (snapshot_ prefixes, UUID strings)
- FoodEntry uses computed properties (foodName, calories, etc.) wrapping snapshot fields
- Flexible Int/Double decoding for all numeric fields
- AI assistant with raw JSON state management (JSONSerialization, not Codable)
- Home dashboard with custom background, frosted glass calorie widget
- Fitness: Today/Templates/Goals/Foods tabs
- Food search with recent + all sections
- Meal sections with colored accent bars, swipe to delete
- 120fps ProMotion, iOS 17+ @Observable

Podcast/Media Service:
- FastAPI backend for podcast RSS + local audiobook folders
- Shows, episodes, playback progress, queue management
- RSS feed fetching with feedparser + ETag support
- Local folder scanning with mutagen for audio metadata
- HTTP Range streaming for local audio files
- Playback events logging (play/pause/seek/complete)
- Reuses brain's PostgreSQL + Redis
- media_ prefixed tables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 02:36:43 -05:00

110 lines
4.0 KiB
Python

"""SQLAlchemy models for the media service."""
import uuid
from datetime import datetime
from sqlalchemy import (
Column, String, Text, Integer, BigInteger, Boolean, Float,
DateTime, ForeignKey, Index, UniqueConstraint,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.database import Base
class Show(Base):
__tablename__ = "media_shows"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(String(64), nullable=False, index=True)
title = Column(String(500), nullable=False)
author = Column(String(500))
description = Column(Text)
artwork_url = Column(Text)
feed_url = Column(Text)
local_path = Column(Text)
show_type = Column(String(20), nullable=False, default="podcast")
etag = Column(String(255))
last_modified = Column(String(255))
last_fetched_at = Column(DateTime)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
episodes = relationship("Episode", back_populates="show", cascade="all, delete-orphan")
class Episode(Base):
__tablename__ = "media_episodes"
__table_args__ = (
UniqueConstraint("show_id", "guid", name="uq_media_episodes_show_guid"),
Index("idx_media_episodes_show", "show_id"),
Index("idx_media_episodes_published", "published_at"),
)
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
show_id = Column(UUID(as_uuid=True), ForeignKey("media_shows.id", ondelete="CASCADE"))
user_id = Column(String(64), nullable=False)
title = Column(String(1000))
description = Column(Text)
audio_url = Column(Text)
duration_seconds = Column(Integer)
file_size_bytes = Column(BigInteger)
published_at = Column(DateTime)
guid = Column(String(500))
artwork_url = Column(Text)
is_downloaded = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
show = relationship("Show", back_populates="episodes")
progress = relationship("Progress", back_populates="episode", uselist=False, cascade="all, delete-orphan")
class Progress(Base):
__tablename__ = "media_progress"
__table_args__ = (
UniqueConstraint("user_id", "episode_id", name="uq_media_progress_user_episode"),
Index("idx_media_progress_user", "user_id"),
)
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(String(64), nullable=False)
episode_id = Column(UUID(as_uuid=True), ForeignKey("media_episodes.id", ondelete="CASCADE"))
position_seconds = Column(Float, default=0)
duration_seconds = Column(Integer)
is_completed = Column(Boolean, default=False)
playback_speed = Column(Float, default=1.0)
last_played_at = Column(DateTime, default=datetime.utcnow)
episode = relationship("Episode", back_populates="progress")
class QueueItem(Base):
__tablename__ = "media_queue"
__table_args__ = (
UniqueConstraint("user_id", "episode_id", name="uq_media_queue_user_episode"),
Index("idx_media_queue_user_order", "user_id", "sort_order"),
)
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(String(64), nullable=False)
episode_id = Column(UUID(as_uuid=True), ForeignKey("media_episodes.id", ondelete="CASCADE"))
sort_order = Column(Integer, nullable=False, default=0)
added_at = Column(DateTime, default=datetime.utcnow)
episode = relationship("Episode")
class PlaybackEvent(Base):
__tablename__ = "media_playback_events"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(String(64), nullable=False)
episode_id = Column(UUID(as_uuid=True), ForeignKey("media_episodes.id", ondelete="CASCADE"))
event_type = Column(String(20), nullable=False)
position_seconds = Column(Float)
playback_speed = Column(Float, default=1.0)
created_at = Column(DateTime, default=datetime.utcnow)
episode = relationship("Episode")