From ea6462e928f588c3fbff32e0850298d3ff3dbfb4 Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Wed, 6 May 2026 23:43:35 -0700 Subject: Add /about page with rating system explanation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a public-facing /about page that explains the 1-3 star rating rubric with real examples pulled from the diary. Each star tier displays 3 randomly-selected, unique films (deduplicated by title to avoid rewatch duplicates). Changes: - New routers/about.py: GET /about queries films by stars, dedupes, randomizes - New templates/about.html: Page with eyebrow, h1, three tier sections with example film cards, closing philosophy, and View Profile button - main.py: Import about router, register it, add /about to public_paths in AuthMiddleware - templates/base.html: Add About nav link after Stats - templates/profile.html: Add About link to /tyler nav - templates/login.html: Add About and View Profile buttons in footer, plus "Made with Lumière" repo link Co-Authored-By: Claude Haiku 4.5 --- main.py | 5 ++- routers/about.py | 43 +++++++++++++++++++++ templates/about.html | 102 +++++++++++++++++++++++++++++++++++++++++++++++++ templates/base.html | 1 + templates/login.html | 12 ++++++ templates/profile.html | 3 +- 6 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 routers/about.py create mode 100644 templates/about.html diff --git a/main.py b/main.py index a861cda..6b1a06b 100644 --- a/main.py +++ b/main.py @@ -9,14 +9,14 @@ from starlette.middleware.sessions import SessionMiddleware from starlette.middleware.base import BaseHTTPMiddleware from database import init_db -from routers import auth, films, imports as imports_router, profile, stats, tmdb +from routers import about, auth, films, imports as imports_router, profile, stats, tmdb load_dotenv() class AuthMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): - public_paths = {"/login", "/logout", "/tyler", "/films/partial"} + public_paths = {"/about", "/login", "/logout", "/tyler", "/films/partial"} path = request.url.path if path.startswith("/static") or path in public_paths: @@ -42,6 +42,7 @@ app.add_middleware(AuthMiddleware) app.add_middleware(SessionMiddleware, secret_key=session_secret) app.mount("/static", StaticFiles(directory="static"), name="static") +app.include_router(about.router) app.include_router(auth.router) app.include_router(profile.router) app.include_router(tmdb.router) diff --git a/routers/about.py b/routers/about.py new file mode 100644 index 0000000..fe28524 --- /dev/null +++ b/routers/about.py @@ -0,0 +1,43 @@ +import random +from fastapi import APIRouter, Depends, Request +from fastapi.templating import Jinja2Templates +from sqlalchemy.orm import Session + +from database import get_db +from models import Film + +router = APIRouter(tags=["about"]) +templates = Jinja2Templates(directory="templates") + + +@router.get("/about", name="about") +def about(request: Request, db: Session = Depends(get_db)): + films_by_rating = {} + + for stars in (1, 2, 3): + all_films = ( + db.query(Film) + .filter(Film.shelf == "diary", Film.stars == stars) + .all() + ) + + # Deduplicate by title, keeping most recent watch per title + seen_titles = {} + for film in sorted(all_films, key=lambda f: f.date_watched or "", reverse=True): + if film.title not in seen_titles: + seen_titles[film.title] = film + + # Randomize and take up to 3 + unique_films = list(seen_titles.values()) + random.shuffle(unique_films) + films_by_rating[str(stars)] = unique_films[:3] + + return templates.TemplateResponse( + request=request, + name="about.html", + context={ + "request": request, + "active_page": "about", + "films_by_rating": films_by_rating, + }, + ) diff --git a/templates/about.html b/templates/about.html new file mode 100644 index 0000000..ab64142 --- /dev/null +++ b/templates/about.html @@ -0,0 +1,102 @@ +{% extends "base.html" %} + +{% block content %} +
+

How it works

+
+

Rating system

+
+
+ +
+

+ This rubric is descriptive, not prescriptive — a way to read the ratings, not a formula for assigning them. Stars reflect something felt, not a checklist passed. +

+ + +
+

✦ Good

+

+ Films that earned their runtime. You were engaged throughout, walked away with something — an image that lingered, a line that landed, an emotion that felt true. You'd send this to someone whose taste you understand. +

+ {% if films_by_rating.get("1") %} +

Examples from your diary

+
+ {% for film in films_by_rating["1"] %} +
+
+ {% if film.poster_url %} + {{ film.title }} poster + {% else %} + {{ film.title[:1] }} + {% endif %} +
+

{{ film.title }}

+ {% if film.year %}

{{ film.year }}

{% endif %} +
+ {% endfor %} +
+ {% endif %} +
+ + +
+

✦✦ Excellent

+

+ Films that stay. Something in the execution — the direction, the performances, the way it holds an idea — lifted it into a different register. Days later it's still running in the background. You don't just mention it; you push it. +

+ {% if films_by_rating.get("2") %} +

Examples from your diary

+
+ {% for film in films_by_rating["2"] %} +
+
+ {% if film.poster_url %} + {{ film.title }} poster + {% else %} + {{ film.title[:1] }} + {% endif %} +
+

{{ film.title }}

+ {% if film.year %}

{{ film.year }}

{% endif %} +
+ {% endfor %} +
+ {% endif %} +
+ + +
+

✦✦✦ Exceptional

+

+ Films that feel necessary. They reframe how you see the medium, or the world, or both. You come back to them. You reference them without meaning to. You can't fully explain why they matter — only that they do, and probably always will. +

+ {% if films_by_rating.get("3") %} +

Examples from your diary

+
+ {% for film in films_by_rating["3"] %} +
+
+ {% if film.poster_url %} + {{ film.title }} poster + {% else %} + {{ film.title[:1] }} + {% endif %} +
+

{{ film.title }}

+ {% if film.year %}

{{ film.year }}

{% endif %} +
+ {% endfor %} +
+ {% endif %} +
+ +

+ The 3s resist defense. That's the point. Films you can explain with rules are just very good; the exceptional ones arrived on their own terms. The rubric exists to calibrate the 1s and 2s — the 3s took care of themselves. +

+ +
+ View Profile +
+
+{% endblock %} diff --git a/templates/base.html b/templates/base.html index 925d161..4e814ad 100644 --- a/templates/base.html +++ b/templates/base.html @@ -18,6 +18,7 @@ Queue Abandoned Stats + About Import Add Film
diff --git a/templates/login.html b/templates/login.html index c164531..a8caae3 100644 --- a/templates/login.html +++ b/templates/login.html @@ -56,5 +56,17 @@ Sign In
+ +
+

Want to learn more?

+
+ About the Rating System + View Profile +
+
+ + {% endblock %} diff --git a/templates/profile.html b/templates/profile.html index 745ed32..7c847f4 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -13,6 +13,7 @@ Lumière @@ -142,7 +143,7 @@ -- cgit v1.3-2-g0d8e