Compare commits
No commits in common. "550d405935afc1892ba28dad1ee34bf3efc617f2" and "b452c9f5938a3b1a3723b7a8eac84af25b016b20" have entirely different histories.
550d405935
...
b452c9f593
@ -25,13 +25,6 @@ services:
|
|||||||
- .env.dev
|
- .env.dev
|
||||||
environment:
|
environment:
|
||||||
- REDIS_HOST=redis-dev
|
- REDIS_HOST=redis-dev
|
||||||
- REDIS_DB_DEV=0
|
|
||||||
- BACKEND_DEV_MODE=true
|
|
||||||
- APP_ENV=development
|
|
||||||
- ENVIRONMENT=development
|
|
||||||
- START_MODE=dev
|
|
||||||
- CC_COMPOSE_PROJECT=api-dev
|
|
||||||
- CC_COMPOSE_SERVICE=backend-dev
|
|
||||||
- RUN_INIT=false
|
- RUN_INIT=false
|
||||||
- INIT_MODE=infra
|
- INIT_MODE=infra
|
||||||
ports:
|
ports:
|
||||||
@ -52,13 +45,6 @@ services:
|
|||||||
- .env.dev
|
- .env.dev
|
||||||
environment:
|
environment:
|
||||||
- REDIS_HOST=redis-dev
|
- REDIS_HOST=redis-dev
|
||||||
- REDIS_DB_DEV=0
|
|
||||||
- BACKEND_DEV_MODE=true
|
|
||||||
- APP_ENV=development
|
|
||||||
- ENVIRONMENT=development
|
|
||||||
- START_MODE=dev
|
|
||||||
- CC_COMPOSE_PROJECT=api-dev
|
|
||||||
- CC_COMPOSE_SERVICE=backend-test
|
|
||||||
- API_HEALTH_URL=http://192.168.0.64:18000/health
|
- API_HEALTH_URL=http://192.168.0.64:18000/health
|
||||||
depends_on:
|
depends_on:
|
||||||
redis-dev:
|
redis-dev:
|
||||||
|
|||||||
@ -46,11 +46,6 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
- START_MODE=prod
|
|
||||||
- APP_ENV=production
|
|
||||||
- ENVIRONMENT=production
|
|
||||||
- CC_COMPOSE_PROJECT=api
|
|
||||||
- CC_COMPOSE_SERVICE=backend
|
|
||||||
- RUN_INIT=${RUN_INIT:-false} # Set to 'true' to run init on startup
|
- RUN_INIT=${RUN_INIT:-false} # Set to 'true' to run init on startup
|
||||||
- INIT_MODE=${INIT_MODE:-infra} # Which init tasks to run
|
- INIT_MODE=${INIT_MODE:-infra} # Which init tasks to run
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@ -98,13 +98,11 @@ if [ "$RUN_INIT" = "true" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Start the server (unless init-only mode). Default remains production, but
|
# Start the production server (unless init-only mode)
|
||||||
# development Compose can set START_MODE=dev so cc-api-dev reports and uses
|
|
||||||
# development Redis/config instead of silently booting as prod.
|
|
||||||
START_MODE="${START_MODE:-prod}"
|
|
||||||
if [ "$1" != "init-only" ] && [ -z "$INIT_ONLY" ]; then
|
if [ "$1" != "init-only" ] && [ -z "$INIT_ONLY" ]; then
|
||||||
print_status "Starting ${START_MODE} server..."
|
print_status "Starting production server..."
|
||||||
exec ./start.sh "$START_MODE"
|
exec ./start.sh prod
|
||||||
else
|
else
|
||||||
print_status "Init-only mode - not starting server"
|
print_status "Init-only mode - not starting server"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
60
main.py
60
main.py
@ -10,7 +10,6 @@ logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH
|
|||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
import uvicorn
|
import uvicorn
|
||||||
import requests
|
import requests
|
||||||
from urllib.parse import urlparse
|
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
from modules.database.tools.neo4j_driver_tools import get_driver
|
from modules.database.tools.neo4j_driver_tools import get_driver
|
||||||
|
|
||||||
@ -23,59 +22,15 @@ from modules.queue_system import ServiceType
|
|||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
setup_cors(app)
|
setup_cors(app)
|
||||||
|
|
||||||
def _truthy_env(name: str) -> bool:
|
|
||||||
return os.getenv(name, "").lower() in {"1", "true", "yes", "on"}
|
|
||||||
|
|
||||||
|
|
||||||
def _runtime_environment() -> str:
|
|
||||||
"""Return the API runtime role used for dev/prod backing-service selection."""
|
|
||||||
start_mode = os.getenv("START_MODE", "prod").lower()
|
|
||||||
if start_mode == "dev" or _truthy_env("BACKEND_DEV_MODE"):
|
|
||||||
return "dev"
|
|
||||||
return "prod"
|
|
||||||
|
|
||||||
|
|
||||||
def _url_host(url: Optional[str]) -> Optional[str]:
|
|
||||||
if not url:
|
|
||||||
return None
|
|
||||||
parsed = urlparse(url)
|
|
||||||
return parsed.hostname
|
|
||||||
|
|
||||||
|
|
||||||
def _runtime_identity() -> Dict[str, Any]:
|
|
||||||
"""Non-secret runtime identity for agents and smoke tests.
|
|
||||||
|
|
||||||
This intentionally exposes only modes, labels, and URL hosts. It must not
|
|
||||||
include API keys, passwords, bearer tokens, or full URLs that may embed
|
|
||||||
credentials.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"api_runtime_role": _runtime_environment(),
|
|
||||||
"start_mode": os.getenv("START_MODE", "prod"),
|
|
||||||
"app_environment": os.getenv("APP_ENV"),
|
|
||||||
"environment": os.getenv("ENVIRONMENT"),
|
|
||||||
"backend_dev_mode": _truthy_env("BACKEND_DEV_MODE"),
|
|
||||||
"compose_project": os.getenv("COMPOSE_PROJECT_NAME") or os.getenv("CC_COMPOSE_PROJECT"),
|
|
||||||
"compose_service": os.getenv("COMPOSE_SERVICE") or os.getenv("CC_COMPOSE_SERVICE"),
|
|
||||||
"supabase_url_host": _url_host(os.getenv("SUPABASE_URL")),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Health check endpoint
|
# Health check endpoint
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
async def health_check() -> Dict[str, Any]:
|
async def health_check() -> Dict[str, Any]:
|
||||||
"""Health check endpoint that verifies all service dependencies"""
|
"""Health check endpoint that verifies all service dependencies"""
|
||||||
runtime_identity = _runtime_identity()
|
|
||||||
health_status = {
|
health_status = {
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"runtime": runtime_identity,
|
|
||||||
"services": {
|
"services": {
|
||||||
"neo4j": {"status": "healthy", "message": "Connected"},
|
"neo4j": {"status": "healthy", "message": "Connected"},
|
||||||
"supabase": {
|
"supabase": {"status": "healthy", "message": "Connected"},
|
||||||
"status": "healthy",
|
|
||||||
"message": "Connected",
|
|
||||||
"url_host": runtime_identity["supabase_url_host"],
|
|
||||||
},
|
|
||||||
"redis": {"status": "healthy", "message": "Connected"}
|
"redis": {"status": "healthy", "message": "Connected"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,10 +45,9 @@ async def health_check() -> Dict[str, Any]:
|
|||||||
}
|
}
|
||||||
health_status["status"] = "unhealthy"
|
health_status["status"] = "unhealthy"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Neo4j health check failed: {e}")
|
|
||||||
health_status["services"]["neo4j"] = {
|
health_status["services"]["neo4j"] = {
|
||||||
"status": "unhealthy",
|
"status": "unhealthy",
|
||||||
"message": "Error checking Neo4j"
|
"message": f"Error checking Neo4j: {str(e)}"
|
||||||
}
|
}
|
||||||
health_status["status"] = "unhealthy"
|
health_status["status"] = "unhealthy"
|
||||||
|
|
||||||
@ -113,10 +67,9 @@ async def health_check() -> Dict[str, Any]:
|
|||||||
}
|
}
|
||||||
health_status["status"] = "unhealthy"
|
health_status["status"] = "unhealthy"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Supabase health check failed: {e}")
|
|
||||||
health_status["services"]["supabase"] = {
|
health_status["services"]["supabase"] = {
|
||||||
"status": "unhealthy",
|
"status": "unhealthy",
|
||||||
"message": "Error checking Supabase Auth API"
|
"message": f"Error checking Supabase Auth API: {str(e)}"
|
||||||
}
|
}
|
||||||
health_status["status"] = "unhealthy"
|
health_status["status"] = "unhealthy"
|
||||||
|
|
||||||
@ -124,8 +77,8 @@ async def health_check() -> Dict[str, Any]:
|
|||||||
# Check Redis using new Redis manager
|
# Check Redis using new Redis manager
|
||||||
from modules.redis_manager import get_redis_manager
|
from modules.redis_manager import get_redis_manager
|
||||||
|
|
||||||
# Determine environment from explicit startup/runtime identity.
|
# Determine environment
|
||||||
environment = runtime_identity["api_runtime_role"]
|
environment = 'dev' if os.getenv('BACKEND_DEV_MODE', 'true').lower() == 'true' else 'prod'
|
||||||
redis_manager = get_redis_manager(environment)
|
redis_manager = get_redis_manager(environment)
|
||||||
|
|
||||||
# Get comprehensive health check
|
# Get comprehensive health check
|
||||||
@ -143,10 +96,9 @@ async def health_check() -> Dict[str, Any]:
|
|||||||
health_status["status"] = "unhealthy"
|
health_status["status"] = "unhealthy"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Redis health check failed: {e}")
|
|
||||||
health_status["services"]["redis"] = {
|
health_status["services"]["redis"] = {
|
||||||
"status": "unhealthy",
|
"status": "unhealthy",
|
||||||
"message": "Error checking Redis"
|
"message": f"Error checking Redis: {str(e)}"
|
||||||
}
|
}
|
||||||
health_status["status"] = "unhealthy"
|
health_status["status"] = "unhealthy"
|
||||||
|
|
||||||
|
|||||||
@ -38,21 +38,8 @@ def test_dev_api_health_endpoint_is_healthy():
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
payload = response.json()
|
payload = response.json()
|
||||||
assert payload['status'] == 'healthy'
|
assert payload['status'] == 'healthy'
|
||||||
|
|
||||||
runtime = payload['runtime']
|
|
||||||
assert runtime['api_runtime_role'] == 'dev'
|
|
||||||
assert runtime['start_mode'] == 'dev'
|
|
||||||
assert runtime['app_environment'] == 'development'
|
|
||||||
assert runtime['environment'] == 'development'
|
|
||||||
assert runtime['backend_dev_mode'] is True
|
|
||||||
assert runtime['compose_project'] == 'api-dev'
|
|
||||||
assert runtime['supabase_url_host'] == '192.168.0.94'
|
|
||||||
|
|
||||||
assert payload['services']['supabase']['status'] == 'healthy'
|
assert payload['services']['supabase']['status'] == 'healthy'
|
||||||
assert payload['services']['supabase']['url_host'] == '192.168.0.94'
|
|
||||||
assert payload['services']['redis']['status'] == 'healthy'
|
assert payload['services']['redis']['status'] == 'healthy'
|
||||||
assert payload['services']['redis']['environment'] == 'dev'
|
|
||||||
assert payload['services']['redis']['database'] == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_supabase_dev_seed_core_counts():
|
def test_supabase_dev_seed_core_counts():
|
||||||
@ -64,19 +51,3 @@ def test_supabase_dev_seed_core_counts():
|
|||||||
def test_supabase_dev_seed_timetable_counts():
|
def test_supabase_dev_seed_timetable_counts():
|
||||||
assert _rest_count('classes') == 17
|
assert _rest_count('classes') == 17
|
||||||
assert _rest_count('taught_lessons') == 1462
|
assert _rest_count('taught_lessons') == 1462
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_identity_does_not_expose_secret_values():
|
|
||||||
health_url = os.getenv('API_HEALTH_URL', 'http://192.168.0.64:18000/health')
|
|
||||||
response = requests.get(health_url, timeout=15)
|
|
||||||
assert response.status_code == 200
|
|
||||||
payload_text = response.text
|
|
||||||
for secret_name in ('SERVICE_ROLE_KEY', 'ANON_KEY', 'SUPABASE_JWT_SECRET', 'REDIS_PASSWORD'):
|
|
||||||
secret_value = os.getenv(secret_name)
|
|
||||||
if secret_value:
|
|
||||||
assert secret_value not in payload_text
|
|
||||||
|
|
||||||
runtime = response.json()['runtime']
|
|
||||||
assert 'supabase_url' not in runtime
|
|
||||||
assert 'service_role_key' not in runtime
|
|
||||||
assert 'anon_key' not in runtime
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user