Files
media-map/backend/app/api/watched.py
Danilo Reyes 96fcc2b9e8 init
2025-12-28 20:59:09 -06:00

209 lines
6.7 KiB
Python

"""Watched items API endpoints"""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
from uuid import UUID
from app.core.database import pool
import json
router = APIRouter()
class WatchedItemCreate(BaseModel):
media_type: str # "movie" or "show"
title: str
year: Optional[int] = None
country_code: str
watched_at: Optional[datetime] = None
notes: Optional[str] = None
class WatchedItemUpdate(BaseModel):
title: Optional[str] = None
year: Optional[int] = None
country_code: Optional[str] = None
watched_at: Optional[datetime] = None
notes: Optional[str] = None
@router.get("/summary")
async def get_watched_summary():
"""Get watched items summary by country"""
# Pool should be initialized on startup
if not pool:
from app.core.database import init_db
await init_db()
async with pool.connection() as conn:
async with conn.cursor() as cur:
query = """
SELECT
country_code,
media_type,
COUNT(*) as count
FROM moviemap.watched_item
WHERE watched_at IS NOT NULL
GROUP BY country_code, media_type
ORDER BY country_code, media_type
"""
await cur.execute(query)
rows = await cur.fetchall()
result = {}
for row in rows:
country_code, media_type, count = row
if country_code not in result:
result[country_code] = {}
result[country_code][media_type] = count
return result
@router.get("")
async def list_watched_items():
"""List all watched items"""
# Pool should be initialized on startup
if not pool:
from app.core.database import init_db
await init_db()
async with pool.connection() as conn:
async with conn.cursor() as cur:
query = """
SELECT
id, media_type, title, year, country_code,
watched_at, notes, created_at, updated_at
FROM moviemap.watched_item
ORDER BY created_at DESC
"""
await cur.execute(query)
rows = await cur.fetchall()
items = []
for row in rows:
items.append({
"id": str(row[0]),
"media_type": row[1],
"title": row[2],
"year": row[3],
"country_code": row[4],
"watched_at": row[5].isoformat() if row[5] else None,
"notes": row[6],
"created_at": row[7].isoformat() if row[7] else None,
"updated_at": row[8].isoformat() if row[8] else None,
})
return items
@router.post("")
async def create_watched_item(item: WatchedItemCreate):
"""Create a new watched item"""
# Pool should be initialized on startup
if not pool:
from app.core.database import init_db
await init_db()
if item.media_type not in ["movie", "show"]:
raise HTTPException(status_code=400, detail="media_type must be 'movie' or 'show'")
async with pool.connection() as conn:
async with conn.cursor() as cur:
query = """
INSERT INTO moviemap.watched_item
(media_type, title, year, country_code, watched_at, notes)
VALUES (%s, %s, %s, %s, %s, %s)
RETURNING id
"""
await cur.execute(
query,
(
item.media_type,
item.title,
item.year,
item.country_code,
item.watched_at,
item.notes,
)
)
result = await cur.fetchone()
await conn.commit()
return {"id": str(result[0]), "status": "created"}
@router.patch("/{item_id}")
async def update_watched_item(item_id: UUID, item: WatchedItemUpdate):
"""Update a watched item"""
# Pool should be initialized on startup
if not pool:
from app.core.database import init_db
await init_db()
async with pool.connection() as conn:
async with conn.cursor() as cur:
# Build dynamic update query
updates = []
params = []
if item.title is not None:
updates.append("title = %s")
params.append(item.title)
if item.year is not None:
updates.append("year = %s")
params.append(item.year)
if item.country_code is not None:
updates.append("country_code = %s")
params.append(item.country_code)
if item.watched_at is not None:
updates.append("watched_at = %s")
params.append(item.watched_at)
if item.notes is not None:
updates.append("notes = %s")
params.append(item.notes)
if not updates:
raise HTTPException(status_code=400, detail="No fields to update")
updates.append("updated_at = NOW()")
params.append(str(item_id))
query = f"""
UPDATE moviemap.watched_item
SET {', '.join(updates)}
WHERE id = %s
RETURNING id
"""
await cur.execute(query, params)
result = await cur.fetchone()
await conn.commit()
if not result:
raise HTTPException(status_code=404, detail="Watched item not found")
return {"id": str(result[0]), "status": "updated"}
@router.delete("/{item_id}")
async def delete_watched_item(item_id: UUID):
"""Delete a watched item"""
# Pool should be initialized on startup
if not pool:
from app.core.database import init_db
await init_db()
async with pool.connection() as conn:
async with conn.cursor() as cur:
query = "DELETE FROM moviemap.watched_item WHERE id = %s RETURNING id"
await cur.execute(query, (str(item_id),))
result = await cur.fetchone()
await conn.commit()
if not result:
raise HTTPException(status_code=404, detail="Watched item not found")
return {"id": str(result[0]), "status": "deleted"}