Fix #2+#3: Routes API 404 et conflit blueprint name

Bug #2: portal_server.py importait api_v1_bp depuis saas_api_v1 au lieu
de api_v1/__init__.py. Tous les sous-blueprints api_v1/routes/* (health,
courses, predictions, valuebets, backtest, export, metrics, ml_feedback)
n'etaient jamais enregistres -> 404.
Fix: utiliser register_api_v1(app) depuis api_v1/__init__.py.

Bug #3: Conflit de nom de blueprint entre saas_api_v1 et api_v1 (tous
deux nommes api_v1). Renomme le blueprint de saas_api_v1 en saas_api_v1_bp.
Supprime les record_once handlers de saas_api_v1 qui dupliquaient
l'enregistrement de sous-blueprints (billing, org, user, history) -
desormais geres par register_api_v1(app).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
CTO H3R7Tech
2026-05-23 22:57:06 +02:00
parent c072f92794
commit cd4cbcfb48
2 changed files with 17 additions and 72 deletions

View File

@@ -13,7 +13,7 @@ from saas_auth import require_auth
DB_PATH = os.environ.get("TURF_SAAS_DB", "/home/h3r7/turf_saas/turf_saas.db")
api_v1_bp = Blueprint("api_v1", __name__, url_prefix="/api/v1")
saas_api_v1_bp = Blueprint("saas_api_v1", __name__, url_prefix="/api/v1")
def get_db():
@@ -30,7 +30,7 @@ def plan_allows(user_plan: str, required: str) -> bool:
# ─── Stats ────────────────────────────────────────────────────────────────────
@api_v1_bp.route("/stats/summary", methods=["GET"])
@saas_api_v1_bp.route("/stats/summary", methods=["GET"])
@require_auth
def stats_summary():
"""GET /api/v1/stats/summary — résumé dashboard."""
@@ -94,7 +94,7 @@ def stats_summary():
# ─── Predictions ──────────────────────────────────────────────────────────────
@api_v1_bp.route("/predictions/today", methods=["GET"])
@saas_api_v1_bp.route("/predictions/today", methods=["GET"])
@require_auth
def predictions_today():
"""GET /api/v1/predictions/today — prédictions du jour selon le plan."""
@@ -149,7 +149,7 @@ def predictions_today():
return jsonify({"error": str(e), "predictions": []}), 200
@api_v1_bp.route("/predictions/race/<race_label>", methods=["GET"])
@saas_api_v1_bp.route("/predictions/race/<race_label>", methods=["GET"])
@require_auth
def predictions_race(race_label):
"""GET /api/v1/predictions/race/<label> — prédictions d'une course."""
@@ -187,7 +187,7 @@ def predictions_race(race_label):
# ─── Value Bets ───────────────────────────────────────────────────────────────
@api_v1_bp.route("/value-bets/today", methods=["GET"])
@saas_api_v1_bp.route("/value-bets/today", methods=["GET"])
@require_auth
def value_bets_today():
"""GET /api/v1/value-bets/today — value bets (Premium+)."""
@@ -220,7 +220,7 @@ def value_bets_today():
# ─── Export ───────────────────────────────────────────────────────────────────
@api_v1_bp.route("/export/csv", methods=["GET"])
@saas_api_v1_bp.route("/export/csv", methods=["GET"])
@require_auth
def export_csv():
"""GET /api/v1/export/csv — export CSV (Pro only)."""
@@ -257,15 +257,13 @@ def export_csv():
)
# ─── Billing Blueprint (Stripe) + JWT init — HRT-49 ─────────────────────────
# Registers /api/v1/billing/* routes via nested Blueprint (Flask 2.0+)
# Also initializes JWTManager on the Flask app (required for jwt_required_middleware)
# ─── JWT init — HRT-49 ────────────────────────────────────────────────────────
# Initialize JWTManager on the Flask app (required for jwt_required_middleware)
# Called when saas_api_v1_bp is registered (portal_server.py)
try:
from flask_jwt_extended import JWTManager
from api_v1.routes.billing import billing_bp
# Initialize JWTManager on the Flask app when api_v1_bp is registered
@api_v1_bp.record_once
@saas_api_v1_bp.record_once
def _init_jwt(state):
app = state.app
if not app.config.get("JWT_SECRET_KEY"):
@@ -276,57 +274,6 @@ try:
)
if "flask_jwt_extended" not in app.extensions:
JWTManager(app)
# Register billing blueprint with url_prefix='/billing'
# (parent api_v1_bp has '/api/v1', so result is /api/v1/billing/*)
api_v1_bp.register_blueprint(billing_bp, url_prefix="/billing")
print("[saas_api_v1] Billing blueprint (Stripe) + JWT registered ✅")
except Exception as _billing_err:
print(f"[saas_api_v1] Warning: billing blueprint not loaded: {_billing_err}")
# ─── Org Blueprint — HRT-82 ───────────────────────────────────────────────────
# Registers /api/v1/org/* routes (Pro plan only, multi-compte max 5 users)
try:
from api_v1.routes.org import org_bp
@api_v1_bp.record_once
def _register_org_bp(state):
app = state.app
app.register_blueprint(org_bp)
print("[saas_api_v1] Org blueprint (multi-compte Pro) registered ✅")
except Exception as _org_err:
print(f"[saas_api_v1] Warning: org blueprint not loaded: {_org_err}")
# ─── User Blueprint — HRT-79 (Telegram) + HRT-80 (API Token + Webhook) ───────
# Registers /api/v1/user/* routes (Premium+ for telegram, Pro for api-token/webhook)
try:
from api_v1.routes.user import user_bp
from api_v1.routes.user_tokens import user_tokens_bp
@api_v1_bp.record_once
def _register_user_bp(state):
app = state.app
app.register_blueprint(user_bp)
app.register_blueprint(user_tokens_bp)
print('[saas_api_v1] User blueprint (Telegram config + API token + Webhook) registered ✅')
except Exception as _user_err:
print(f'[saas_api_v1] Warning: user blueprints not loaded: {_user_err}')
# ─── History Blueprint — HRT-81 ───────────────────────────────────────────────
# Registers /api/v1/history route (Free:7j, Premium:90j, Pro:illimité)
try:
from api_v1.routes.history import history_bp
@api_v1_bp.record_once
def _register_history_bp(state):
app = state.app
app.register_blueprint(history_bp)
print('[saas_api_v1] History blueprint (plan-limited history) registered ✅')
except Exception as _history_err:
print(f'[saas_api_v1] Warning: history blueprint not loaded: {_history_err}')
print("[saas_api_v1] JWT init registered ✅")
except Exception as _jwt_err:
print(f"[saas_api_v1] Warning: JWT init not loaded: {_jwt_err}")