|
@@ -1,7 +1,7 @@
|
|
|
from fastapi import FastAPI, Request, Depends, Form, Response, HTTPException, status
|
|
from fastapi import FastAPI, Request, Depends, Form, Response, HTTPException, status
|
|
|
from fastapi.templating import Jinja2Templates
|
|
from fastapi.templating import Jinja2Templates
|
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
-from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
|
|
|
|
|
|
+from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse, StreamingResponse
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
from sqlalchemy import select, func
|
|
from sqlalchemy import select, func
|
|
|
from contextlib import asynccontextmanager
|
|
from contextlib import asynccontextmanager
|
|
@@ -281,13 +281,14 @@ async def sync_with_audiobookshelf(
|
|
|
genres=json.dumps(metadata.get("genres", [])),
|
|
genres=json.dumps(metadata.get("genres", [])),
|
|
|
tags=json.dumps(media.get("tags", [])),
|
|
tags=json.dumps(media.get("tags", [])),
|
|
|
duration=media.get("duration", 0),
|
|
duration=media.get("duration", 0),
|
|
|
- cover_url=media.get("coverPath")
|
|
|
|
|
|
|
+ cover_url=media.get("coverPath") # Store relative path
|
|
|
)
|
|
)
|
|
|
db.add(book)
|
|
db.add(book)
|
|
|
else:
|
|
else:
|
|
|
# Update existing book
|
|
# Update existing book
|
|
|
book.title = metadata.get("title", book.title)
|
|
book.title = metadata.get("title", book.title)
|
|
|
book.author = metadata.get("authorName", book.author)
|
|
book.author = metadata.get("authorName", book.author)
|
|
|
|
|
+ book.cover_url = media.get("coverPath") # Store relative path
|
|
|
book.updated_at = datetime.now()
|
|
book.updated_at = datetime.now()
|
|
|
|
|
|
|
|
# Update or create listening session
|
|
# Update or create listening session
|
|
@@ -731,3 +732,39 @@ async def delete_user(
|
|
|
async def health_check():
|
|
async def health_check():
|
|
|
"""Health check endpoint."""
|
|
"""Health check endpoint."""
|
|
|
return {"status": "healthy"}
|
|
return {"status": "healthy"}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@app.get("/api/cover/{book_id}")
|
|
|
|
|
+async def get_book_cover(
|
|
|
|
|
+ book_id: str,
|
|
|
|
|
+ db: AsyncSession = Depends(get_db),
|
|
|
|
|
+ user: User = Depends(get_current_user)
|
|
|
|
|
+):
|
|
|
|
|
+ """Proxy book cover images from Audiobookshelf with authentication."""
|
|
|
|
|
+ import httpx
|
|
|
|
|
+
|
|
|
|
|
+ # Get book from database
|
|
|
|
|
+ result = await db.execute(select(Book).where(Book.id == book_id))
|
|
|
|
|
+ book = result.scalar_one_or_none()
|
|
|
|
|
+
|
|
|
|
|
+ if not book or not book.cover_url:
|
|
|
|
|
+ raise HTTPException(status_code=404, detail="Cover not found")
|
|
|
|
|
+
|
|
|
|
|
+ # Get user's ABS client
|
|
|
|
|
+ abs_client = get_abs_client(user)
|
|
|
|
|
+
|
|
|
|
|
+ # Fetch cover from Audiobookshelf with auth
|
|
|
|
|
+ cover_url = f"{abs_client.base_url}{book.cover_url}"
|
|
|
|
|
+
|
|
|
|
|
+ async with httpx.AsyncClient() as client:
|
|
|
|
|
+ try:
|
|
|
|
|
+ response = await client.get(cover_url, headers=abs_client.headers)
|
|
|
|
|
+ response.raise_for_status()
|
|
|
|
|
+
|
|
|
|
|
+ # Return image with appropriate content type
|
|
|
|
|
+ return Response(
|
|
|
|
|
+ content=response.content,
|
|
|
|
|
+ media_type=response.headers.get("content-type", "image/jpeg")
|
|
|
|
|
+ )
|
|
|
|
|
+ except httpx.HTTPError:
|
|
|
|
|
+ raise HTTPException(status_code=404, detail="Cover image not found")
|