Feature/gitea release (#4)

Reviewed-on: #4
- Changed RSS feed parser to be generic
- Added Gitea RSS feed parser
- Changed source database table

Co-authored-by: Florian <flo@meergans.org>
This commit was merged in pull request #4.
This commit is contained in:
florian authored and florian committed 2026-10-15 15:07:35 +00:00
1 parent 098248ae18
commit f48ac98638
2 files changed
+184 -64

No files matched your search

+111 -32
View File
@@ -5,8 +5,8 @@ import uvicorn
from fastapi import Depends, FastAPI, HTTPException, Request
from db_module import close_connection_pool, create_connection_pool, get_db, start_healthcheck_thread
from feed_handler import grab_latest_chapter_information
from metrics_server import REQUEST_COUNTER
from feed_handler import grab_gitea_release_information, grab_royal_road_chapter_information
from metrics_server import REQUEST_COUNTER, metrics_api
from secret_manager import cleanup_secret_files
from send_notification import send_notification
from simple_logger_handler import LOG_LEVEL, setup_logger
@@ -60,59 +60,138 @@ async def prometheus_middleware(request: Request, call_next):
logger.debug(f"[Metrics] Counter incremented for {request.method} {request.url.path} [{status}]")
return response
@api.get("/gitea")
def get_release_information(db = Depends(get_db)):
"""
Checks for new Gitea releases and updates the database accordingly.
Behaviour:
- Fetches all active Gitea feeds and their last known release link.
- Queries each feed for its latest release.
- Updates `last_entry` if the latest release link differs from the stored one.
- Sends a notification for each changed release.
- A failure on one feed is logged and the remaining feeds are still checked.
Returns:
A JSON object with checked/new/error counts. If any feed failed,
a 500 is returned with the same counts as the detail.
"""
logger.info("[FastAPI] Checking for new Gitea releases")
results = {"checked": 0, "new": 0, "errors": 0}
with db.cursor() as cursor:
cursor.execute(
"SELECT id, rss_feed, feed_name, last_entry "
"FROM rss_feeds WHERE active = 1 AND type = %s",
("gitea",),
)
feeds = cursor.fetchall()
logger.debug(f"[FastAPI] Found {len(feeds)} active Gitea feeds to check.")
for feed_id, rss_feed, feed_name, last_entry in feeds:
results["checked"] += 1
try:
title, link = grab_gitea_release_information(rss_feed)
logger.debug(f"[FastAPI] ID:{feed_id},RSS Feed {rss_feed}: db={last_entry}, online={link}")
if link == last_entry:
continue
logger.info(f"[FastAPI] Release change for ID {feed_id}: {title}")
send_notification(feed_name, title, link)
cursor.execute(
"UPDATE rss_feeds SET last_entry = %s WHERE id = %s",
(link, feed_id),
)
db.commit()
logger.debug(f"[FastAPI] Notification sent and database updated for Gitea feed ID {feed_id}")
results["new"] += 1
except Exception as e:
db.rollback()
results["errors"] += 1
logger.error(
f"[FastAPI] Failed for feed ID {feed_id} ({rss_feed}): {e}",
exc_info=True,
)
if results["errors"]:
logger.warning(f"[FastAPI] Gitea check finished with errors: {results}")
raise HTTPException(status_code=500, detail=results)
logger.info(f"[FastAPI] Gitea check complete: {results}")
return results
@api.get("/royalroad")
def get_chapters(request: Request, db = Depends(get_db)):
def get_chapters(db = Depends(get_db)):
"""
Checks for new Royalroad chapters and updates the database accordingly.
Behaviour:
- Fetches all active stories and their last known chapter number.
- Fetches all active stories and their last known chapter ID.
- Queries Royalroad for each story's latest chapter.
- Updates `lastChapter` if a new chapter is found.
- Updates `last_entry` if a new chapter is found.
- Sends a notification for any newly discovered chapters.
- A failure on one story is logged and the remaining stories are still checked.
Returns:
A JSON object indicating the check status.
A JSON object with checked/new/error counts. If any story failed,
a 500 is returned with the same counts as the detail.
"""
logger.info("[Royalroad] Checking for new chapters...")
try:
cursor = db.cursor()
cursor.execute("SELECT id, royalroadId, lastChapter FROM stories WHERE active=1")
logger.info("[FastAPI] Checking for new Royalroad chapters")
results = {"checked": 0, "new": 0, "errors": 0}
with db.cursor() as cursor:
cursor.execute(
"SELECT id, rss_feed, last_entry "
"FROM rss_feeds WHERE active = 1 AND type = %s",
("royalroad",),
)
feeds = cursor.fetchall()
logger.debug(f"[FastAPI] Found {len(feeds)} active stories to check.")
stories = cursor.fetchall()
logger.debug(f"[Royalroad] Found {len(stories)} active stories to check.")
for feed_id, rss_feed, last_entry in feeds:
results["checked"] += 1
try:
chapter_title, link, story_title, online_latest_entry = grab_royal_road_chapter_information(rss_feed)
logger.debug(f"[FastAPI] ID:{feed_id},RSS Feed {rss_feed}: db={last_entry}, online={online_latest_entry}")
for id, royalroadId, last_chapter_db in stories:
chapter_title, chapter_link, story_title, chapter_id = grab_latest_chapter_information(royalroadId)
logger.debug(f"[Royalroad] Story {id}: last={last_chapter_db}, latest={chapter_id}")
if online_latest_entry == last_entry:
continue
if chapter_id > last_chapter_db:
logger.info(f"[Royalroad] New chapter detected for story ID {id}: {story_title}")
cursor.execute("UPDATE stories SET lastChapter = %s WHERE id = %s", (chapter_id, id))
logger.info(f"[FastAPI] New chapter detected for story ID {feed_id}: {story_title}")
send_notification(story_title, chapter_title, link)
cursor.execute(
"UPDATE rss_feeds SET last_entry = %s WHERE id = %s",
(online_latest_entry, feed_id),
)
db.commit()
send_notification(story_title, chapter_title, chapter_link)
logger.debug(f"[Royalroad] Notification sent for story ID {id}")
logger.debug(f"[FastAPI] Notification sent and database updated for story ID {feed_id}")
results["new"] += 1
logger.info("[Royalroad] Chapter check completed successfully.")
return {"status": "checked"}
except ValueError as e:
logger.error(f"[Royalroad] Failed to fetch feed {royalroadId}: {e}")
raise HTTPException(status_code=404, detail=str(e)) from e
except Exception as e:
db.rollback()
results["errors"] += 1
logger.error(
f"[Royalroad] Failed for feed ID {feed_id} ({rss_feed}): {e}",
exc_info=True,
)
except Exception as e:
logger.error(f"[Royalroad] Error during chapter check: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e)) from e
if results["errors"]:
logger.warning(f"[FastAPI] Royalroad check finished with errors: {results}")
raise HTTPException(status_code=500, detail=results)
logger.info(f"[FastAPI] Royalroad check complete: {results}")
return results
async def start_servers():
logger.info("[Server] Starting main API (port 5000) and metrics server (port 9000)...")
config_main = uvicorn.Config("main:api", host="0.0.0.0", port=5000, log_level=LOG_LEVEL.lower(), log_config=LOGGING_CONFIG)
config_metrics = uvicorn.Config("metrics_server:metrics_api", host="0.0.0.0", port=9000, log_level=LOG_LEVEL.lower(), log_config=LOGGING_CONFIG)
config_main = uvicorn.Config(api, host="0.0.0.0", port=5000, log_level=LOG_LEVEL.lower(), log_config=LOGGING_CONFIG)
config_metrics = uvicorn.Config(metrics_api, host="0.0.0.0", port=9000, log_level=LOG_LEVEL.lower(), log_config=LOGGING_CONFIG)
server_main = uvicorn.Server(config_main)
server_metrics = uvicorn.Server(config_metrics)
await asyncio.gather(server_main.serve(), server_metrics.serve())
logger.info("[Server] Both servers started successfully.")
logger.info("[Server] Both servers have stopped.")
if __name__ == "__main__":
asyncio.run(start_servers())
asyncio.run(start_servers())