Skip to content

Commit 5e1a952

Browse files
committed
Changed health_controller, DatabaseConnectionManager and added health_schema. I think I fixed it, it should work if I understand it correctly | [AntonioMrtz#283]
1 parent befa99c commit 5e1a952

File tree

5 files changed

+185
-87
lines changed

5 files changed

+185
-87
lines changed

Backend/app/database/DatabaseConnectionManager.py

+32-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
from pymongo.collection import Collection
44

55
from app.common.app_schema import AppEnvironmentMode
6-
from app.database.database_schema import BaseDatabaseConnection, DatabaseCollection
6+
from app.database.database_schema import (
7+
BaseDatabaseConnection,
8+
DatabaseCollection,
9+
DatabasePingFailedException,
10+
)
711
from app.database.DatabaseProductionConnection import DatabaseProductionConnection
812
from app.database.DatabaseTestingConnection import DatabaseTestingConnection
913
from app.logging.logging_constants import LOGGING_DATABASE_MANAGER
@@ -41,15 +45,38 @@ def get_collection_connection(cls, collection_name: DatabaseCollection) -> Colle
4145
def init_database_connection(
4246
cls, environment: AppEnvironmentMode, connection_uri: str
4347
) -> None:
44-
"""Initializes the database connection and loads its unique instance\
45-
based on current environment value
48+
"""
49+
Initializes the database connection.
4650
4751
Args:
48-
environment (AppEnvironmentMode): the current environment value
49-
connection_uri (str): the database connection uri
52+
environment (AppEnvironmentMode): The current environment mode.
53+
connection_uri (str): The URI for connecting to the database.
54+
55+
Returns:
56+
None
5057
"""
58+
from pymongo import MongoClient
59+
5160
database_connection_class = cls.database_connection_mapping.get(
5261
environment, DatabaseProductionConnection
5362
)
5463
database_connection_class.init_connection(connection_uri)
5564
cls.connection = database_connection_class
65+
cls.client = MongoClient(connection_uri)
66+
67+
@classmethod
68+
def ping_database(cls) -> bool:
69+
"""Pings the database to check the connection
70+
71+
Raises:
72+
DatabasePingFailedException: if ping has failed
73+
74+
Returns:
75+
bool: True if the database is reachable, False otherwise
76+
"""
77+
try:
78+
cls.client.admin.command("ping")
79+
except DatabasePingFailedException:
80+
return False
81+
else:
82+
return True

Backend/app/logging/logging_constants.py

+1
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,4 @@
7575

7676
# Utilities
7777
LOGGING_AUDIO_MANAGEMENT_UTILS = "AUDIO_MANAGEMENT_UTILS"
78+
LOGGING_HEALTH = "HEALTH"
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,121 @@
11
"""
22
Health controller for handling incoming HTTP Requests
33
"""
4-
from fastapi import APIRouter, HTTPException
4+
5+
from fastapi import APIRouter
56
from fastapi.responses import JSONResponse
6-
from pydantic import BaseModel
7-
from typing import Dict
87
from starlette.status import HTTP_200_OK, HTTP_503_SERVICE_UNAVAILABLE
98

109
from app.auth.auth_schema import AuthConfig
10+
from app.database.database_schema import DatabasePingFailedException
1111
from app.database.DatabaseConnectionManager import DatabaseConnectionManager
12-
from app.spotify_electron.song.providers.song_service_provider import SongServiceProvider
13-
from app.logging.logging_schema import SpotifyElectronLogger
1412
from app.logging.logging_constants import LOGGING_HEALTH
13+
from app.logging.logging_schema import SpotifyElectronLogger
14+
from app.spotify_electron.health.health_schema import HealthCheckResponse
15+
from app.spotify_electron.song.providers.song_service_provider import SongServiceProvider
1516

1617
router = APIRouter(prefix="/health", tags=["health"])
1718
logger = SpotifyElectronLogger(LOGGING_HEALTH).getLogger()
1819

19-
class HealthCheckResponse(BaseModel):
20-
status: str
21-
details: Dict[str, Dict[str, str]]
2220

23-
async def check_database_connection() -> Dict[str, str]:
21+
def check_database_connection() -> dict[str, str]:
22+
"""Validates ping to database
23+
24+
Returns:
25+
dict[str, str]: Dictionary with status and explanatory message
26+
"""
2427
try:
25-
db_client = DatabaseConnectionManager.get_database_client()
26-
await db_client.admin.command('ping')
27-
return {"status": "healthy", "message": "Database connection is active"}
28-
except Exception as e:
29-
logger.error(f"Database health check failed: {str(e)}")
30-
return {"status": "unhealthy", "message": f"Database connection failed: {str(e)}"}
31-
32-
def check_song_service() -> Dict[str, str]:
28+
if DatabaseConnectionManager.ping_database():
29+
return {"status": "healthy", "message": "Database connection is active"}
30+
except DatabasePingFailedException:
31+
logger.exception(DatabasePingFailedException.ERROR)
32+
else:
33+
return {"status": "unhealthy", "message": DatabasePingFailedException.ERROR}
34+
35+
36+
def check_song_service() -> dict[str, str]:
37+
"""Validates if SongServiceProvider is inited
38+
39+
Returns:
40+
dict[str, str]: Dictionary with status and explanatory message
41+
"""
3342
try:
34-
service = SongServiceProvider.get_service()
43+
service = SongServiceProvider.get_song_service()
44+
except Exception as exception:
45+
logger.exception("SongServiceProvider health check failed")
46+
return {
47+
"status": "unhealthy",
48+
"message": f"SongServiceProvider check failed: {str(exception)}",
49+
}
50+
else:
3551
if service is None:
36-
return {"status": "unhealthy", "message": "SongService is not initialized"}
37-
return {"status": "healthy", "message": "SongService is properly initialized"}
38-
except Exception as e:
39-
logger.error(f"SongService health check failed: {str(e)}")
40-
return {"status": "unhealthy", "message": f"SongService check failed: {str(e)}"}
52+
return {"status": "unhealthy", "message": "SongServiceProvider is not initialized"}
53+
return {"status": "healthy", "message": "SongServiceProvider is properly initialized"}
54+
55+
56+
def check_auth_config() -> dict[str, str]:
57+
"""Validates if auth configurations are set
4158
42-
def check_auth_config() -> Dict[str, str]:
59+
Returns:
60+
dict[str, str]: Dictionary with status and explanatory message
61+
"""
4362
try:
44-
if not all([
45-
hasattr(AuthConfig, 'SIGNING_SECRET_KEY') and AuthConfig.SIGNING_SECRET_KEY,
46-
hasattr(AuthConfig, 'VERTIFICATION_ALGORITHM') and AuthConfig.VERTIFICATION_ALGORITHM,
47-
hasattr(AuthConfig, 'ACCESS_TOKEN_EXPIRE_MINUTES') and AuthConfig.ACCESS_TOKEN_EXPIRE_MINUTES,
48-
hasattr(AuthConfig, 'DAYS_TO_EXPIRE_COOKIE') and AuthConfig.DAYS_TO_EXPIRE_COOKIE
49-
]):
63+
if not all(
64+
[
65+
hasattr(AuthConfig, "VERTIFICATION_ALGORITHM")
66+
and AuthConfig.VERTIFICATION_ALGORITHM,
67+
hasattr(AuthConfig, "ACCESS_TOKEN_EXPIRE_MINUTES")
68+
and AuthConfig.ACCESS_TOKEN_EXPIRE_MINUTES,
69+
hasattr(AuthConfig, "DAYS_TO_EXPIRE_COOKIE")
70+
and AuthConfig.DAYS_TO_EXPIRE_COOKIE,
71+
]
72+
):
5073
return {
5174
"status": "unhealthy",
52-
"message": "Auth configuration is not fully initialized"
75+
"message": "Auth configuration is not fully initialized",
5376
}
54-
77+
except Exception:
78+
logger.exception("Auth configuration health check failed")
5579
return {
56-
"status": "healthy",
57-
"message": "Auth configuration is properly initialized"
80+
"status": "unhealthy",
81+
"message": "Auth configuration check failed",
5882
}
59-
except Exception as e:
60-
logger.error(f"Auth configuration health check failed: {str(e)}")
61-
return {"status": "unhealthy", "message": f"Auth configuration check failed: {str(e)}"}
83+
else:
84+
return {"status": "healthy", "message": "Auth configuration is properly initialized"}
85+
6286

6387
@router.get(
6488
"/",
6589
response_model=HealthCheckResponse,
6690
summary="Health Check Endpoint",
67-
description="Validates if the app and its critical components are functioning correctly"
91+
description="Validates if the app and its critical components are functioning correctly",
6892
)
6993
async def get_health() -> JSONResponse:
7094
"""Validates if the app has launched correctly and all critical components are healthy
71-
72-
Returns
73-
-------
74-
JSONResponse with health status and details of each component
75-
200 OK if all components are healthy
76-
503 Service Unavailable if any component is unhealthy
95+
96+
Returns:
97+
JSONResponse: health status with details of each component and status code
7798
"""
78-
db_health = await check_database_connection()
99+
db_health = check_database_connection()
79100
service_health = check_song_service()
80101
auth_health = check_auth_config()
81-
102+
82103
health_details = {
83104
"database": db_health,
84105
"song_service": service_health,
85-
"auth_config": auth_health
106+
"auth_config": auth_health,
86107
}
87-
108+
88109
is_healthy = all(check["status"] == "healthy" for check in health_details.values())
89-
110+
90111
response_data = {
91112
"status": "healthy" if is_healthy else "unhealthy",
92-
"details": health_details
113+
"details": health_details,
93114
}
94-
115+
95116
status_code = HTTP_200_OK if is_healthy else HTTP_503_SERVICE_UNAVAILABLE
96-
117+
97118
if not is_healthy:
98119
logger.warning(f"Health check failed: {health_details}")
99-
100-
return JSONResponse(
101-
content=response_data,
102-
status_code=status_code
103-
)
120+
121+
return JSONResponse(content=response_data, status_code=status_code)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""
2+
This module defines the schemas for health-related API responses.
3+
4+
It includes data models and structures used for health checks and
5+
related functionalities in the application.
6+
"""
7+
8+
from pydantic import BaseModel
9+
10+
11+
class HealthCheckResponse(BaseModel):
12+
"""Class that represents response of health check"""
13+
14+
status: str
15+
details: dict[str, dict[str, str]]

0 commit comments

Comments
 (0)