Added path to unsubscribe from RSS feeds and exception handler to propagate FeedErrors

This commit is contained in:
florian committed 2026-10-31 23:34:13 +01:00
1 parent 6ffce24bc7
commit 6f1f024e90
1 file changed
+69 -2
+69 -2
View File
@@ -4,6 +4,7 @@ from contextlib import asynccontextmanager
import uvicorn import uvicorn
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from requests.exceptions import RequestException from requests.exceptions import RequestException
@@ -63,6 +64,27 @@ api = FastAPI(
async def feed_error_handler(request: Request, exc: FeedError): async def feed_error_handler(request: Request, exc: FeedError):
return JSONResponse(status_code=exc.status_code, content={"detail":str(exc)}) return JSONResponse(status_code=exc.status_code, content={"detail":str(exc)})
@api.exception_handler(RequestValidationError)
async def validation_error_handler(request: Request, exc: RequestValidationError):
errors = exc.errors()
# 1) If one of our own FeedErrors caused it, use its message as is
for err in errors:
original = (err.get("ctx") or {}).get("error")
if isinstance(original, FeedError):
return JSONResponse(
status_code=original.status_code,
content={"detail": str(original)},
)
# 2) Anything else (missing field, bad enum value, bad header): flatten to one string
messages = []
for err in errors:
field = ".".join(str(p) for p in err["loc"] if p != "body")
msg = err["msg"].removeprefix("Value error, ")
messages.append(f"{field}: {msg}" if field else msg)
return JSONResponse(status_code=422, content={"detail": "; ".join(messages)})
@api.middleware("http") @api.middleware("http")
async def prometheus_middleware(request: Request, call_next): async def prometheus_middleware(request: Request, call_next):
logger.debug(f"[FastAPI] Incoming request: {request.method} {request.url.path}") logger.debug(f"[FastAPI] Incoming request: {request.method} {request.url.path}")
@@ -319,12 +341,57 @@ def add_feed(
db.commit() db.commit()
except Exception as e: except Exception as e:
db.rollback() db.rollback()
logger.exception(f"[FastAPI] Failed to subscribe to user_id={user_id} to {url}") logger.exception(f"[FastAPI] Failed to subscribe user_id={user_id} to {url}")
raise HTTPException(status_code=500) from e raise HTTPException(status_code=500) from e
logger.info(f"[FastAPI] user_id={user_id} subscribed to feed_id={feed_id}, url={url}") logger.info(f"[FastAPI] user_id={user_id} subscribed to feed_id={feed_id}, url={url}")
return {"status":"ok","detail":"Subscribed to feed","feed_id":feed_id} return {"status":"ok","detail":"Subscribed to feed","feed_url":url}
@user_router.post("/delete/feed")
def delete_feed(
user_id: UserId,
feed_entry: FeedEntry,
db = Depends(get_db),
):
url = feed_entry.rss_feed_url
logger.info(f"[FastAPI] Unsubscribe request: RSS URL={url}, user_id={user_id}")
try:
with db.cursor() as cursor:
cursor.execute(
"SELECT id FROM rss_feeds WHERE rss_feed = %s",
(url,),
)
known = cursor.fetchone()
if known is None:
db.rollback()
return {"status":"ok","detail":"Unsubscribed to feed","feed_url":url}
feed_id = known[0]
cursor.execute(
"UPDATE rss_feed_subscriptions SET active = 0 "
"WHERE user_id = %s AND rss_feed_id = %s",
(user_id, feed_id),
)
cursor.execute(
"SELECT 1 from rss_feed_subscriptions "
"WHERE rss_feed_id = %s AND active = 1 LIMIT 1",
(feed_id,),
)
if cursor.fetchone() is None:
cursor.execute(
"UPDATE rss_feeds SET active = 0 WHERE id = %s",
(feed_id,),
)
db.commit()
except Exception as e:
db.rollback()
logger.exception(f"[FastAPI] Failed to unsubscribe user_id={user_id} to {url}")
raise HTTPException(status_code=500) from e
return {"status":"ok","detail":"Unsubscribed to feed","feed_url":url}
api.include_router(user_router) api.include_router(user_router)
api.include_router(scheduler_router) api.include_router(scheduler_router)