"""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"}