import os import logging from fastapi import FastAPI, BackgroundTasks, Depends, HTTPException, Query from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from pydantic import BaseModel from typing import Optional, List import aiosqlite from db import init_db, get_db from fetcher import fetch_subscriptions from summarizer import summarize_video, summarize_all_pending logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') log = logging.getLogger(__name__) app = FastAPI(title="T YouTube", description="Text-based YouTube Summarizer") # Enable CORS for development app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) class StatusUpdate(BaseModel): status: str # Database Startup Hook @app.on_event("startup") async def startup(): await init_db() @app.get("/api/health") async def health(): return {"status": "ok"} @app.get("/") async def root(): from fastapi.responses import FileResponse return FileResponse(os.path.join(frontend_build_path, "index.html")) @app.get("/api/videos") async def get_videos( status: Optional[str] = "pending", limit: int = 50, db: aiosqlite.Connection = Depends(get_db) ): query = "SELECT * FROM videos WHERE status = ? ORDER BY publish_date DESC, created_at DESC LIMIT ?" cursor = await db.execute(query, (status, limit)) rows = await cursor.fetchall() return [dict(row) for row in rows] async def bg_fetch_and_summarize(): log.info("Starting background subscription fetch and summarization loop...") try: new_count = await fetch_subscriptions() if new_count > 0: await summarize_all_pending() except Exception as e: log.error(f"Error in background fetch and summarize task: {e}") @app.post("/api/videos/fetch") async def trigger_fetch(background_tasks: BackgroundTasks): background_tasks.add_task(bg_fetch_and_summarize) return {"message": "Subscription sync and summarization running in background."} @app.post("/api/videos/{video_id}/summarize") async def trigger_summarize(video_id: str, background_tasks: BackgroundTasks, db: aiosqlite.Connection = Depends(get_db)): cursor = await db.execute("SELECT 1 FROM videos WHERE video_id = ?", (video_id,)) exists = await cursor.fetchone() if not exists: raise HTTPException(status_code=404, detail="Video not found") background_tasks.add_task(summarize_video, video_id) return {"message": f"Summarization for video {video_id} queued."} @app.post("/api/videos/{video_id}/status") async def update_status( video_id: str, update: StatusUpdate, db: aiosqlite.Connection = Depends(get_db) ): if update.status not in ["pending", "read", "skipped"]: raise HTTPException(status_code=400, detail="Invalid status value") cursor = await db.execute("SELECT 1 FROM videos WHERE video_id = ?", (video_id,)) exists = await cursor.fetchone() if not exists: raise HTTPException(status_code=404, detail="Video not found") await db.execute("UPDATE videos SET status = ? WHERE video_id = ?", (update.status, video_id)) await db.commit() return {"message": f"Video {video_id} status updated to {update.status}."} # Mount frontend built files for production if they exist frontend_build_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "frontend/dist") if os.path.exists(frontend_build_path): app.mount("/", StaticFiles(directory=frontend_build_path, html=True), name="frontend") log.info("Mounted static frontend build successfully.") else: log.warning("Frontend build directory 'frontend/dist' not found. API only mode is active.") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)