summaryrefslogtreecommitdiff
path: root/routers/films.py
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-06 15:25:36 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-06 15:25:36 -0700
commit1bdf4ca8c0f51718124ffe5247ac133973d4f251 (patch)
tree2904145bdbc5d2a2164cd3cb6c95346e48afd4a5 /routers/films.py
parent051775337251b0c7036959901eacb58471100862 (diff)
Add authentication, public profile, and infinite scroll
- Implement session-based auth with argon2 password hashing - Add login form and logout button in nav - Create public /tyler profile page with curated stats - Implement infinite scroll for film lists (load 20 at a time) - Add lazy loading for poster images - Fix profile page CSS to use dark theme variables - Use consistent star character (✦) across all pages - Add /films/partial endpoint for pagination Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Diffstat (limited to 'routers/films.py')
-rw-r--r--routers/films.py59
1 files changed, 53 insertions, 6 deletions
diff --git a/routers/films.py b/routers/films.py
index fc087b1..4bb7cc9 100644
--- a/routers/films.py
+++ b/routers/films.py
@@ -176,6 +176,17 @@ def _notice_context(
}
+def _get_shelf_query(db: Session, shelf: str):
+ """Get a query for films on a shelf, ordered appropriately."""
+ query = db.query(Film).filter(Film.shelf == shelf)
+ if shelf == "diary":
+ return query.order_by(Film.date_watched.desc(), Film.created_at.desc(), Film.id.desc())
+ elif shelf == "queue":
+ return query.order_by(Film.created_at.desc(), Film.id.desc())
+ else: # abandoned
+ return query.order_by(Film.updated_at.desc(), Film.created_at.desc(), Film.id.desc())
+
+
def _group_films_by_month(films: list[Film]) -> list[dict]:
def month_key(film: Film) -> str:
return film.date_watched.strftime("%B %Y") if film.date_watched else "Unknown"
@@ -192,15 +203,14 @@ def _render_shelf(
db: Session,
notices: dict,
):
- query = db.query(Film).filter(Film.shelf == shelf)
+ query = _get_shelf_query(db, shelf)
+ total_films = query.count()
+ films = query.limit(20).all()
+ has_more = total_films > 20
+
if shelf == "diary":
- films = query.order_by(Film.date_watched.desc(), Film.created_at.desc(), Film.id.desc()).all()
grouped_films = _group_films_by_month(films)
- elif shelf == "queue":
- films = query.order_by(Film.created_at.desc(), Film.id.desc()).all()
- grouped_films = None
else:
- films = query.order_by(Film.updated_at.desc(), Film.created_at.desc(), Film.id.desc()).all()
grouped_films = None
return templates.TemplateResponse(
@@ -212,6 +222,8 @@ def _render_shelf(
"grouped_films": grouped_films,
"active_shelf": shelf,
"shelf_meta": SHELF_META[shelf],
+ "total_films": total_films,
+ "has_more": has_more,
**notices,
},
)
@@ -361,6 +373,41 @@ async def _film_tmdb_context(film: Film) -> dict | None:
return context
+@router.get("/films/partial")
+def get_films_partial(
+ request: Request,
+ shelf: str = Query("diary"),
+ offset: int = Query(0),
+ limit: int = Query(20),
+ db: Session = Depends(get_db),
+):
+ """Returns partial HTML for pagination. Used by infinite scroll."""
+ if shelf not in ALLOWED_SHELVES:
+ raise HTTPException(status_code=400, detail="Invalid shelf.")
+
+ query = _get_shelf_query(db, shelf)
+ total = query.count()
+ films = query.offset(offset).limit(limit).all()
+ has_more = (offset + limit) < total
+
+ if shelf == "diary":
+ grouped_films = _group_films_by_month(films)
+ else:
+ grouped_films = None
+
+ from fastapi.responses import HTMLResponse
+ html = templates.get_template("_feed_partial.html").render(
+ request=request,
+ films=films,
+ grouped_films=grouped_films,
+ active_shelf=shelf,
+ )
+
+ response = HTMLResponse(html)
+ response.headers["X-Has-More"] = "true" if has_more else "false"
+ return response
+
+
@router.get("/films/{film_id}")
async def film_detail(film_id: int, request: Request, db: Session = Depends(get_db)):
film = _get_film_or_404(db, film_id)