From 7dda7ffe07cf188e52d56b7cb77caed92eaf8a29 Mon Sep 17 00:00:00 2001 From: Florian Date: Sun, 18 Oct 2026 11:32:25 +0200 Subject: [PATCH] Upgraded modules --- pyproject.toml | 2 +- requirements.txt | 50 +++++++++++++++-------------- src/main.py | 49 +++++++++++++++++------------ src/send_notification.py | 68 ---------------------------------------- 4 files changed, 56 insertions(+), 113 deletions(-) delete mode 100644 src/send_notification.py diff --git a/pyproject.toml b/pyproject.toml index 9f8f13d..0cd9699 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ ignore = ["E501"] [tool.ruff.lint.isort] known-first-party = ["db_module", "metrics_server", "rabbitmq_handler", "secret_manager", - "simple_logger_handler", "uvicorn_logger_config", "validator"] + "simple_logger_handler", "uvicorn_logger_config", "validator","send_notifications"] [tool.ruff.format] indent-style = "tab" diff --git a/requirements.txt b/requirements.txt index 5d2da5c..442e985 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,27 +1,29 @@ --extra-index-url https://git.gansejunge.com/api/packages/notifier/pypi/simple/ -annotated-types==0.7.0 -anyio==4.11.0 -certifi==2025.10.5 -charset-normalizer==3.4.3 -click==8.3.0 -fastapi==0.118.2 -feedparser==6.0.12 +annotated-doc==0.0.5 +annotated-types==0.8.0 +anyio==4.15.1 +certifi==2026.7.22 +charset-normalizer==3.5.2 +click==8.5.0 +fastapi==0.143.5 +feedparser==6.0.14 +feedparser-sgmllib==2.1.0 h11==0.16.0 -idna==3.10 -lib-db-module==0.2.0 -lib-secret-manager==0.2.0 -lib-uvicorn-config==0.2.0 +idna==3.20 +lib-db-module==0.3.0 +lib-secret-manager==0.3.1 +lib-send-notifications==0.1.0 mysql-connector-python==9.4.0 -prometheus_client==0.23.1 -pydantic==2.12.0 -pydantic_core==2.41.1 -python-json-logger==4.0.0 -simple-logger-handler==0.1.0 -requests==2.32.5 -sgmllib3k==1.0.0 -sniffio==1.3.1 -starlette==0.48.0 -typing-inspection==0.4.2 -typing_extensions==4.15.0 -urllib3==2.5.0 -uvicorn==0.37.0 +opentelemetry-api==1.45.1 +prometheus_client==0.26.0 +pydantic==2.14.1 +pydantic_core==2.50.1 +python-json-logger==4.2.0 +requests==2.34.2 +simple-logger-handler==0.2.1 +starlette==1.8.0 +typing-inspection==0.4.4 +typing_extensions==4.16.0 +urllib3==2.8.0 +uvicorn==0.54.0 + diff --git a/src/main.py b/src/main.py index 6aa2106..fe44ed0 100644 --- a/src/main.py +++ b/src/main.py @@ -8,22 +8,21 @@ from db_module import close_connection_pool, create_connection_pool, get_db, sta 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 send_notifications import NotificationClient from simple_logger_handler import LOG_LEVEL, setup_logger -from uvicorn_logger_config import LOGGING_CONFIG logger = setup_logger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): - logger.info("[App] Starting application...") - - logger.info("[DB] Creating MySQL connection pool...") + logger.info("[FastAPI] Starting application...") + app.state.notifier = NotificationClient() + logger.info("[FastAPI] Creating MySQL connection pool...") create_connection_pool("royalRoadPool") start_healthcheck_thread() - logger.info("[DB] MySQL healthcheck thread started.") + logger.info("[FastAPI] MySQL healthcheck thread started.") SECRET_PATHS = frozenset({ "/etc/secrets/api_key", @@ -33,9 +32,9 @@ async def lifespan(app: FastAPI): cleanup_secret_files(SECRET_PATHS) yield - logger.info("[App] Closing MySQL connection pool...") + logger.info("[FastAPI] Closing MySQL connection pool...") close_connection_pool() - logger.info("[App] Shutdown complete.") + logger.info("[FastAPI] Shutdown complete.") api = FastAPI( title="Docker Repository Query", @@ -46,22 +45,22 @@ api = FastAPI( @api.middleware("http") async def prometheus_middleware(request: Request, call_next): - logger.debug(f"[Metrics] Incoming request: {request.method} {request.url.path}") + logger.debug(f"[FastAPI] Incoming request: {request.method} {request.url.path}") status = 500 try: response = await call_next(request) status = response.status_code - logger.debug(f"[Metrics] Request processed with status {status}") + logger.debug(f"[FastAPI] Request processed with status {status}") except Exception as e: - logger.error(f"[Metrics] Exception occurred: {e}", exc_info=True) + logger.error(f"[FastAPI] Exception occurred: {e}", exc_info=True) raise finally: REQUEST_COUNTER.labels(request.method, request.url.path, status).inc() - logger.debug(f"[Metrics] Counter incremented for {request.method} {request.url.path} [{status}]") + logger.debug(f"[FastAPI] Counter incremented for {request.method} {request.url.path} [{status}]") return response @api.get("/gitea") -def get_release_information(db = Depends(get_db)): +def get_release_information(request: Request, db = Depends(get_db)): """ Checks for new Gitea releases and updates the database accordingly. @@ -90,14 +89,19 @@ def get_release_information(db = Depends(get_db)): for feed_id, rss_feed, feed_name, last_entry in feeds: results["checked"] += 1 try: - title, link = grab_gitea_release_information(rss_feed) + release_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) + logger.info(f"[FastAPI] Release change for ID {feed_id}: {release_title}") + request.app.state.notifier.send( + title=feed_name, + body=release_title, + link=link, + category="utility" + ) cursor.execute( "UPDATE rss_feeds SET last_entry = %s WHERE id = %s", (link, feed_id), @@ -122,7 +126,7 @@ def get_release_information(db = Depends(get_db)): return results @api.get("/royalroad") -def get_chapters(db = Depends(get_db)): +def get_chapters(request: Request, db = Depends(get_db)): """ Checks for new Royalroad chapters and updates the database accordingly. @@ -158,7 +162,12 @@ def get_chapters(db = Depends(get_db)): continue logger.info(f"[FastAPI] New chapter detected for story ID {feed_id}: {story_title}") - send_notification(story_title, chapter_title, link) + request.app.state.notifier.send( + title=story_title, + body=chapter_title, + link=link, + category="royal-road" + ) cursor.execute( "UPDATE rss_feeds SET last_entry = %s WHERE id = %s", (online_latest_entry, feed_id), @@ -184,8 +193,8 @@ def get_chapters(db = Depends(get_db)): async def start_servers(): logger.info("[Server] Starting main API (port 5000) and metrics server (port 9000)...") - 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) + config_main = uvicorn.Config(api, host="0.0.0.0", port=5000, log_level=LOG_LEVEL.lower(), log_config=None) + config_metrics = uvicorn.Config(metrics_api, host="0.0.0.0", port=9000, log_level=LOG_LEVEL.lower(), log_config=None) server_main = uvicorn.Server(config_main) server_metrics = uvicorn.Server(config_metrics) diff --git a/src/send_notification.py b/src/send_notification.py deleted file mode 100644 index d43f5d8..0000000 --- a/src/send_notification.py +++ /dev/null @@ -1,68 +0,0 @@ -import os -import time - -import requests -from fastapi import HTTPException -from requests.exceptions import ConnectionError, HTTPError, RequestException, Timeout - -from secret_manager import return_credentials -from simple_logger_handler import setup_logger - -backend_api_url=os.getenv("BACKEND_API_URL","localhost:8101/internal/receive-notifications") -api_key= return_credentials("/etc/secrets/api_key") -logger = setup_logger(__name__) - - -def send_notification(title: str, chapter_title: str,link: str,max_retries: int = 5,timeout: int = 5): - """ - Sends a notification to the internal backend service when a new Royalroad chapter is released. - - Parameters: - title: Name of the story. - chapter_title: Title of the chapter. - link: Direct link to the new chapter. - """ - - headers = { - "X-API-Key-Internal": api_key, - "Content-Type": "application/json" - } - - data = { - "user_id": 1, - "message": { - "title": title, - "body": chapter_title, - "link": link, - "category":"royal-road", - "timestamp": int(time.time()) - } - } - - logger.debug(f"[Notify] Preparing to send notification: title='{title}', chapter={chapter_title}, link='{link}'") - - with requests.Session() as session: - for attempt in range(1, max_retries + 1): - try: - logger.debug(f"[Notify] Sending request to backend (attempt {attempt}/{max_retries})") - response = session.post(backend_api_url, headers=headers, json=data, timeout=timeout) - response.raise_for_status() - logger.info(f"[Notify] Notification sent successfully for '{title}' (chapter {chapter_title})") - return response.text - - except (Timeout, ConnectionError) as e: - logger.warning(f"[Notify] Attempt {attempt}/{max_retries} failed: {type(e).__name__}") - if attempt == max_retries: - logger.error(f"[Notify] All retry attempts failed for '{title}'") - raise HTTPException(status_code=503, detail=f"Notification service unavailable: {type(e).__name__}") from e - sleep_time = 2 ** (attempt - 1) - logger.debug(f"[Notify] Retrying in {sleep_time} seconds...") - time.sleep(sleep_time) - - except HTTPError as e: - logger.error(f"[Notify] HTTP {e.response.status_code}: {e.response.text}") - raise HTTPException(status_code=e.response.status_code, detail=e.response.text) from e - - except RequestException as e: - logger.error(f"[Notify] Unexpected request failure: {e}") - raise HTTPException(status_code=500, detail=f"Request failed: {str(e)}") from e -- 2.43.0