|
1 | 1 | """
|
2 | 2 | Health controller for handling incoming HTTP Requests
|
3 | 3 | """
|
4 |
| -from fastapi import APIRouter, HTTPException |
| 4 | + |
| 5 | +from fastapi import APIRouter |
5 | 6 | from fastapi.responses import JSONResponse
|
6 |
| -from pydantic import BaseModel |
7 |
| -from typing import Dict |
8 | 7 | from starlette.status import HTTP_200_OK, HTTP_503_SERVICE_UNAVAILABLE
|
9 | 8 |
|
10 | 9 | from app.auth.auth_schema import AuthConfig
|
| 10 | +from app.database.database_schema import DatabasePingFailedException |
11 | 11 | 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 |
14 | 12 | 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 |
15 | 16 |
|
16 | 17 | router = APIRouter(prefix="/health", tags=["health"])
|
17 | 18 | logger = SpotifyElectronLogger(LOGGING_HEALTH).getLogger()
|
18 | 19 |
|
19 |
| -class HealthCheckResponse(BaseModel): |
20 |
| - status: str |
21 |
| - details: Dict[str, Dict[str, str]] |
22 | 20 |
|
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 | + """ |
24 | 27 | 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 | + """ |
33 | 42 | 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: |
35 | 51 | 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 |
41 | 58 |
|
42 |
| -def check_auth_config() -> Dict[str, str]: |
| 59 | + Returns: |
| 60 | + dict[str, str]: Dictionary with status and explanatory message |
| 61 | + """ |
43 | 62 | 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 | + ): |
50 | 73 | return {
|
51 | 74 | "status": "unhealthy",
|
52 |
| - "message": "Auth configuration is not fully initialized" |
| 75 | + "message": "Auth configuration is not fully initialized", |
53 | 76 | }
|
54 |
| - |
| 77 | + except Exception: |
| 78 | + logger.exception("Auth configuration health check failed") |
55 | 79 | return {
|
56 |
| - "status": "healthy", |
57 |
| - "message": "Auth configuration is properly initialized" |
| 80 | + "status": "unhealthy", |
| 81 | + "message": "Auth configuration check failed", |
58 | 82 | }
|
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 | + |
62 | 86 |
|
63 | 87 | @router.get(
|
64 | 88 | "/",
|
65 | 89 | response_model=HealthCheckResponse,
|
66 | 90 | 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", |
68 | 92 | )
|
69 | 93 | async def get_health() -> JSONResponse:
|
70 | 94 | """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 |
77 | 98 | """
|
78 |
| - db_health = await check_database_connection() |
| 99 | + db_health = check_database_connection() |
79 | 100 | service_health = check_song_service()
|
80 | 101 | auth_health = check_auth_config()
|
81 |
| - |
| 102 | + |
82 | 103 | health_details = {
|
83 | 104 | "database": db_health,
|
84 | 105 | "song_service": service_health,
|
85 |
| - "auth_config": auth_health |
| 106 | + "auth_config": auth_health, |
86 | 107 | }
|
87 |
| - |
| 108 | + |
88 | 109 | is_healthy = all(check["status"] == "healthy" for check in health_details.values())
|
89 |
| - |
| 110 | + |
90 | 111 | response_data = {
|
91 | 112 | "status": "healthy" if is_healthy else "unhealthy",
|
92 |
| - "details": health_details |
| 113 | + "details": health_details, |
93 | 114 | }
|
94 |
| - |
| 115 | + |
95 | 116 | status_code = HTTP_200_OK if is_healthy else HTTP_503_SERVICE_UNAVAILABLE
|
96 |
| - |
| 117 | + |
97 | 118 | if not is_healthy:
|
98 | 119 | 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) |
0 commit comments