Files
platform/services/media/app/main.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

88 lines
2.4 KiB
Python

"""Media service — FastAPI entrypoint."""
import logging
from fastapi import FastAPI, Depends
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import DEBUG, PORT
logging.basicConfig(
level=logging.DEBUG if DEBUG else logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
app = FastAPI(
title="Media Service",
description="Podcast and local audio management with playback tracking.",
version="1.0.0",
docs_url="/api/docs" if DEBUG else None,
redoc_url=None,
)
@app.on_event("startup")
async def startup():
from app.database import engine, Base
from app.models import Show, Episode, Progress, QueueItem, PlaybackEvent # noqa
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
logging.getLogger(__name__).info("Media service started on port %s", PORT)
# Register routers
from app.api.shows import router as shows_router
from app.api.episodes import router as episodes_router
from app.api.playback import router as playback_router
from app.api.queue import router as queue_router
app.include_router(shows_router)
app.include_router(episodes_router)
app.include_router(playback_router)
app.include_router(queue_router)
@app.get("/api/health")
async def health():
return {"status": "ok", "service": "media"}
from app.api.deps import get_user_id, get_db_session
@app.get("/api/stats")
async def get_stats(
user_id: str = Depends(get_user_id),
db: AsyncSession = Depends(get_db_session),
):
from app.models import Show, Episode, Progress
total_shows = await db.scalar(
select(func.count(Show.id)).where(Show.user_id == user_id)
)
total_episodes = await db.scalar(
select(func.count(Episode.id)).where(Episode.user_id == user_id)
)
total_seconds = await db.scalar(
select(func.coalesce(func.sum(Progress.position_seconds), 0)).where(
Progress.user_id == user_id,
)
)
in_progress = await db.scalar(
select(func.count(Progress.id)).where(
Progress.user_id == user_id,
Progress.position_seconds > 0,
Progress.is_completed == False, # noqa: E712
)
)
return {
"total_shows": total_shows or 0,
"total_episodes": total_episodes or 0,
"total_listened_hours": round((total_seconds or 0) / 3600, 1),
"in_progress": in_progress or 0,
}