radio/web/api/feeds.py
profit 3d635b742c Initial commit: self-hosted personal radio station
Flask + React web UI with audio player, podcast queue, feed management,
episode browser, music library, schedule viewer, and log tail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 19:01:33 -07:00

128 lines
3.2 KiB
Python

"""Feed management API — CRUD on feeds.yaml."""
import os
import tempfile
import yaml
from flask import Blueprint, jsonify, request
from ..config import FEEDS_CONFIG
from ..db import get_db
bp = Blueprint("feeds", __name__)
def _read_feeds():
if not FEEDS_CONFIG.exists():
return []
with open(FEEDS_CONFIG) as f:
data = yaml.safe_load(f) or {}
return data.get("feeds", [])
def _write_feeds(feeds):
data = {"feeds": feeds}
# Atomic write: temp file then rename
fd, tmp_path = tempfile.mkstemp(
dir=str(FEEDS_CONFIG.parent), suffix=".yaml.tmp"
)
try:
with os.fdopen(fd, "w") as f:
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
os.replace(tmp_path, str(FEEDS_CONFIG))
except Exception:
os.unlink(tmp_path)
raise
@bp.route("/feeds")
def list_feeds():
feeds = _read_feeds()
db = get_db()
result = []
for feed in feeds:
name = feed.get("name", "")
state = db.execute(
"SELECT last_poll FROM feed_state WHERE feed_name=?", (name,)
).fetchone()
ep_count = db.execute(
"SELECT COUNT(*) as c FROM episodes WHERE feed_name=?", (name,)
).fetchone()["c"]
result.append({
**feed,
"last_poll": state["last_poll"] if state else None,
"episode_count": ep_count,
})
return jsonify(result)
@bp.route("/feeds", methods=["POST"])
def add_feed():
data = request.get_json()
if not data or not data.get("name") or not data.get("url"):
return jsonify({"error": "name and url are required"}), 400
feeds = _read_feeds()
# Check for duplicate name
for f in feeds:
if f.get("name") == data["name"]:
return jsonify({"error": "Feed with this name already exists"}), 409
new_feed = {
"name": data["name"],
"url": data["url"],
"enabled": data.get("enabled", True),
"priority": data.get("priority", 10),
"max_episodes": data.get("max_episodes", 20),
}
feeds.append(new_feed)
_write_feeds(feeds)
return jsonify(new_feed), 201
@bp.route("/feeds/<name>", methods=["PUT"])
def update_feed(name):
data = request.get_json()
if not data:
return jsonify({"error": "JSON body required"}), 400
feeds = _read_feeds()
found = False
for feed in feeds:
if feed.get("name") == name:
if "enabled" in data:
feed["enabled"] = data["enabled"]
if "url" in data:
feed["url"] = data["url"]
if "priority" in data:
feed["priority"] = data["priority"]
if "max_episodes" in data:
feed["max_episodes"] = data["max_episodes"]
found = True
break
if not found:
return jsonify({"error": "Feed not found"}), 404
_write_feeds(feeds)
return jsonify({"status": "updated"})
@bp.route("/feeds/<name>", methods=["DELETE"])
def delete_feed(name):
feeds = _read_feeds()
new_feeds = [f for f in feeds if f.get("name") != name]
if len(new_feeds) == len(feeds):
return jsonify({"error": "Feed not found"}), 404
_write_feeds(new_feeds)
return jsonify({"status": "deleted"})