- Fix health check sqlExe calls: add missing ns parameter - Fix accounting ID generation: use getID() instead of uuid4 (VARCHAR length) - Fix accounting request_id: normalize empty to NULL to avoid UNIQUE constraint violation - Fix test_server balance/update route: was incorrectly pointing to accounting handler - Add test_server.py: standalone aiohttp test server for local development - Update conf/config.yaml: local MySQL credentials (test/test) - Update db/schema.sql and scripts/generate_ddl.py for local testing - Fix router sync_status sqlExe call: add missing ns parameter - Fix sync uapi_sync: use correct table/column names
110 lines
3.3 KiB
Python
110 lines
3.3 KiB
Python
"""Health check API handler.
|
|
|
|
Provides endpoints for service health and readiness checks.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import time
|
|
from typing import Any
|
|
|
|
from appPublic.log import debug, error
|
|
from sqlor.dbpools import DBPools
|
|
from ahserver.serverenv import ServerEnv
|
|
|
|
_START_TIME = time.time()
|
|
|
|
|
|
async def health_check() -> str:
|
|
"""Basic health check - returns service status."""
|
|
uptime = time.time() - _START_TIME
|
|
|
|
result = {
|
|
'status': 'ok',
|
|
'service': 'sageapi',
|
|
'uptime_seconds': round(uptime, 1),
|
|
'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S%z'),
|
|
}
|
|
return json.dumps(result, ensure_ascii=False, default=str)
|
|
|
|
|
|
async def readiness_check() -> str:
|
|
"""Readiness check - verifies database connectivity."""
|
|
result: dict[str, Any] = {
|
|
'status': 'unknown',
|
|
'checks': {},
|
|
}
|
|
|
|
# Check cache database connection
|
|
try:
|
|
env = ServerEnv()
|
|
dbname = env.get_module_dbname('sageapi')
|
|
if not dbname:
|
|
result['checks']['cache_db'] = {
|
|
'status': 'fail',
|
|
'error': 'No database configured for sageapi module',
|
|
}
|
|
else:
|
|
async with DBPools().sqlorContext(dbname) as sor:
|
|
rows = await sor.sqlExe('SELECT 1 as ping', {})
|
|
result['checks']['cache_db'] = {
|
|
'status': 'ok',
|
|
'dbname': dbname,
|
|
}
|
|
except Exception as e:
|
|
error(f'readiness_check cache_db error: {e}')
|
|
result['checks']['cache_db'] = {
|
|
'status': 'fail',
|
|
'error': str(e),
|
|
}
|
|
|
|
# Check Sage database connection
|
|
try:
|
|
from sqlor.dbpools import get_sor_context
|
|
async with get_sor_context(env, 'sage') as sor:
|
|
rows = await sor.sqlExe('SELECT 1 as ping', {})
|
|
result['checks']['sage_db'] = {'status': 'ok'}
|
|
except Exception as e:
|
|
error(f'readiness_check sage_db error: {e}')
|
|
result['checks']['sage_db'] = {
|
|
'status': 'fail',
|
|
'error': str(e),
|
|
}
|
|
|
|
# Check sync state
|
|
try:
|
|
async with DBPools().sqlorContext(dbname) as sor:
|
|
sql = """
|
|
SELECT entity_type, sync_status, last_sync_time
|
|
FROM sync_state
|
|
ORDER BY last_sync_time DESC
|
|
"""
|
|
rows = await sor.sqlExe(sql, {})
|
|
if isinstance(rows, list):
|
|
result['checks']['sync_status'] = {
|
|
'status': 'ok',
|
|
'entities': [
|
|
{
|
|
'entity_type': r.get('entity_type', ''),
|
|
'sync_status': r.get('sync_status', ''),
|
|
'last_sync_time': str(r.get('last_sync_time', '')),
|
|
}
|
|
for r in rows
|
|
],
|
|
}
|
|
except Exception as e:
|
|
result['checks']['sync_status'] = {
|
|
'status': 'fail',
|
|
'error': str(e),
|
|
}
|
|
|
|
# Overall status
|
|
all_ok = all(
|
|
check.get('status') == 'ok'
|
|
for check in result['checks'].values()
|
|
)
|
|
result['status'] = 'ready' if all_ok else 'degraded'
|
|
|
|
return json.dumps(result, ensure_ascii=False, default=str)
|