Feature/add or remove #7

Merged
florian merged 6 commits from feature/add-or-remove into main 2026-11-01 08:49:48 +00:00
Showing only changes of commit 6f1f024e90 - Show all commits
+69 -2
View File
@@ -4,6 +4,7 @@ from contextlib import asynccontextmanager
import uvicorn
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from requests.exceptions import RequestException
@@ -63,6 +64,27 @@ api = FastAPI(
async def feed_error_handler(request: Request, exc: FeedError):
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")
async def prometheus_middleware(request: Request, call_next):
logger.debug(f"[FastAPI] Incoming request: {request.method} {request.url.path}")
@@ -319,12 +341,57 @@ def add_feed(
db.commit()
except Exception as e:
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
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(scheduler_router)