Spaces:
Build error
Build error
| # app.py | |
| import requests | |
| import json | |
| import os | |
| import sys | |
| import io | |
| import uuid | |
| from functools import wraps | |
| from datetime import datetime | |
| from flask import Flask, request, jsonify, Response, session | |
| from flask_cors import CORS | |
| import baserow_storage # Assurez-vous que ceci est présent | |
| import shutil | |
| from flask_socketio import SocketIO | |
| # NOUVEL IMPORT POUR LE STOCKAGE PERSISTANT HUGGING FACE | |
| import huggingface_storage # <-- NOUVEL IMPORT | |
| # NOUVEL IMPORT POUR LA GESTION DU TERMINAL | |
| from terminal_manager import setup_terminal_events | |
| # Importation des modules backend | |
| from auth_backend import ( | |
| get_user_by_id, | |
| login_user, | |
| register_user, | |
| generate_api_key, | |
| get_plan_limit, | |
| reset_password_via_security_question, | |
| generate_password_hash, | |
| ) | |
| from decorators import api_key_required # <-- NOUVEL IMPORT | |
| # Importation des Blueprints | |
| from web_routes import web_bp | |
| from user_routes import user_bp | |
| from billing_routes import billing_bp | |
| from embed_routes import embed_bp | |
| # Ajout d'une constante pour la taille maximale du contenu (16 Mo par défaut) | |
| # Ceci corrige un potentiel NameError sur DEFAULT_MAX_CONTENT_LENGTH | |
| DEFAULT_MAX_CONTENT_LENGTH = 16 * 1024 * 1024 | |
| # Dossier pour le stockage temporaire des fichiers HTML uploadés | |
| # Nous utilisons un dossier 'temp_uploads' à la racine de l'application | |
| UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp_uploads') | |
| if not os.path.exists(UPLOAD_FOLDER): | |
| os.makedirs(UPLOAD_FOLDER) # Créer le dossier s'il n'existe pas | |
| # Extensions de fichiers autorisées (très important pour la sécurité) | |
| ALLOWED_EXTENSIONS = {'html', 'htm'} | |
| # --- Initialisation de l'Application Flask (DÉPLACÉ ICI pour corriger le NameError) --- | |
| app = Flask(__name__) | |
| app.config['HF_DATASET_REPO_ID'] = os.environ.get('HF_DATASET_REPO_ID') | |
| # Maintenant, 'app' est défini et on peut lui appliquer des configurations | |
| app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER | |
| app.config['ALLOWED_EXTENSIONS'] = ALLOWED_EXTENSIONS | |
| from werkzeug.middleware.proxy_fix import ProxyFix | |
| app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1) | |
| # ------------------------------------------------------------------ | |
| # car Hugging Face utilise souvent Gunicorn avec ces workers. | |
| socketio = SocketIO(app, cors_allowed_origins="*") | |
| # --- Configuration des événements SocketIO pour le Terminal --- | |
| setup_terminal_events(socketio) | |
| # Configuration | |
| app.secret_key = os.environ.get("FLASK_SECRET_KEY", "super_secret_dev_key") | |
| app.config['MAX_CONTENT_LENGTH'] = DEFAULT_MAX_CONTENT_LENGTH | |
| # Permettre les requêtes cross-origin (CORS) | |
| CORS(app, supports_credentials=True, origins="*", allow_headers=["Content-Type", "X-User-API-Key"]) | |
| # Permettre les requêtes cross-origin pour l'API | |
| CORS(app) | |
| # --- Enregistrement des Blueprints (Nouveau) --- | |
| app.register_blueprint(web_bp) | |
| app.register_blueprint(user_bp) | |
| app.register_blueprint(billing_bp) | |
| app.register_blueprint(embed_bp) # <-- NOUVEL ENREGISTREMENT | |
| # --- Décorateurs d'Authentification (Conservés) --- | |
| def login_required(f): | |
| def decorated_function(*args, **kwargs): | |
| if 'user_id' not in session: | |
| # Redirection HTTP 302 vers la page de connexion pour les requêtes non-API | |
| if not request.path.startswith('/api/'): | |
| from flask import redirect, url_for | |
| return redirect(url_for('user_bp.connexion')) | |
| # Réponse JSON pour les API | |
| return jsonify({"status": "Error", "message": "Accès non autorisé. Veuillez vous connecter.", "code": "AUTH_REQUIRED"}), 401 | |
| return f(*args, **kwargs) | |
| return decorated_function | |
| # --- Routes d'Authentification (API - Conservées) --- | |
| def register(): | |
| data = request.get_json() | |
| username = data.get("username") | |
| email = data.get("email") | |
| password = data.get("password") | |
| confirm_password = data.get("confirm_password") | |
| security_question = data.get("security_question") | |
| security_answer = data.get("security_answer") | |
| # CORRECTION ICI: Déballage des 3 valeurs retournées par register_user | |
| user_id, message, new_user_data = register_user(username, email, password, confirm_password, security_question, security_answer) | |
| if user_id and new_user_data: # Vérifier l'ID et les données pour s'assurer du succès | |
| session['user_id'] = user_id | |
| # Réponse JSON pour l'API, incluant la clé API | |
| return jsonify({ | |
| "message": message, | |
| "status": "Success", | |
| "user_id": user_id, | |
| # On récupère la clé API directement des données utilisateur | |
| "api_key": new_user_data.get("api_key") | |
| }), 201 | |
| else: | |
| # Échec de l'inscription (message d'erreur de register_user) | |
| return jsonify({"message": message, "status": "Error"}), 400 | |
| def login(): | |
| """ | |
| Route de connexion de l'utilisateur principal. | |
| Prend le nom d'utilisateur/email et le mot de passe. | |
| """ | |
| data = request.get_json() | |
| username = data.get("username") | |
| password = data.get("password") | |
| # CORRECTION DE L'ERREUR : | |
| # Nous déballons maintenant 3 valeurs (ID, Message, Données Utilisateur) | |
| # car la fonction login_user() dans auth_backend.py a été modifiée pour | |
| # retourner les 3 valeurs. | |
| user_id, message, user_data = login_user(username, password) | |
| # Note: user_data est la 3ème valeur (Dict des données utilisateur ou None) | |
| if user_id and user_data: | |
| # La connexion est réussie | |
| session['user_id'] = user_id | |
| # Réponse API avec la clé API de l'utilisateur pour les futures requêtes | |
| return jsonify({ | |
| "message": message, | |
| "status": "Success", | |
| "user_id": user_id, | |
| # On utilise les données utilisateur (user_data) que nous avons déjà récupérées | |
| "api_key": user_data.get("api_key") | |
| }), 200 | |
| else: | |
| # La connexion a échoué (identifiants invalides ou autre erreur) | |
| return jsonify({"message": message, "status": "Error"}), 401 | |
| def logout(): | |
| session.pop('user_id', None) | |
| return jsonify({"message": "Déconnexion réussie.", "status": "Success"}), 200 | |
| def forgot_password_api(): # Renommée pour éviter conflit avec la route HTML | |
| data = request.get_json() | |
| username_or_email = data.get("username_or_email") | |
| security_answer = data.get("security_answer") | |
| new_password = data.get("new_password") | |
| if not username_or_email or not security_answer or not new_password: | |
| return jsonify({"message": "Champs manquants.", "status": "Error"}), 400 | |
| success, message = reset_password_via_security_question(username_or_email, security_answer, new_password) | |
| if success: | |
| return jsonify({ | |
| "message": message, | |
| "status": "Success" | |
| }), 200 | |
| else: | |
| return jsonify({ | |
| "message": message, | |
| "status": "Error" | |
| }), 400 | |
| # --- Routes de Gestion de Compte (API - Conservées) --- | |
| def generate_user_api_key(): | |
| user_id = session.get('user_id') | |
| new_api_key = create_dynamic_api_key() | |
| success, message = update_user_data(user_id, {"api_key": new_api_key}) | |
| if success: | |
| return jsonify({ | |
| "message": "Clé API utilisateur générée et sauvegardée. Conservez-la en lieu sûr. **Utilisez-la via URL simple ('api_key=...') ou en-tête 'X-User-API-Key'.**", | |
| "status": "Success", | |
| "api_key": new_api_key | |
| }), 200 | |
| else: | |
| return jsonify({ | |
| "message": f"Erreur lors de la génération de la clé : {message}", | |
| "status": "Error" | |
| }), 500 | |
| def update_user_info(): | |
| user_id = session.get('user_id') | |
| data = request.get_json() | |
| updates = {} | |
| if 'username' in data: | |
| updates['username'] = data['username'] | |
| if 'email' in data: | |
| updates['email'] = data['email'] | |
| if 'plan' in data: | |
| updates['plan'] = data['plan'] | |
| if not updates: | |
| return jsonify({ | |
| "message": "Aucune information à mettre à jour fournie.", | |
| "status": "Error" | |
| }), 400 | |
| success, message = update_user_data(user_id, updates) | |
| if success: | |
| return jsonify({ | |
| "message": message, | |
| "status": "Success" | |
| }), 200 | |
| else: | |
| return jsonify({ | |
| "message": f"Échec de la mise à jour : {message}", | |
| "status": "Error" | |
| }), 400 | |
| def api_user_info(client_user): | |
| """ | |
| Route API pour récupérer les informations de l'utilisateur principal (client) | |
| à partir de la clé API fournie. | |
| Le 'client_user' est injecté par le décorateur api_key_required. | |
| """ | |
| # client_user est l'objet utilisateur complet injecté par le décorateur | |
| # Sécurité : créer une copie et supprimer les données sensibles avant l'envoi | |
| user_info_safe = client_user.copy() | |
| user_info_safe.pop('password_hash', None) | |
| user_info_safe.pop('security_answer_hash', None) | |
| return jsonify({ | |
| "message": "Informations utilisateur récupérées avec succès.", | |
| "status": "Success", | |
| "user": user_info_safe | |
| }), 200 | |
| def health_check(): | |
| """Vérifie l'état du service en utilisant le statut Baserow.""" | |
| # 1. Tenter d'obtenir le statut Baserow réel | |
| try: | |
| # Appelle la fonction de baserow_storage pour vérifier l'état | |
| health_status = baserow_storage.get_health_status() | |
| # Le statut 'data_storage' est la clé pour le frontend | |
| db_status_message = health_status.get('data_storage', 'Unknown') | |
| # Si la DB est 'operational', on envoie 'Ready' | |
| if db_status_message == 'operational': | |
| data_status = "Ready" | |
| else: | |
| data_status = f"Failed (Baserow: {db_status_message})" | |
| except Exception as e: | |
| # Erreur générale, Baserow inaccessible ou problème de configuration critique | |
| data_status = f"Failed (Exception: {str(e)})" | |
| return jsonify({ | |
| "status": "Online", | |
| "data_storage": data_status | |
| }), 200 | |
| def read_root(): | |
| """Endpoint racine pour le Health Check (Flask version).""" | |
| if baserow_storage.is_baserow_up(): | |
| # Statut OK (200) avec le message | |
| return jsonify({"status": "ok", "message": "Backend and Baserow API are reachable."}), 200 | |
| else: | |
| # Statut de service non disponible (503) avec le message d'erreur | |
| return jsonify({"detail": "Baserow service unavailable (Health Check failed)."}), 503 | |
| # Fonction de nettoyage des fichiers temporaires (utilisée à l'arrêt/redémarrage, etc.) | |
| def shutdown_session(exception=None): | |
| """Supprime les dossiers de déploiement temporaires après la fin de la session, ou lors du nettoyage.""" | |
| # NOTE: Cette approche est simplifiée. Pour la production, vous voudriez | |
| # une tâche en arrière-plan (cron job) pour nettoyer périodiquement les | |
| # vieux dossiers pour éviter les fuites de mémoire. | |
| # Pour l'instant, on se contente de vider le dossier 'temp_uploads' | |
| # Cela devrait être exécuté uniquement si on est sûr que l'application s'arrête. | |
| # Dans un environnement de développement, on pourrait le faire au démarrage, | |
| # mais ce n'est pas fiable en production (Gunicorn/multi-worker). | |
| pass # On laisse la gestion du nettoyage à une tâche externe ou un redémarrage. |