From 1bdf4ca8c0f51718124ffe5247ac133973d4f251 Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Wed, 6 May 2026 15:25:36 -0700 Subject: Add authentication, public profile, and infinite scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- routers/films.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 6 deletions(-) (limited to 'routers/films.py') 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) -- cgit v1.3-2-g0d8e