diff options
| author | Ben Sima <ben@bsima.me> | 2025-11-15 23:16:45 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bsima.me> | 2025-11-15 23:16:45 -0500 |
| commit | 79e9158793e65b984494c12d02277a1c8790c484 (patch) | |
| tree | ff3a86d68b0829bdbca4fc01f433918c14be5280 | |
| parent | a4f7427540840d31195ad37d7c68955ec7a36584 (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.jsonl | 6 | ||||
| -rw-r--r-- | Biz/PodcastItLater/Web.py | 64 |
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 |
