summaryrefslogtreecommitdiff
path: root/Biz/PodcastItLater/Web.py
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-12-15 08:47:02 -0500
committerBen Sima <ben@bensima.com>2025-12-15 08:47:02 -0500
commit0baab1972e30c0e4629e67152838e660b02a2537 (patch)
treed82d9402e4a0840777ee3d4e39ab4329f246918b /Biz/PodcastItLater/Web.py
parentadf693eb82cddd2c383cdebd3392716446ddf054 (diff)
t-265.6: Add feedback collection endpoint for PIL
- Add feedback table with migration in Core.py - Add FeedbackForm and FeedbackPage UI components - Add /feedback GET/POST routes and /api/feedback JSON endpoint - Add admin feedback view at /admin/feedback - Create Omni/Agent/Tools/Feedback.hs with feedback_list tool - Wire feedback tool into Telegram agent
Diffstat (limited to 'Biz/PodcastItLater/Web.py')
-rw-r--r--Biz/PodcastItLater/Web.py85
1 files changed, 83 insertions, 2 deletions
diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py
index 30b5236..076eb95 100644
--- a/Biz/PodcastItLater/Web.py
+++ b/Biz/PodcastItLater/Web.py
@@ -167,7 +167,8 @@ def send_magic_link(email: str, token: str) -> None:
# Create email body
magic_link = f"{BASE_URL}/auth/verify?token={token}"
- body_text_path.write_text(f"""
+ body_text_path.write_text(
+ f"""
Hello,
Click this link to login to PodcastItLater:
@@ -179,7 +180,9 @@ If you didn't request this, please ignore this email.
Best,
PodcastItLater
-""")
+""",
+ encoding="utf-8",
+ )
try:
Biz.EmailAgent.send_email(
@@ -1033,6 +1036,83 @@ def pricing(request: Request) -> UI.PricingPage:
)
+@app.get("/feedback")
+def feedback_form(request: Request) -> UI.FeedbackPage:
+ """Display feedback form."""
+ user_id = request.session.get("user_id")
+ user = Core.Database.get_user_by_id(user_id) if user_id else None
+ campaign_id = request.query_params.get("campaign")
+
+ return UI.FeedbackPage(
+ user=user,
+ campaign_id=campaign_id,
+ success=False,
+ )
+
+
+@app.post("/feedback")
+async def submit_feedback(request: Request) -> UI.FeedbackPage:
+ """Submit feedback form."""
+ import secrets
+
+ user_id = request.session.get("user_id")
+ user = Core.Database.get_user_by_id(user_id) if user_id else None
+
+ form_data = await request.form()
+
+ email = form_data.get("email")
+ source = form_data.get("source")
+ campaign_id = form_data.get("campaign_id")
+ rating_str = form_data.get("rating")
+ feedback_text = form_data.get("feedback_text")
+ use_case = form_data.get("use_case")
+
+ rating = int(rating_str) if rating_str else None
+
+ feedback_id = secrets.token_urlsafe(16)
+
+ Core.Database.create_feedback(
+ feedback_id=feedback_id,
+ email=str(email) if email else None,
+ source=str(source) if source else None,
+ campaign_id=str(campaign_id) if campaign_id else None,
+ rating=rating,
+ feedback_text=str(feedback_text) if feedback_text else None,
+ use_case=str(use_case) if use_case else None,
+ )
+
+ logger.info(
+ "Received feedback %s from %s", feedback_id, email or "anonymous"
+ )
+
+ return UI.FeedbackPage(
+ user=user,
+ campaign_id=None,
+ success=True,
+ )
+
+
+@app.get("/api/feedback")
+def api_feedback(request: Request) -> Response:
+ """Return feedback entries as JSON for agent tools."""
+ import json
+
+ limit_str = request.query_params.get("limit", "20")
+ since = request.query_params.get("since")
+
+ try:
+ limit = min(100, max(1, int(limit_str)))
+ except ValueError:
+ limit = 20
+
+ feedback = Core.Database.get_feedback(limit=limit, since=since)
+
+ return Response(
+ json.dumps(feedback),
+ media_type="application/json",
+ )
+
+
@app.post("/upgrade")
def upgrade(request: Request) -> RedirectResponse:
"""Start upgrade checkout flow."""
@@ -1832,6 +1912,7 @@ 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.get("/admin/metrics")(Admin.admin_metrics)
+app.get("/admin/feedback")(Admin.admin_feedback)
app.post("/admin/users/{user_id}/status")(Admin.update_user_status)
app.post("/admin/episode/{episode_id}/toggle-public")(
Admin.toggle_episode_public,