Feature/windows notifications #7
No files matched your search
@@ -5,10 +5,13 @@ annotated-types==0.7.0
|
|||||||
anyio==4.11.0
|
anyio==4.11.0
|
||||||
argon2-cffi==25.1.0
|
argon2-cffi==25.1.0
|
||||||
argon2-cffi-bindings==25.1.0
|
argon2-cffi-bindings==25.1.0
|
||||||
|
certifi==2026.7.22
|
||||||
cffi==2.0.0
|
cffi==2.0.0
|
||||||
click==8.3.0
|
click==8.3.0
|
||||||
fastapi==0.119.0
|
fastapi==0.119.0
|
||||||
h11==0.16.0
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httpx==0.28.1
|
||||||
idna==3.11
|
idna==3.11
|
||||||
lib-db-module==0.2.0
|
lib-db-module==0.2.0
|
||||||
lib-secret-manager==0.2.0
|
lib-secret-manager==0.2.0
|
||||||
|
|||||||
+57
-3
@@ -3,7 +3,7 @@ from fastapi.responses import JSONResponse
|
|||||||
from fastapi.security.api_key import APIKeyHeader
|
from fastapi.security.api_key import APIKeyHeader
|
||||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, Field
|
||||||
from validator import verify_api_key
|
from validator import verify_api_key
|
||||||
from db_module import get_db, create_connection_pool, close_connection_pool, start_healthcheck_thread
|
from db_module import get_db, create_connection_pool, close_connection_pool, start_healthcheck_thread
|
||||||
from simple_logger_handler import setup_logger, LOG_LEVEL
|
from simple_logger_handler import setup_logger, LOG_LEVEL
|
||||||
@@ -14,15 +14,29 @@ from metrics_server import REQUEST_COUNTER
|
|||||||
import asyncio
|
import asyncio
|
||||||
from uvicorn_logger_config import LOGGING_CONFIG
|
from uvicorn_logger_config import LOGGING_CONFIG
|
||||||
from secret_manager import cleanup_secret_files
|
from secret_manager import cleanup_secret_files
|
||||||
|
import httpx
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
logger = setup_logger(__name__)
|
logger = setup_logger(__name__)
|
||||||
api_key_header_internal = APIKeyHeader(name="X-API-Key-Internal")
|
api_key_header_internal = APIKeyHeader(name="X-API-Key-Internal")
|
||||||
|
PULL_SERVICE_URL = os.getenv(
|
||||||
|
"BACKEND_PULL_NOTIFICATIONS_URL",
|
||||||
|
"http://backend-pull-notifications.app-notifications.svc.cluster.local:8101",
|
||||||
|
)
|
||||||
|
MAX_PAGES = 5
|
||||||
|
|
||||||
class Notification(BaseModel):
|
class Notification(BaseModel):
|
||||||
receipent_user_id : int
|
receipent_user_id : int = Field(gt=0) #ToDo fix among all services
|
||||||
message : Dict
|
message : Dict
|
||||||
|
|
||||||
|
class PullResponse(BaseModel):
|
||||||
|
messages: list[dict]
|
||||||
|
count: int
|
||||||
|
has_more: bool
|
||||||
|
|
||||||
|
class PullRequest(BaseModel):
|
||||||
|
user_id: int = Field(gt=0)
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
@@ -41,6 +55,12 @@ async def lifespan(app: FastAPI):
|
|||||||
app.state.rmq_producer = producer
|
app.state.rmq_producer = producer
|
||||||
logger.info("[FastAPI] RabbitMQ producer initialized.")
|
logger.info("[FastAPI] RabbitMQ producer initialized.")
|
||||||
|
|
||||||
|
app.state.http_client = httpx.AsyncClient (
|
||||||
|
base_url=PULL_SERVICE_URL,
|
||||||
|
timeout=httpx.Timeout(10.0,connect=3.0)
|
||||||
|
)
|
||||||
|
logger.info("[FastAPI] HTTP client initialized.")
|
||||||
|
|
||||||
SECRET_PATHS = frozenset({
|
SECRET_PATHS = frozenset({
|
||||||
"/etc/secrets/db_username",
|
"/etc/secrets/db_username",
|
||||||
"/etc/secrets/db_password",
|
"/etc/secrets/db_password",
|
||||||
@@ -57,6 +77,9 @@ async def lifespan(app: FastAPI):
|
|||||||
logger.info("Closing MySQL connection pool...")
|
logger.info("Closing MySQL connection pool...")
|
||||||
close_connection_pool()
|
close_connection_pool()
|
||||||
|
|
||||||
|
await app.state.http_client.aclose()
|
||||||
|
logger.info("[FastAPI] Closing Http client")
|
||||||
|
|
||||||
api = FastAPI(
|
api = FastAPI(
|
||||||
title="Internal Notifier API",
|
title="Internal Notifier API",
|
||||||
description="API to forward messages to RabbitMQ",
|
description="API to forward messages to RabbitMQ",
|
||||||
@@ -104,7 +127,7 @@ async def custom_http_exception_handler(request,exc):
|
|||||||
content={"detail": exc.detail}
|
content={"detail": exc.detail}
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.post("/internal/receive-notifications")
|
@api.post("/internal/receive-notifications") #ToDo fix among all services
|
||||||
async def receive_notifications(
|
async def receive_notifications(
|
||||||
notification_data: Notification,
|
notification_data: Notification,
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -121,6 +144,37 @@ async def receive_notifications(
|
|||||||
logger.info("Successfully queued for delivery to RMQ")
|
logger.info("Successfully queued for delivery to RMQ")
|
||||||
return {"status": "queued"}
|
return {"status": "queued"}
|
||||||
|
|
||||||
|
@api.post("/internal/pull-notifications", response_model=PullResponse)
|
||||||
|
async def pull_notifications(
|
||||||
|
pull_request: PullRequest,
|
||||||
|
request: Request,
|
||||||
|
program_name: str = Depends(verify_api_key_dependency_internal),
|
||||||
|
):
|
||||||
|
user_id = pull_request.user_id
|
||||||
|
logger.debug(f"[FastAPI] User id {user_id} is requesting messages")
|
||||||
|
client: httpx.AsyncClient = request.app.state.http_client
|
||||||
|
|
||||||
|
messages: list[dict] = []
|
||||||
|
has_more = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
for _ in range(MAX_PAGES):
|
||||||
|
resp = await client.post("/internal/pull-notifications", json={"user_id": user_id})
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = PullResponse.model_validate(resp.json())
|
||||||
|
|
||||||
|
messages.extend(data.messages)
|
||||||
|
has_more = data.has_more
|
||||||
|
if not has_more:
|
||||||
|
break
|
||||||
|
except (httpx.HTTPError, ValueError) as e:
|
||||||
|
logger.error(f"[FastAPI] Pull service failed for user_id={user_id}: {e!r}")
|
||||||
|
status = 504 if isinstance(e, httpx.TimeoutException) else 502
|
||||||
|
raise HTTPException(status_code=status, detail="Notification service unavailable")
|
||||||
|
|
||||||
|
logger.info(f"[FastAPI] Returning {len(messages)} message(s) for user_id={user_id}, has_more={has_more}")
|
||||||
|
return PullResponse(messages=messages, count=len(messages), has_more=has_more)
|
||||||
|
|
||||||
async def start_servers():
|
async def start_servers():
|
||||||
logger.debug("Starting FastAPI and metrics servers")
|
logger.debug("Starting FastAPI and metrics servers")
|
||||||
config_main = uvicorn.Config("main:api", host="0.0.0.0", port=8101, log_level=LOG_LEVEL.lower(), log_config=LOGGING_CONFIG)
|
config_main = uvicorn.Config("main:api", host="0.0.0.0", port=8101, log_level=LOG_LEVEL.lower(), log_config=LOGGING_CONFIG)
|
||||||
|
|||||||
Reference in new issue
Block a user