#!/usr/bin/env python3 from flask import Flask, send_from_directory, jsonify, request, make_response import os import json import requests import subprocess import db from middleware import rate_limit_middleware, access_log_middleware app = Flask(__name__) rate_limit_middleware(app) access_log_middleware(app) DASHBOARD_API_URL = "http://localhost:8791" 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 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(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/", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) def proxy_prompts(subpath=""): PROMPTS_API_URL = "http://localhost:8781" full_url = PROMPTS_API_URL + ("/" + subpath if subpath else "/") if request.query_string: full_url += "?" + request.query_string.decode() try: headers = { k: v for k, v in request.headers if k.lower() not in ("host", "content-length", "transfer-encoding", "connection") } raw_body = request.get_data() resp = requests.request( request.method, full_url, headers=headers, data=raw_body, cookies=request.cookies, allow_redirects=False, timeout=10, ) if resp.status_code in (301, 302, 303, 307, 308): location = resp.headers.get("Location", "") if location.startswith("/") and not location.startswith("/prompts"): location = "/prompts" + location r = make_response(b"", resp.status_code) r.headers["Location"] = location return r response = make_response(resp.content, resp.status_code) for k, v in resp.headers.items(): if k.lower() not in ("content-encoding", "transfer-encoding", "connection"): response.headers[k] = v return response except Exception as e: return f"Erreur proxy prompts: {e}", 502 @app.route("/business") def business(): return send_from_directory("/home/h3r7/depenses_trello/templates", "business.html") @app.route( "/depenses", methods=["GET", "POST", "PUT", "DELETE", "PATCH"], strict_slashes=False ) @app.route( "/depenses/", methods=["GET", "POST", "PUT", "DELETE", "PATCH"], strict_slashes=False, ) @app.route( "/depenses/", methods=["GET", "POST", "PUT", "DELETE", "PATCH"] ) def proxy_depenses(subpath=""): backend_url = "http://localhost:8769" if subpath: full_url = f"{backend_url}/{subpath}" else: full_url = backend_url if request.query_string: full_url += "?" + request.query_string.decode() try: print(f"[DEPENSES PROXY] {request.method} {full_url}") # Transmettre les headers bruts sans reparser headers = { k: v for k, v in request.headers if k.lower() not in ("host", "content-length", "transfer-encoding", "connection") } # Body brut transmis tel quel — Content-Type préservé raw_body = request.get_data() resp = requests.request( request.method, full_url, headers=headers, data=raw_body, cookies=request.cookies, allow_redirects=False, timeout=10, ) print(f"[DEPENSES PROXY] resp={resp.status_code}") if resp.status_code in (301, 302, 303, 307, 308): location = resp.headers.get("Location", "") if location.startswith("/") and not location.startswith("/depenses"): location = "/depenses" + location response = make_response(b"", resp.status_code) response.headers["Location"] = location response.headers["Content-Length"] = "0" return response if resp.status_code >= 400: return resp.content, resp.status_code response = make_response(resp.content, resp.status_code) for k, v in resp.headers.items(): if k.lower() not in ("content-encoding", "transfer-encoding", "connection"): response.headers[k] = v return response except Exception as e: return f"Erreur proxy depenses: {e}", 502 @app.route("/skills", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) @app.route("/skills/", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) @app.route("/skills/", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) def proxy_skills(subpath=""): SKILLS_API_URL = "http://localhost:8772" full_url = SKILLS_API_URL + ("/" + subpath if subpath else "/") if request.query_string: full_url += "?" + request.query_string.decode() try: headers = { k: v for k, v in request.headers if k.lower() not in ("host", "content-length", "transfer-encoding", "connection") } raw_body = request.get_data() resp = requests.request( request.method, full_url, headers=headers, data=raw_body, cookies=request.cookies, allow_redirects=False, timeout=10, ) if resp.status_code in (301, 302, 303, 307, 308): location = resp.headers.get("Location", "") if location.startswith("/") and not location.startswith("/skills"): location = "/skills" + location r = make_response(b"", resp.status_code) r.headers["Location"] = location return r response = make_response(resp.content, resp.status_code) for k, v in resp.headers.items(): if k.lower() not in ("content-encoding", "transfer-encoding", "connection"): response.headers[k] = v return response except Exception as e: return f"Erreur proxy skills: {e}", 502 @app.route("/crm", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) @app.route("/crm/", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) @app.route("/crm/", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) def proxy_crm(subpath=""): CRM_API_URL = "http://localhost:8770" full_url = CRM_API_URL + ("/" + subpath if subpath else "/") if request.query_string: full_url += "?" + request.query_string.decode() try: headers = { k: v for k, v in request.headers if k.lower() not in ("host", "content-length", "transfer-encoding", "connection") } raw_body = request.get_data() resp = requests.request( request.method, full_url, headers=headers, data=raw_body, cookies=request.cookies, allow_redirects=False, timeout=10, ) if resp.status_code in (301, 302, 303, 307, 308): location = resp.headers.get("Location", "") if location.startswith("/") and not location.startswith("/crm"): location = "/crm" + location r = make_response(b"", resp.status_code) r.headers["Location"] = location return r response = make_response(resp.content, resp.status_code) for k, v in resp.headers.items(): if k.lower() not in ("content-encoding", "transfer-encoding", "connection"): response.headers[k] = v return response except Exception as e: return f"Erreur proxy crm: {e}", 502 @app.route("/gitea", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) @app.route("/gitea/", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) @app.route("/gitea/", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) def proxy_gitea(subpath=""): GITEA_API_URL = "http://localhost:3000" full_url = GITEA_API_URL + ("/" + subpath if subpath else "/") if request.query_string: full_url += "?" + request.query_string.decode() try: headers = { k: v for k, v in request.headers if k.lower() not in ("host", "content-length", "transfer-encoding", "connection") } raw_body = request.get_data() resp = requests.request( request.method, full_url, headers=headers, data=raw_body, cookies=request.cookies, allow_redirects=False, timeout=10, ) if resp.status_code in (301, 302, 303, 307, 308): location = resp.headers.get("Location", "") if location.startswith("/") and not location.startswith("/gitea"): location = "/gitea" + location r = make_response(b"", resp.status_code) r.headers["Location"] = location return r response = make_response(resp.content, resp.status_code) for k, v in resp.headers.items(): if k.lower() not in ("content-encoding", "transfer-encoding", "connection"): response.headers[k] = v return response except Exception as e: return f"Erreur proxy gitea: {e}", 502 @app.route("/boite_a_idees.html") def boite_a_idees(): return send_from_directory("/home/h3r7/depenses_trello", "boite_a_idees.html") @app.route("/niches_business.html") def niches_business(): return send_from_directory("/home/h3r7/depenses_trello/templates", "business.html") @app.route("/template_restaurant_json.html") def template_restaurant(): return send_from_directory("/home/h3r7/turf_saas", "template_restaurant_json.html") @app.route("/template_boulangerie_final.html") def template_boulangerie(): return send_from_directory( "/home/h3r7/turf_saas", "template_boulangerie_final.html" ) @app.route("/template_artisan_final.html") def template_artisan(): return send_from_directory("/home/h3r7/turf_saas", "template_artisan_final.html") @app.route("/template_restaurant_final.html") def template_restaurant_final(): return send_from_directory("/home/h3r7/turf_saas", "template_restaurant_final.html") @app.route("/template_complet.html") def template_complet(): return send_from_directory("/home/h3r7/turf_saas", "template_complet.html") @app.route("/boite_a_idees_dashboard") def boite_a_idees_dashboard(): return send_from_directory("/home/h3r7/turf_saas", "boite_a_idees_dashboard.html") @app.route("/datagouv_explorer.html") def datagouv_explorer(): return send_from_directory("/home/h3r7/turf_saas", "datagouv_explorer.html") @app.route("/api_datagouv_reference.html") def api_datagouv_reference(): return send_from_directory("/home/h3r7/turf_saas", "api_datagouv_reference.html") # Agent IA - Page principale @app.route("/agent-ia") @app.route("/agent-ia/") def agent_ia(): return send_from_directory("/home/h3r7/turf_saas", "agent_ia.html") # Agent IA - Page de config @app.route("/agent-ia/config") @app.route("/agent-ia/config/") def agent_ia_config(): return send_from_directory("/home/h3r7/turf_saas", "agent_ia_config.html") # Ancienne page (compatibilité) @app.route("/agent-ia/legacy") def agent_ia_legacy(): return send_from_directory("/home/h3r7/turf_saas", "gemini_agent.html") # --- API Chat --- @app.route("/api/chat/workflows", methods=["GET"]) def api_chat_workflows(): try: workflows = db.get_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() ] ) @app.route("/api/chat/sessions", methods=["GET"]) def api_chat_sessions(): workflow_slug = request.args.get("workflow") if not workflow_slug: return jsonify({"error": "Paramètre workflow requis"}), 400 try: sessions = db.get_sessions(workflow_slug) return jsonify([dict(s) for s in sessions]) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route("/api/chat/history", methods=["GET"]) def api_chat_history(): session_id = request.args.get("session_id") workflow_slug = request.args.get("workflow") if not session_id or not workflow_slug: return jsonify({"error": "session_id et workflow requis"}), 400 try: messages = db.get_messages(session_id, workflow_slug) return jsonify([dict(m) for m in messages]) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route("/api/chat/search", methods=["GET"]) def api_chat_search(): query = request.args.get("q") workflow_slug = request.args.get("workflow") if not query or not workflow_slug: return jsonify({"error": "q et workflow requis"}), 400 try: results = db.search_messages(query, workflow_slug) return jsonify([dict(r) for r in results]) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route("/api/chat/session", methods=["POST"]) def api_chat_session_create(): data = request.json or {} session_id = data.get("session_id") workflow_slug = data.get("workflow") title = data.get("title") if not session_id or not workflow_slug: return jsonify({"error": "session_id et workflow requis"}), 400 try: workflows = db.get_workflows() wf = next((w for w in workflows if w["slug"] == workflow_slug), None) if not wf: return jsonify({"error": "Workflow introuvable"}), 404 db.create_session(session_id, wf["id"], title) return jsonify({"status": "ok", "session_id": session_id}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route("/api/chat/session", methods=["PUT"]) def api_chat_session_rename(): data = request.json or {} session_id = request.args.get("session_id") workflow_slug = request.args.get("workflow") new_title = data.get("title") if not session_id or not workflow_slug: return jsonify({"error": "session_id et workflow requis"}), 400 try: db.rename_session(session_id, workflow_slug, new_title) return jsonify({"status": "ok"}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route("/api/chat/session", methods=["DELETE"]) def api_chat_session_delete(): session_id = request.args.get("session_id") workflow_slug = request.args.get("workflow") if not session_id or not workflow_slug: return jsonify({"error": "session_id et workflow requis"}), 400 try: deleted = db.delete_session(session_id, workflow_slug) return jsonify({"status": "ok", "deleted": deleted}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route("/api/chat/cleanup", methods=["POST"]) def api_chat_cleanup(): data = request.json or {} days = data.get("days") workflow_slug = data.get("workflow") if not days or not workflow_slug: return jsonify({"error": "days et workflow requis"}), 400 try: deleted = db.delete_messages_before(days, workflow_slug) return jsonify({"status": "ok", "deleted": deleted}) except Exception as e: return jsonify({"error": str(e)}), 500 # --- Webhook proxy avec persistance --- OPENCLAW_TOKEN = "szDd75yG15ZRADWEhGus3dfNoQve7ZhdxpGq9gudeEzoVhUqB0TomVJd90XcwkUz" OPENCLAW_CONTAINER = "openclaw-ow404080wwkkgkgc4oswwssc" 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 = { "llama-3.1-8b": "meta/llama-3.1-8b-instruct", "llama-3.1-70b": "meta/llama-3.1-70b-instruct", "llama-3.3-70b": "meta/llama-3.3-70b-instruct", "llama-4-scout": "meta/llama-4-scout-17b-16e-instruct", "nemotron-mini": "nvidia/nemotron-mini-4b-instruct", "nemotron-super": "nvidia/llama-3.3-nemotron-super-49b-v1", "mistral-small": "mistralai/mistral-small-3.1-24b-instruct-2503", "mistral-medium": "mistralai/mistral-medium-3-instruct", "mistral-large": "mistralai/mistral-large-3-675b-instruct-2512", "qwen-coder": "qwen/qwen3-coder-480b-a35b-instruct", "gemma-3": "google/gemma-3-27b-it", "deepseek": "deepseek-ai/deepseek-v3.2", } @app.route("/webhook/telegram", methods=["POST"]) def telegram_webhook(): try: data = request.get_data() resp = requests.post( "http://localhost:5003/webhook", data=data, headers={"Content-Type": "application/json"}, timeout=30, ) return resp.content, resp.status_code except Exception as e: return jsonify({"error": str(e)}), 500 @app.route("/webhook/", methods=["POST"]) def webhook_proxy(workflow_slug): try: workflows = db.get_workflows() wf = next((w for w in workflows if w["slug"] == workflow_slug), None) if not wf: return jsonify({"error": f'Workflow "{workflow_slug}" introuvable'}), 404 session_id = request.headers.get("X-Session-ID", "default") user_message = request.json.get("message", "") db.create_session(session_id, wf["id"]) db.save_message(session_id, wf["id"], "user", user_message) mode = wf.get("mode", "n8n") if mode == "direct": # OpenClaw gateway bind sur 127.0.0.1 uniquement → docker exec import subprocess escaped_msg = user_message.replace('"', '\\"').replace("\n", "\\n") cmd = [ "docker", "exec", OPENCLAW_CONTAINER, "curl", "-s", "http://127.0.0.1:18789/v1/chat/completions", "-H", "Content-Type: application/json", "-H", f"Authorization: Bearer {OPENCLAW_TOKEN}", "-d", f'{{"model":"openclaw","messages":[{{"role":"user","content":"{escaped_msg}"}}],"max_tokens":4096,"temperature":0.7}}', ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) if result.returncode != 0: raise Exception(f"docker exec failed: {result.stderr}") data = json.loads(result.stdout) ai_response = ( data.get("choices", [{}])[0] .get("message", {}) .get("content", str(data)) ) elif mode == "nvidia": # Appel direct a l API Nvidia NIM # Recuperer le modele choisi (par defaut: llama-3.1-8b) 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, ) data = resp.json() ai_response = ( data.get("choices", [{}])[0] .get("message", {}) .get("content", str(data)) ) else: # Proxy vers webhook n8n resp = requests.post( wf["webhook_url"], headers={ "Content-Type": "application/json", "X-Session-ID": session_id, }, json=request.json, timeout=60, ) ai_response = resp.text db.save_message(session_id, wf["id"], "ai", ai_response) return ai_response, 200, {"Content-Type": "text/plain; charset=utf-8"} except Exception as e: return str(e), 500 # --- Anciens webhooks (compatibilité) --- @app.route("/webhook/gemini-agent", methods=["POST"]) def webhook_proxy_legacy_gemini(): return webhook_proxy("llm-models") @app.route("/webhook/openclaw-web", methods=["POST"]) def webhook_proxy_legacy_openclaw(): return webhook_proxy("openclaw") TELEGRAM_TOKEN = "8649773134:AAFqzZVtSHfPPFDadcte1B-1h23nZ8DmdYE" @app.route("/webhook/telegram-opencode", methods=["POST"]) def webhook_proxy_telegram(): """Handle Telegram messages directly""" try: data = request.get_json(force=True, silent=True) print(f"[TELEGRAM] Raw: {request.data}") print(f"[TELEGRAM] Remote: {request.remote_addr}") print(f"[TELEGRAM] Headers: {request.headers.get('X-Forwarded-For', 'none')}") if not data: return jsonify({"error": "No data", "raw": request.data.decode()}), 400 # Accept both direct message and Telegram update format message = data.get("message") or data.get("update", {}).get("message") if not message: return jsonify({"data": data, "keys": list(data.keys())}), 400 chat_id = message["chat"]["id"] text = message.get("text", "") user_id = message["from"]["id"] if text.startswith("/start"): reply = "🤖 *OpencdPilot Bot*\n\nPilotez OpenCode depuis Telegram!" elif text.startswith("/help"): reply = "*Commandes:*\n/start - Démarrer\n/help - Aide\n/status - Statut" elif text.startswith("/status"): reply = "✅ *Système actif*\n- OpenCode: OK\n- Bot: OK" else: # Call OpenCode API opencode_resp = requests.post( "http://localhost:8792/api/opencode", json={"prompt": text}, timeout=180 ) opencode_data = opencode_resp.json() reply = opencode_data.get("output", opencode_data.get("error", "Erreur"))[ :4000 ] # Send reply requests.post( f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage", json={"chat_id": chat_id, "text": reply, "parse_mode": "Markdown"}, ) return jsonify({"ok": True}) except Exception as e: print(f"[TELEGRAM] Error: {e}") return jsonify({"error": str(e)}), 500 # --- Turf Dashboard --- @app.route("/dashboard") @app.route("/dashboard.html") def dashboard(): return send_from_directory("/home/h3r7/turf_saas", "dashboard_system.html") @app.route("/turf/") @app.route("/turf") def turf_index(): return send_from_directory("/home/h3r7/turf_saas", "dashboard.html") @app.route("/turf/") def turf_static(filename): return send_from_directory("/home/h3r7/turf_saas", filename) # --- POD Routes --- @app.route("/pod/") @app.route("/pod/") def pod_static(filename=""): return send_from_directory( "/home/h3r7/turf_saas/POD", filename if filename else "pod_manager.html" ) @app.route("/turf/api") @app.route("/turf/api/") @app.route("/turf/api/") def api_proxy(api_path=""): # Routes servies par combined_api.py (port 8790) : # backtest, stats, paris, parisroi, races, scores, report, ask, brave-search, # execute-sql, send-email, vitesse, n8n-proxy, predictions_analysis, ideas # Fix HRT-73 : alignement complet avec turf_scraper fix #23 COMBINED_ROUTES = ( "backtest", "stats", "parisroi", "paris", "predictions_analysis", "vitesse", "n8n-proxy", "races", "race/", "scores", "ask", "brave-search", "execute-sql", "send-email", "report", "ideas", ) if any(api_path.startswith(r) for r in COMBINED_ROUTES): url = f"{COMBINED_API_URL}/turf/api/{api_path}" elif api_path.startswith("scoring"): url = f"{DASHBOARD_API_URL}/turf/api/{api_path}" elif api_path: url = f"{DASHBOARD_API_URL}/turf/api/{api_path}" else: 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 ) # Forwarder Authorization header (combined_api.py exige Basic h3r7:h3r7 pour parisroi/paris) fwd_headers = {"Content-Type": "application/json"} incoming_auth = request.headers.get("Authorization") if incoming_auth: fwd_headers["Authorization"] = incoming_auth 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 @app.route("/api/opencode", methods=["POST"]) def opencode_api(): """Execute OpenCode commands via API""" try: data = request.get_json() prompt = data.get("prompt", "") if not prompt: return jsonify({"error": "No prompt provided"}), 400 # Execute opencode with the wrapper script result = subprocess.run( ["/home/h3r7/opencode_wrapper.sh", prompt], cwd="/home/h3r7", capture_output=True, text=True, timeout=180, ) return jsonify( { "output": result.stdout, "error": result.stderr, "returncode": result.returncode, } ) except subprocess.TimeoutExpired: return jsonify({"error": "Timeout"}), 504 except Exception as e: 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/") 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) # Telegram Bot Webhook Proxy def telegram_webhook(): """Proxy Telegram webhook to bot service""" try: data = request.get_data() resp = requests.post( "http://localhost:5003/webhook", data=data, headers={"Content-Type": "application/json"}, timeout=30, ) return resp.content, resp.status_code except Exception as e: return jsonify({"error": str(e)}), 500 PROMPTS_API_URL = "http://localhost:8781" @app.route("/testprompts", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) def proxy_prompts_test(): return "TEST_PROMPTS_OK" full_url = PROMPTS_API_URL + ("/" + subpath if subpath else "/") if request.query_string: full_url += "?" + request.query_string.decode() try: headers = { k: v for k, v in request.headers if k.lower() not in ( "host", "content-length", "transfer-encoding", "connection", "authorization", ) } raw_body = request.get_data() resp = requests.request( request.method, full_url, headers=headers, data=raw_body, cookies=request.cookies, allow_redirects=False, timeout=10, ) if resp.status_code in (301, 302, 303, 307, 308): location = resp.headers.get("Location", "") if location.startswith("/") and not location.startswith("/prompts"): location = "/prompts" + location r = make_response(b"", resp.status_code) r.headers["Location"] = location return r response = make_response(resp.content, resp.status_code) for k, v in resp.headers.items(): if k.lower() not in ("content-encoding", "transfer-encoding", "connection"): response.headers[k] = v return response except Exception as e: return f"Erreur proxy prompts: {e}", 502