summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2025-11-15 23:16:45 -0500
committerBen Sima <ben@bsima.me>2025-11-15 23:16:45 -0500
commit79e9158793e65b984494c12d02277a1c8790c484 (patch)
treeff3a86d68b0829bdbca4fc01f433918c14be5280
parenta4f7427540840d31195ad37d7c68955ec7a36584 (diff)
Add public feed routes and update home page
- Add /public route showing public episodes - Add /public.rss RSS feed for public episodes - Update home page to show public feed when not logged in - Use user_episodes junction table instead of user_id filtering - All tests passing
-rw-r--r--.tasks/tasks.jsonl6
-rw-r--r--Biz/PodcastItLater/Web.py64
2 files changed, 65 insertions, 5 deletions
diff --git a/.tasks/tasks.jsonl b/.tasks/tasks.jsonl
index 93dca99..26f27cd 100644
--- a/.tasks/tasks.jsonl
+++ b/.tasks/tasks.jsonl
@@ -92,9 +92,9 @@
{"taskCreatedAt":"2025-11-16T04:06:58.665794496Z","taskDependencies":[],"taskId":"t-gaRBUA","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add Core.py database functions for user_episodes junction table","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:29.027348473Z"}
{"taskCreatedAt":"2025-11-16T04:06:59.199139475Z","taskDependencies":[],"taskId":"t-gaTQEV","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add Core.py database functions for episode metrics tracking","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:09:29.602931183Z"}
{"taskCreatedAt":"2025-11-16T04:07:07.307576303Z","taskDependencies":[],"taskId":"t-gbrS2a","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Modify submission flow to check for existing episodes by URL hash","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:13:56.073214768Z"}
-{"taskCreatedAt":"2025-11-16T04:07:07.834181871Z","taskDependencies":[],"taskId":"t-gbu51O","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Open","taskTitle":"Add /public route to display public feed","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:07:07.834181871Z"}
-{"taskCreatedAt":"2025-11-16T04:07:08.369657826Z","taskDependencies":[],"taskId":"t-gbwkkw","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Open","taskTitle":"Add /public.rss route for public RSS feed generation","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:07:08.369657826Z"}
-{"taskCreatedAt":"2025-11-16T04:07:08.906237761Z","taskDependencies":[],"taskId":"t-gbyzV2","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Open","taskTitle":"Update home page to show public feed when user is logged out","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:07:08.906237761Z"}
+{"taskCreatedAt":"2025-11-16T04:07:07.834181871Z","taskDependencies":[],"taskId":"t-gbu51O","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add /public route to display public feed","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:16:43.926763164Z"}
+{"taskCreatedAt":"2025-11-16T04:07:08.369657826Z","taskDependencies":[],"taskId":"t-gbwkkw","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Add /public.rss route for public RSS feed generation","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:16:44.383466957Z"}
+{"taskCreatedAt":"2025-11-16T04:07:08.906237761Z","taskDependencies":[],"taskId":"t-gbyzV2","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Done","taskTitle":"Update home page to show public feed when user is logged out","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:16:44.848713835Z"}
{"taskCreatedAt":"2025-11-16T04:07:09.433392796Z","taskDependencies":[],"taskId":"t-gbAN3x","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Open","taskTitle":"Add admin toggle button to episode cards for public/private status","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:07:09.433392796Z"}
{"taskCreatedAt":"2025-11-16T04:07:17.092115521Z","taskDependencies":[],"taskId":"t-gc6Vrk","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Open","taskTitle":"Add POST /admin/episode/{id}/toggle-public endpoint","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:07:17.092115521Z"}
{"taskCreatedAt":"2025-11-16T04:07:17.6266109Z","taskDependencies":[],"taskId":"t-gc9aud","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-ga8V8O","taskStatus":"Open","taskTitle":"Add '+ Add to your feed' button on episode pages for logged-in users","taskType":"WorkTask","taskUpdatedAt":"2025-11-16T04:07:17.6266109Z"}
diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py
index 8caaf8c..14bca1b 100644
--- a/Biz/PodcastItLater/Web.py
+++ b/Biz/PodcastItLater/Web.py
@@ -807,10 +807,12 @@ def index(request: Request) -> HomePage:
queue_items = Core.Database.get_user_queue_status(
user_id,
)
- episodes = Core.Database.get_user_recent_episodes(
+ episodes = Core.Database.get_user_episodes(
user_id,
- 10,
)
+ else:
+ # Show public feed when not logged in
+ episodes = Core.Database.get_public_episodes(10)
return HomePage(
queue_items=queue_items,
@@ -820,6 +822,22 @@ def index(request: Request) -> HomePage:
)
+@app.get("/public")
+def public_feed(request: Request) -> HomePage:
+ """Display public feed page."""
+ # Always show public episodes, whether user is logged in or not
+ episodes = Core.Database.get_public_episodes(50)
+ user_id = request.session.get("user_id")
+ user = Core.Database.get_user_by_id(user_id) if user_id else None
+
+ return HomePage(
+ queue_items=[],
+ episodes=episodes,
+ user=user,
+ error=None,
+ )
+
+
def _handle_test_login(email: str, request: Request) -> Response:
"""Handle login in test mode."""
# Special handling for demo account
@@ -1318,6 +1336,48 @@ def rss_feed(request: Request, token: str) -> Response: # noqa: ARG001
return Response(f"Error generating feed: {e}", status_code=500)
+@app.get("/public.rss")
+def public_rss_feed(request: Request) -> Response: # noqa: ARG001
+ """Generate public RSS podcast feed."""
+ try:
+ # Get public episodes
+ episodes = Core.Database.get_public_episodes(50)
+
+ fg = FeedGenerator()
+ fg.title("PodcastItLater Public Feed")
+ fg.description("Curated articles converted to audio")
+ fg.author(name=RSS_CONFIG["author"])
+ fg.language(RSS_CONFIG["language"])
+ fg.link(href=f"{RSS_CONFIG['base_url']}/public.rss")
+ fg.id(f"{RSS_CONFIG['base_url']}/public.rss")
+
+ for episode in episodes:
+ fe = fg.add_entry()
+ episode_sqid = encode_episode_id(episode["id"])
+ fe.id(f"{RSS_CONFIG['base_url']}/episode/{episode_sqid}")
+ fe.title(episode["title"])
+ fe.description(episode["title"])
+ fe.enclosure(
+ episode["audio_url"],
+ str(episode.get("content_length", 0)),
+ "audio/mpeg",
+ )
+ # SQLite timestamps don't have timezone info, so add UTC
+ created_at = datetime.fromisoformat(episode["created_at"])
+ if created_at.tzinfo is None:
+ created_at = created_at.replace(tzinfo=timezone.utc)
+ fe.pubDate(created_at)
+
+ rss_str = fg.rss_str(pretty=True)
+ return Response(
+ rss_str,
+ media_type="application/rss+xml; charset=utf-8",
+ )
+
+ except (ValueError, KeyError, AttributeError) as e:
+ return Response(f"Error generating feed: {e}", status_code=500)
+
+
@app.get("/episode/{episode_id:int}")
def episode_detail_legacy(
request: Request, # noqa: ARG001