From b60fc6f95e68c8581e2cec48f8d99e7c467a1db2 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Tue, 2 Dec 2025 15:52:27 -0500 Subject: Remove pyproject=true, use format=setuptools, add toggle_episode_public --- Biz/PodcastItLater/Core.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'Biz/PodcastItLater/Core.py') diff --git a/Biz/PodcastItLater/Core.py b/Biz/PodcastItLater/Core.py index 3a88f22..62eead8 100644 --- a/Biz/PodcastItLater/Core.py +++ b/Biz/PodcastItLater/Core.py @@ -1072,6 +1072,18 @@ class Database: # noqa: PLR0904 conn.commit() logger.info("Unmarked episode %s as public", episode_id) + @staticmethod + def toggle_episode_public(episode_id: int) -> None: + """Toggle an episode's public status.""" + with Database.get_connection() as conn: + cursor = conn.cursor() + cursor.execute( + "UPDATE episodes SET is_public = NOT is_public WHERE id = ?", + (episode_id,), + ) + conn.commit() + logger.info("Toggled public status for episode %s", episode_id) + @staticmethod def get_public_episodes(limit: int = 50) -> list[dict[str, Any]]: """Get public episodes for public feed.""" -- cgit v1.2.3 From 0baab1972e30c0e4629e67152838e660b02a2537 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Mon, 15 Dec 2025 08:47:02 -0500 Subject: 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 --- Biz/PodcastItLater/Core.py | 144 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) (limited to 'Biz/PodcastItLater/Core.py') diff --git a/Biz/PodcastItLater/Core.py b/Biz/PodcastItLater/Core.py index 62eead8..d0ed2f0 100644 --- a/Biz/PodcastItLater/Core.py +++ b/Biz/PodcastItLater/Core.py @@ -202,6 +202,9 @@ class Database: # noqa: PLR0904 # Run migration to add public feed features Database.migrate_add_public_feed() + # Run migration to add feedback table + Database.migrate_add_feedback_table() + @staticmethod def add_to_queue( url: str, @@ -773,6 +776,42 @@ class Database: # noqa: PLR0904 conn.commit() logger.info("Database migrated for public feed feature") + @staticmethod + def migrate_add_feedback_table() -> None: + """Add feedback table for collecting user research data.""" + with Database.get_connection() as conn: + cursor = conn.cursor() + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS feedback ( + id TEXT PRIMARY KEY, + email TEXT, + source TEXT, + campaign_id TEXT, + rating INTEGER, + feedback_text TEXT, + use_case TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + + # Create indexes for querying feedback + cursor.execute( + "CREATE INDEX IF NOT EXISTS idx_feedback_source " + "ON feedback(source)", + ) + cursor.execute( + "CREATE INDEX IF NOT EXISTS idx_feedback_campaign " + "ON feedback(campaign_id)", + ) + cursor.execute( + "CREATE INDEX IF NOT EXISTS idx_feedback_created " + "ON feedback(created_at)", + ) + + conn.commit() + logger.info("Created feedback table") + @staticmethod def migrate_add_default_titles() -> None: """Add default titles to queue items that have None titles.""" @@ -1200,6 +1239,111 @@ class Database: # noqa: PLR0904 row = cursor.fetchone() return dict(row) if row is not None else None + @staticmethod + def create_feedback( # noqa: PLR0913, PLR0917 + feedback_id: str, + email: str | None, + source: str | None, + campaign_id: str | None, + rating: int | None, + feedback_text: str | None, + use_case: str | None, + ) -> str: + """Create a new feedback entry. + + Args: + feedback_id: Unique ID for the feedback + email: Optional email address + source: How they heard about PIL (outreach, organic, trial) + campaign_id: Optional link to outreach draft ID + rating: Optional 1-5 rating + feedback_text: Optional general feedback + use_case: What they want to use PIL for + + Returns: + The feedback ID + """ + with Database.get_connection() as conn: + cursor = conn.cursor() + cursor.execute( + """ + INSERT INTO feedback + (id, email, source, campaign_id, rating, + feedback_text, use_case) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + feedback_id, + email, + source, + campaign_id, + rating, + feedback_text, + use_case, + ), + ) + conn.commit() + logger.info("Created feedback %s", feedback_id) + return feedback_id + + @staticmethod + def get_feedback( + limit: int = 50, + since: str | None = None, + ) -> list[dict[str, Any]]: + """Get feedback entries. + + Args: + limit: Maximum number of entries to return + since: Optional ISO date to filter by (get entries after this date) + + Returns: + List of feedback entries + """ + with Database.get_connection() as conn: + cursor = conn.cursor() + if since: + cursor.execute( + """ + SELECT * FROM feedback + WHERE created_at >= ? + ORDER BY created_at DESC + LIMIT ? + """, + (since, limit), + ) + else: + cursor.execute( + """ + SELECT * FROM feedback + ORDER BY created_at DESC + LIMIT ? + """, + (limit,), + ) + rows = cursor.fetchall() + return [dict(row) for row in rows] + + @staticmethod + def get_feedback_by_id(feedback_id: str) -> dict[str, Any] | None: + """Get a single feedback entry by ID.""" + with Database.get_connection() as conn: + cursor = conn.cursor() + cursor.execute( + "SELECT * FROM feedback WHERE id = ?", + (feedback_id,), + ) + row = cursor.fetchone() + return dict(row) if row is not None else None + + @staticmethod + def get_feedback_count() -> int: + """Get total count of feedback entries.""" + with Database.get_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT COUNT(*) as count FROM feedback") + return cursor.fetchone()["count"] + @staticmethod def get_metrics_summary() -> dict[str, Any]: """Get aggregate metrics summary for admin dashboard. -- cgit v1.2.3 From a7dcb30c7a465d9fce72b7fc3e605470b2b59814 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Tue, 16 Dec 2025 08:06:09 -0500 Subject: feat(deploy): Complete mini-PaaS deployment system (t-266) - Add Omni/Deploy/ with Manifest, Deployer, Systemd, Caddy modules - Manifest CLI: show, update, add-service, list, rollback commands - Deployer: polls S3 manifest, pulls closures, manages systemd units - Caddy integration for dynamic reverse proxy routes - bild: auto-cache to S3, outputs STORE_PATH for push.sh - push.sh: supports both NixOS and service deploys - Biz.nix: simplified to base OS + deployer only - Services (podcastitlater-web/worker) now deployer-managed - Documentation: README.md with operations guide --- Biz/PodcastItLater/Core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Biz/PodcastItLater/Core.py') diff --git a/Biz/PodcastItLater/Core.py b/Biz/PodcastItLater/Core.py index d0ed2f0..05ed153 100644 --- a/Biz/PodcastItLater/Core.py +++ b/Biz/PodcastItLater/Core.py @@ -1342,7 +1342,7 @@ class Database: # noqa: PLR0904 with Database.get_connection() as conn: cursor = conn.cursor() cursor.execute("SELECT COUNT(*) as count FROM feedback") - return cursor.fetchone()["count"] + return int(cursor.fetchone()["count"]) @staticmethod def get_metrics_summary() -> dict[str, Any]: -- cgit v1.2.3