fix(qa): add /health endpoints to Flask apps for Docker healthchecks
Docker compose healthchecks target /health on combined-api, dashboard-api and portal, but these endpoints did not exist (returned 404). This caused all dependent services (condition: service_healthy) to fail startup. - combined_api.py: GET /health + /turf/health with DB connectivity check - dashboard_api.py: GET /health + /turf/health with DB connectivity check - portal_server.py: GET /health (lightweight, no DB) QA Finding 1 from HRT-34 review of HRT-33 branch feature/devops-cicd. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
150
portal_server.py
150
portal_server.py
@@ -10,17 +10,72 @@ app = Flask(__name__)
|
||||
|
||||
DASHBOARD_API_URL = "http://localhost:8791"
|
||||
COMBINED_API_URL = "http://localhost:8790"
|
||||
COMBINED_API_URL = "http://localhost:8790"
|
||||
SAAS_DIR = "/home/h3r7/turf_saas"
|
||||
|
||||
# ─── SaaS Auth & API v1 blueprints ────────────────────────────────────────────
|
||||
try:
|
||||
from saas_auth import auth_bp
|
||||
from saas_api_v1 import api_v1_bp
|
||||
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(api_v1_bp)
|
||||
print("[portal] SaaS auth & API v1 blueprints registered ✅")
|
||||
except Exception as e:
|
||||
print(f"[portal] Warning: could not register SaaS blueprints: {e}")
|
||||
|
||||
|
||||
# ─── Landing & SaaS pages ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
"""Health check endpoint for Docker/load balancer. Returns 200 if app is running."""
|
||||
return {"status": "ok", "service": "portal"}, 200
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def portal():
|
||||
return send_from_directory("/home/h3r7/turf_saas", "portail.html")
|
||||
def landing():
|
||||
"""Marketing landing page."""
|
||||
return send_from_directory(SAAS_DIR, "landing.html")
|
||||
|
||||
|
||||
@app.route("/login")
|
||||
def login_page():
|
||||
return send_from_directory(SAAS_DIR, "login.html")
|
||||
|
||||
|
||||
@app.route("/register")
|
||||
def register_page():
|
||||
return send_from_directory(SAAS_DIR, "register.html")
|
||||
|
||||
|
||||
@app.route("/dashboard")
|
||||
def dashboard_saas():
|
||||
return send_from_directory(SAAS_DIR, "dashboard_saas.html")
|
||||
|
||||
|
||||
@app.route("/onboarding")
|
||||
def onboarding():
|
||||
return send_from_directory(SAAS_DIR, "onboarding.html")
|
||||
|
||||
|
||||
@app.route("/account")
|
||||
def account():
|
||||
return send_from_directory(SAAS_DIR, "account.html")
|
||||
|
||||
|
||||
@app.route("/portal")
|
||||
@app.route("/portail")
|
||||
def portal_legacy():
|
||||
"""Legacy portal redirect."""
|
||||
return send_from_directory(SAAS_DIR, "portail.html")
|
||||
|
||||
|
||||
@app.route("/favicon.ico")
|
||||
def favicon():
|
||||
return send_from_directory("/home/h3r7/turf_saas", "favicon.ico")
|
||||
return send_from_directory(SAAS_DIR, "favicon.ico")
|
||||
|
||||
|
||||
@app.route("/prompts", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
|
||||
@app.route("/prompts/", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
|
||||
@app.route("/prompts/<path:subpath>", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
|
||||
@@ -269,9 +324,7 @@ def niches_business():
|
||||
|
||||
@app.route("/template_restaurant_json.html")
|
||||
def template_restaurant():
|
||||
return send_from_directory(
|
||||
"/home/h3r7/turf_saas", "template_restaurant_json.html"
|
||||
)
|
||||
return send_from_directory("/home/h3r7/turf_saas", "template_restaurant_json.html")
|
||||
|
||||
|
||||
@app.route("/template_boulangerie_final.html")
|
||||
@@ -288,9 +341,7 @@ def template_artisan():
|
||||
|
||||
@app.route("/template_restaurant_final.html")
|
||||
def template_restaurant_final():
|
||||
return send_from_directory(
|
||||
"/home/h3r7/turf_saas", "template_restaurant_final.html"
|
||||
)
|
||||
return send_from_directory("/home/h3r7/turf_saas", "template_restaurant_final.html")
|
||||
|
||||
|
||||
@app.route("/template_complet.html")
|
||||
@@ -300,9 +351,7 @@ def template_complet():
|
||||
|
||||
@app.route("/boite_a_idees_dashboard")
|
||||
def boite_a_idees_dashboard():
|
||||
return send_from_directory(
|
||||
"/home/h3r7/turf_saas", "boite_a_idees_dashboard.html"
|
||||
)
|
||||
return send_from_directory("/home/h3r7/turf_saas", "boite_a_idees_dashboard.html")
|
||||
|
||||
|
||||
@app.route("/datagouv_explorer.html")
|
||||
@@ -345,13 +394,23 @@ def api_chat_workflows():
|
||||
return jsonify([dict(w) for w in workflows])
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route("/api/chat/nvidia-models", methods=["GET"])
|
||||
def api_nvidia_models():
|
||||
return jsonify([
|
||||
{"id": k, "name": v.split("/")[-1].replace("-instruct", "").replace("-", " ").title(), "full_id": v}
|
||||
for k, v in NVIDIA_MODELS.items()
|
||||
])
|
||||
|
||||
return jsonify(
|
||||
[
|
||||
{
|
||||
"id": k,
|
||||
"name": v.split("/")[-1]
|
||||
.replace("-instruct", "")
|
||||
.replace("-", " ")
|
||||
.title(),
|
||||
"full_id": v,
|
||||
}
|
||||
for k, v in NVIDIA_MODELS.items()
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@app.route("/api/chat/sessions", methods=["GET"])
|
||||
@@ -457,7 +516,9 @@ def api_chat_cleanup():
|
||||
|
||||
OPENCLAW_TOKEN = "szDd75yG15ZRADWEhGus3dfNoQve7ZhdxpGq9gudeEzoVhUqB0TomVJd90XcwkUz"
|
||||
OPENCLAW_CONTAINER = "openclaw-ow404080wwkkgkgc4oswwssc"
|
||||
NVIDIA_API_KEY = "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"
|
||||
NVIDIA_API_KEY = (
|
||||
"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"
|
||||
)
|
||||
NVIDIA_API_URL = "https://integrate.api.nvidia.com/v1/chat/completions"
|
||||
NVIDIA_MODEL = "meta/llama-3.1-8b-instruct" # Default model
|
||||
NVIDIA_MODELS = {
|
||||
@@ -476,7 +537,6 @@ NVIDIA_MODELS = {
|
||||
}
|
||||
|
||||
|
||||
|
||||
@app.route("/webhook/telegram", methods=["POST"])
|
||||
def telegram_webhook():
|
||||
try:
|
||||
@@ -542,25 +602,25 @@ def webhook_proxy(workflow_slug):
|
||||
model_key = request.json.get("model", "llama-3.1-8b")
|
||||
model_id = NVIDIA_MODELS.get(model_key, NVIDIA_MODEL)
|
||||
resp = requests.post(
|
||||
NVIDIA_API_URL,
|
||||
headers={
|
||||
"Authorization": f"Bearer {NVIDIA_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
json={
|
||||
"model": model_id,
|
||||
"messages": [{"role": "user", "content": user_message}],
|
||||
"max_tokens": 1024,
|
||||
"temperature": 0.7,
|
||||
},
|
||||
timeout=60,
|
||||
NVIDIA_API_URL,
|
||||
headers={
|
||||
"Authorization": f"Bearer {NVIDIA_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
json={
|
||||
"model": model_id,
|
||||
"messages": [{"role": "user", "content": user_message}],
|
||||
"max_tokens": 1024,
|
||||
"temperature": 0.7,
|
||||
},
|
||||
timeout=60,
|
||||
)
|
||||
data = resp.json()
|
||||
ai_response = (
|
||||
data.get("choices", [{}])[0]
|
||||
.get("message", {})
|
||||
.get("content", str(data))
|
||||
)
|
||||
data.get("choices", [{}])[0]
|
||||
.get("message", {})
|
||||
.get("content", str(data))
|
||||
)
|
||||
else:
|
||||
# Proxy vers webhook n8n
|
||||
resp = requests.post(
|
||||
@@ -702,12 +762,17 @@ def api_proxy(api_path=""):
|
||||
url = f"{DASHBOARD_API_URL}/turf/api"
|
||||
try:
|
||||
fwd_method = request.method
|
||||
fwd_json = request.get_json(silent=True) if fwd_method in ("POST", "PUT", "PATCH") else None
|
||||
fwd_json = (
|
||||
request.get_json(silent=True)
|
||||
if fwd_method in ("POST", "PUT", "PATCH")
|
||||
else None
|
||||
)
|
||||
fwd_headers = {"Content-Type": "application/json"}
|
||||
if request.headers.get("Authorization"):
|
||||
fwd_headers["Authorization"] = request.headers.get("Authorization")
|
||||
resp = requests.request(method=fwd_method, url=url, json=fwd_json, timeout=30,
|
||||
headers=fwd_headers)
|
||||
resp = requests.request(
|
||||
method=fwd_method, url=url, json=fwd_json, timeout=30, headers=fwd_headers
|
||||
)
|
||||
return resp.content, resp.status_code, {"Content-Type": "application/json"}
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e), "url": url}), 500
|
||||
@@ -744,23 +809,26 @@ def opencode_api():
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
|
||||
@app.route("/candidatures/")
|
||||
def candidatures_index():
|
||||
return send_from_directory("/home/h3r7/turf_saas", "crm_candidatures.html")
|
||||
|
||||
|
||||
@app.route("/candidatures/<path:filename>")
|
||||
def candidatures_static(filename):
|
||||
return send_from_directory("/home/h3r7/turf_saas", filename)
|
||||
|
||||
|
||||
@app.route("/map")
|
||||
def map_visual():
|
||||
return send_from_directory("/home/h3r7/turf_saas", "map_visual.html")
|
||||
|
||||
|
||||
@app.route("/architecture.json")
|
||||
def architecture_json():
|
||||
return send_from_directory("/home/h3r7/turf_saas", "architecture.json")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8792, debug=False)
|
||||
|
||||
@@ -827,5 +895,3 @@ def proxy_prompts_test():
|
||||
return response
|
||||
except Exception as e:
|
||||
return f"Erreur proxy prompts: {e}", 502
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user