summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2025-11-16 03:13:46 -0500
committerBen Sima <ben@bsima.me>2025-11-16 03:13:46 -0500
commita2a4360e89603052e9f88d8405eaa386019b680f (patch)
tree11d6a9a33117629a459833a8d3be763c03ba9bdb
parentc8c239e048c8b9b84a89cb8482a67fb6e85fb724 (diff)
Add admin toggle for episode public/private status
- Add toggle button to episode cards (admins only) - Button shows globe icon for public, lock icon for private - Toggle endpoint at POST /admin/episode/{id}/toggle-public - Updates episode is_public status and redirects to home Tasks completed: t-gcbqDl, t-gc6Vrk (partially - admin toggle complete)
-rw-r--r--Biz/PodcastItLater/Admin.py31
-rw-r--r--Biz/PodcastItLater/Web.py41
2 files changed, 69 insertions, 3 deletions
diff --git a/Biz/PodcastItLater/Admin.py b/Biz/PodcastItLater/Admin.py
index 6ee255e..8e12fc7 100644
--- a/Biz/PodcastItLater/Admin.py
+++ b/Biz/PodcastItLater/Admin.py
@@ -672,6 +672,37 @@ def update_user_status(
)
+def toggle_episode_public(request: Request, episode_id: int) -> Response:
+ """Toggle episode public/private status."""
+ # Check if user is logged in and is admin
+ session_user_id = request.session.get("user_id")
+ if not session_user_id:
+ return Response("Unauthorized", status_code=401)
+
+ user = Core.Database.get_user_by_id(session_user_id)
+ if not user or not Core.is_admin(user):
+ return Response("Forbidden", status_code=403)
+
+ # Get current episode status
+ episode = Core.Database.get_episode_by_id(episode_id)
+ if not episode:
+ return Response("Episode not found", status_code=404)
+
+ # Toggle public status
+ current_public = episode.get("is_public", 0) == 1
+ if current_public:
+ Core.Database.unmark_episode_public(episode_id)
+ else:
+ Core.Database.mark_episode_public(episode_id)
+
+ # Redirect to home page to see updated status
+ return Response(
+ "",
+ status_code=200,
+ headers={"HX-Redirect": "/"},
+ )
+
+
def main() -> None:
"""Admin tests are currently in Web."""
if "test" in sys.argv:
diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py
index 2868147..97ec439 100644
--- a/Biz/PodcastItLater/Web.py
+++ b/Biz/PodcastItLater/Web.py
@@ -515,6 +515,7 @@ class EpisodeListAttrs(Attrs):
episodes: list[dict[str, typing.Any]]
rss_url: str | None
+ user: dict[str, typing.Any] | None
class EpisodeList(Component[AnyChildren, EpisodeListAttrs]):
@@ -524,6 +525,7 @@ class EpisodeList(Component[AnyChildren, EpisodeListAttrs]):
def render(self) -> html.div:
episodes = self.attrs["episodes"]
rss_url = self.attrs.get("rss_url")
+ user = self.attrs.get("user")
if not episodes:
return html.div(
@@ -539,8 +541,37 @@ class EpisodeList(Component[AnyChildren, EpisodeListAttrs]):
for episode in episodes:
duration_str = UI.format_duration(episode.get("duration"))
episode_sqid = encode_episode_id(episode["id"])
+ is_public = episode.get("is_public", 0) == 1
+
+ # Admin toggle button for public/private status
+ admin_toggle = html.div()
+ if user and Core.is_admin(user):
+ admin_toggle = html.div(
+ html.button(
+ html.i(
+ classes=[
+ "bi",
+ "bi-globe" if is_public else "bi-lock",
+ "me-1",
+ ],
+ ),
+ "Public" if is_public else "Private",
+ hx_post=f"/admin/episode/{episode['id']}/toggle-public",
+ hx_target="body",
+ hx_swap="outerHTML",
+ classes=[
+ "btn",
+ "btn-sm",
+ "btn-success" if is_public else "btn-secondary",
+ ],
+ ),
+ classes=["position-absolute", "top-0", "end-0", "m-2"],
+ style={"z-index": "10"},
+ )
+
episode_items.append(
html.div(
+ admin_toggle,
html.div(
html.h5(
html.a(
@@ -589,7 +620,7 @@ class EpisodeList(Component[AnyChildren, EpisodeListAttrs]):
else html.div(),
classes=["card-body"],
),
- classes=["card", "mb-3"],
+ classes=["card", "mb-3", "position-relative"],
),
)
@@ -750,6 +781,7 @@ class HomePage(Component[AnyChildren, HomePageAttrs]):
EpisodeList(
episodes=episodes,
rss_url=f"{BASE_URL}/feed/{user['token']}.xml",
+ user=user,
),
id="dashboard-content",
hx_get="/dashboard-updates",
@@ -1461,7 +1493,7 @@ def dashboard_updates(request: Request) -> html.div:
if not user_id:
return html.div(
QueueStatus(items=[]),
- EpisodeList(episodes=[], rss_url=None),
+ EpisodeList(episodes=[], rss_url=None, user=None),
)
# Get user info for RSS URL
@@ -1474,7 +1506,7 @@ def dashboard_updates(request: Request) -> html.div:
return html.div(
QueueStatus(items=queue_items),
- EpisodeList(episodes=episodes, rss_url=rss_url),
+ EpisodeList(episodes=episodes, rss_url=rss_url, user=user),
id="dashboard-content",
)
@@ -1574,6 +1606,9 @@ def cancel_queue_item(request: Request, job_id: int) -> Response:
app.delete("/queue/{job_id}")(Admin.delete_queue_item)
app.get("/admin/users")(Admin.admin_users)
app.post("/admin/users/{user_id}/status")(Admin.update_user_status)
+app.post("/admin/episode/{episode_id}/toggle-public")(
+ Admin.toggle_episode_public,
+)
class BaseWebTest(Test.TestCase):