#!/usr/bin/env python3 """ app_v1.py — Turf SaaS Flask application with versioned API /v1/ This module creates the Flask app, registers: - Auth JWT (from Sprint 2-3) - API v1 blueprints - Swagger/OpenAPI documentation at /api/v1/docs Usage: python app_v1.py # or via gunicorn: gunicorn -w 2 -b 0.0.0.0:8792 app_v1:app Sprint 3-4: HRT-29 — Refacto API /v1/ """ import os import logging from datetime import timedelta from flask import Flask, jsonify from flask_cors import CORS from flask_jwt_extended import JWTManager from flasgger import Swagger from auth_db import init_auth_tables from auth import auth_bp from api_v1 import register_api_v1 logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) logger = logging.getLogger("turf_saas.app_v1") def create_app() -> Flask: """Application factory.""" app = Flask(__name__) # ── CORS ── CORS(app, origins=["*"], methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"]) # ── JWT config ── app.config["JWT_SECRET_KEY"] = os.environ.get( "JWT_SECRET_KEY", "change-me-in-production-use-strong-random-secret" ) app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(minutes=15) app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=30) JWTManager(app) # ── Swagger / OpenAPI ── swagger_config = { "headers": [], "specs": [ { "endpoint": "apispec_v1", "route": "/api/v1/apispec.json", "rule_filter": lambda rule: str(rule).startswith("/api/v1"), "model_filter": lambda tag: True, } ], "static_url_path": "/flasgger_static", "swagger_ui": True, "specs_route": "/api/v1/docs", } swagger_template = { "swagger": "2.0", "info": { "title": "Turf SaaS API", "description": ( "API v1 — Prédictions turf IA, value bets, backtest & métriques.\n\n" "**Plans:** `free` | `premium` | `pro`\n\n" "**Auth:** Bearer JWT — obtenir un token via `POST /api/v1/auth/login`" ), "version": "1.0.0", "contact": {"name": "H3R7 Tech"}, }, "basePath": "/", "schemes": ["http", "https"], "securityDefinitions": { "Bearer": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "Entrer: **Bearer <token>**", } }, "consumes": ["application/json"], "produces": ["application/json"], } Swagger(app, config=swagger_config, template=swagger_template) # ── Auth DB init ── with app.app_context(): try: init_auth_tables() except Exception as e: logger.warning("init_auth_tables warning: %s", e) # ── Register auth blueprint ── app.register_blueprint(auth_bp) # ── Register API v1 blueprints ── register_api_v1(app) # ── Global error handlers ── @app.errorhandler(404) def not_found_handler(e): return jsonify( {"status": "error", "message": "Route introuvable", "code": 404} ), 404 @app.errorhandler(405) def method_not_allowed_handler(e): return jsonify( {"status": "error", "message": "Méthode non autorisée", "code": 405} ), 405 @app.errorhandler(500) def internal_error_handler(e): logger.exception("Unhandled 500 error") return jsonify( {"status": "error", "message": "Erreur serveur interne", "code": 500} ), 500 logger.info("Turf SaaS API v1 ready — docs at /api/v1/docs") return app app = create_app() if __name__ == "__main__": port = int(os.environ.get("PORT", 8792)) app.run(host="0.0.0.0", port=port, debug=False)