ernestmindres commited on
Commit
f490d20
·
verified ·
1 Parent(s): cd61f79

Upload 6 files

Browse files
Files changed (6) hide show
  1. .dockerignore +20 -0
  2. Dockerfile +36 -0
  3. app.py +120 -0
  4. auth_backend.py +76 -0
  5. git_storage.py +100 -0
  6. requirements.txt +6 -0
.dockerignore ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fichiers invisibles de versionnement et d'IDE
2
+ .git
3
+ .gitattributes
4
+ .gitignore
5
+ .DS_Store
6
+
7
+ # Exclure le Dockerfile et le .dockerignore lui-même (bonne pratique)
8
+ Dockerfile
9
+ .dockerignore
10
+
11
+ # Exclure les dossiers de cache Python
12
+ __pycache__/
13
+ *.pyc
14
+
15
+ # Exclure les dossiers de développement/log
16
+ venv/ # Si vous avez un environnement virtuel
17
+ env/ # Si vous avez un autre nom d'environnement virtuel
18
+ logs/ # Dossiers de logs
19
+ tmp/ # Dossiers temporaires
20
+ *.log
Dockerfile ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile
2
+
3
+ # Utiliser une image Python officielle comme base
4
+ FROM python:3.11-slim
5
+
6
+ # Définir des variables d'environnement
7
+ # Le port 5000 sera exposé et utilisé comme PORT par défaut dans app.py
8
+ ENV PORT 7860
9
+ ENV FLASK_APP app.py
10
+
11
+ # Créer un répertoire de travail
12
+ WORKDIR /app
13
+
14
+ # Créer un utilisateur non-root "user" avec l'UID 1000
15
+ RUN useradd -m -u 1000 user
16
+
17
+ # Copier les fichiers d'application et changer le propriétaire
18
+ COPY --chown=user:user requirements.txt .
19
+ COPY --chown=user:user app.py .
20
+ COPY --chown=user:user auth_backend.py .
21
+ COPY --chown=user:user git_storage.py .
22
+
23
+
24
+ # Installer les dépendances
25
+ RUN pip install --no-cache-dir -r requirements.txt
26
+
27
+ # Passer à l'utilisateur non-root
28
+ USER user
29
+
30
+ # Exposer le port par défaut (même si Hugging Face gère la redirection)
31
+ EXPOSE 7860
32
+
33
+ # Commande pour démarrer l'application avec Gunicorn (recommandé pour la production)
34
+ # L'application écoutera sur toutes les interfaces (0.0.0.0) sur le port 5000 (défini par ENV PORT)
35
+ # --workers 4 est un bon point de départ, ajustez au besoin.
36
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860 ", "--workers", "4", "app:app"]
app.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+
3
+ from flask import Flask, request, jsonify, session, g, redirect, url_for
4
+ from flask_cors import CORS
5
+ from functools import wraps
6
+ import os
7
+ from dotenv import load_dotenv # <-- CONSERVÉ au cas où, mais n'est pas appelé
8
+
9
+ # Charger les variables d'environnement du fichier .env
10
+ # load_dotenv() # <-- SUPPRIMÉ pour utiliser directement les variables d'environnement du Space
11
+
12
+ from auth_backend import register_user, login_user, get_user_by_id
13
+ # Note: conversation_backend n'est pas utilisé ici, mais serait pour les routes de conversation
14
+
15
+ app = Flask(__name__)
16
+
17
+ # Configuration de la clé secrète pour les sessions
18
+ # C'est important en production, assurez-vous de la définir dans .env ou comme Secret
19
+ app.secret_key = os.environ.get("FLASK_SECRET_KEY", "super_secret_dev_key")
20
+
21
+ # Permettre les requêtes cross-origin (CORS)
22
+ CORS(app, supports_credentials=True)
23
+
24
+ # Décorateur pour vérifier si l'utilisateur est connecté
25
+ def login_required(f):
26
+ @wraps(f)
27
+ def decorated_function(*args, **kwargs):
28
+ if 'user_id' not in session:
29
+ return jsonify({"status": "Error", "message": "Accès non autorisé. Veuillez vous connecter."}), 401
30
+ return f(*args, **kwargs)
31
+ return decorated_function
32
+
33
+ # Route de vérification de l'état (Statut du service)
34
+ @app.route('/', methods=['GET'])
35
+ def index():
36
+ user_id = session.get('user_id')
37
+ is_logged_in = user_id is not None
38
+ user_status = "Connecté" if is_logged_in else "Déconnecté (Invité)"
39
+
40
+ return jsonify({
41
+ "status": "OK",
42
+ "is_logged_in": is_logged_in,
43
+ "message": f"Bienvenue sur le Backend API ErnestMind. Statut de la session: {user_status}."
44
+ })
45
+
46
+ # --- Routes d'Authentification ---
47
+ @app.route('/api/register', methods=['POST'])
48
+ def api_register():
49
+ data = request.get_json()
50
+ email = data.get('email')
51
+ password = data.get('password')
52
+
53
+ if not email or not password:
54
+ return jsonify({"status": "Error", "message": "Email et mot de passe sont requis."}), 400
55
+
56
+ user_id, message = register_user(email, password)
57
+
58
+ if user_id:
59
+ # Inscription réussie et connexion automatique
60
+ session['user_id'] = user_id
61
+ return jsonify({"status": "Success", "message": message, "user_id": user_id}), 201
62
+ else:
63
+ return jsonify({"status": "Error", "message": message}), 400
64
+
65
+ @app.route('/api/login', methods=['POST'])
66
+ def api_login():
67
+ data = request.get_json()
68
+ email = data.get('email')
69
+ password = data.get('password')
70
+
71
+ if not email or not password:
72
+ return jsonify({"status": "Error", "message": "Email et mot de passe sont requis."}), 400
73
+
74
+ user_id, message = login_user(email, password)
75
+
76
+ if user_id:
77
+ # Connexion réussie, définir la session
78
+ session['user_id'] = user_id
79
+ return jsonify({"status": "Success", "message": message, "user_id": user_id}), 200
80
+ else:
81
+ return jsonify({"status": "Error", "message": message}), 401 # 401 Unauthorized
82
+
83
+ @app.route('/api/logout', methods=['POST'])
84
+ def api_logout():
85
+ # Retirer l'utilisateur de la session
86
+ session.pop('user_id', None)
87
+ return jsonify({"status": "Success", "message": "Déconnexion réussie."}), 200
88
+
89
+ # --- Routes Protégées (Exemple) ---
90
+ @app.route('/api/profile', methods=['GET'])
91
+ @login_required
92
+ def api_profile():
93
+ user_id = session['user_id']
94
+ user_data = get_user_by_id(user_id)
95
+
96
+ if user_data:
97
+ # On ne renvoie pas le mot de passe !
98
+ profile = {
99
+ "user_id": user_data["user_id"],
100
+ "email": user_data["email"],
101
+ "created_at": user_data["created_at"],
102
+ "message": "Bienvenue sur votre profil."
103
+ }
104
+ return jsonify({"status": "Success", "profile": profile}), 200
105
+
106
+ # Cas où l'ID est dans la session mais l'utilisateur n'existe pas dans le JSON
107
+ session.pop('user_id', None)
108
+ return jsonify({"status": "Error", "message": "Profil introuvable, session réinitialisée."}), 404
109
+
110
+
111
+ if __name__ == '__main__':
112
+ # Le port est configuré ici pour le développement
113
+ # Pour Hugging Face Spaces Docker, l'application doit écouter sur le port 7860
114
+ # ou celui spécifié par la variable d'environnement PORT,
115
+ # qui peut être lue via os.environ.get("PORT", 7860) ou similaire.
116
+ # Ici, nous conservons la logique initiale avec 5000 par défaut
117
+ # et nous laisserons le Dockerfile s'assurer que le port 5000 est exposé.
118
+ port = int(os.environ.get("PORT", 5000))
119
+ # Note: On utilise le mode debug qui est idéal pour le développement
120
+ app.run(debug=True, host='0.0.0.0', port=port)
auth_backend.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # auth_backend.py
2
+
3
+ import json
4
+ import uuid
5
+ from datetime import datetime
6
+ from werkzeug.security import generate_password_hash, check_password_hash
7
+ from git_storage import (
8
+ load_users_data,
9
+ save_users_data,
10
+ create_user_data_structure,
11
+ )
12
+
13
+ # Fonction pour valider l'email
14
+ def is_valid_email(email):
15
+ # Une validation simple
16
+ return "@" in email and "." in email
17
+
18
+ # Fonction d'inscription de l'utilisateur
19
+ def register_user(email, password):
20
+ # 1. Validation de base
21
+ if not is_valid_email(email) or len(password) < 8:
22
+ return None, "Email invalide ou mot de passe trop court (min 8 caractères)."
23
+
24
+ users = load_users_data()
25
+
26
+ # 2. Vérification de l'existence
27
+ if any(u.get("email") == email for u in users.values()):
28
+ return None, "Cet email est déjà enregistré."
29
+
30
+ # 3. Création du nouvel utilisateur
31
+ user_id = str(uuid.uuid4())
32
+ hashed_password = generate_password_hash(password)
33
+
34
+ new_user = {
35
+ "email": email,
36
+ "password_hash": hashed_password,
37
+ "user_id": user_id,
38
+ "created_at": datetime.now().isoformat()
39
+ }
40
+ users[user_id] = new_user
41
+
42
+ # 4. Sauvegarde et commit de users.json
43
+ success_save = save_users_data(users, commit_message=f"feat: New user registration for {email}")
44
+
45
+ if not success_save:
46
+ return None, "Erreur lors de la sauvegarde des données utilisateur (Hugging Face)."
47
+
48
+ # 5. Création de la structure de données et de la première conversation (si nécessaire)
49
+ # L'implémentation actuelle de create_user_data_structure crée un fichier vide.
50
+ # Vous pouvez ajouter ici la logique pour une première conversation si vous le souhaitez.
51
+ create_user_data_structure(user_id)
52
+
53
+ # Retourne l'ID et un message de succès
54
+ return user_id, "Inscription réussie."
55
+
56
+ # Fonction de connexion de l'utilisateur
57
+ def login_user(email, password):
58
+ users = load_users_data()
59
+
60
+ # 1. Recherche de l'utilisateur par email
61
+ user = next((u for u in users.values() if u.get("email") == email), None)
62
+
63
+ if user is None:
64
+ return None, "Email ou mot de passe incorrect."
65
+
66
+ # 2. Vérification du mot de passe
67
+ if not check_password_hash(user["password_hash"], password):
68
+ return None, "Email ou mot de passe incorrect."
69
+
70
+ # 3. Connexion réussie
71
+ return user["user_id"], "Connexion réussie."
72
+
73
+ # Fonction pour obtenir les données utilisateur par ID
74
+ def get_user_by_id(user_id):
75
+ users = load_users_data()
76
+ return users.get(user_id)
git_storage.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # git_storage.py
2
+
3
+ import json
4
+ from huggingface_hub import HfApi, CommitOperationAdd
5
+ from os import environ
6
+
7
+ # Configuration du dépôt
8
+ # L'ID du dépôt est maintenant directement dans le code.
9
+ REPO_ID = "ernestmindres/ErnestMind_database"
10
+
11
+ # L'API_TOKEN est lu directement depuis les secrets de l'environnement (Secret HF_TOKEN)
12
+ API_TOKEN = environ.get("HF_TOKEN")
13
+
14
+ # Initialisation de l'API Hugging Face
15
+ api = HfApi(token=API_TOKEN)
16
+
17
+ def load_users_data():
18
+ """Charge le fichier users.json depuis le dépôt."""
19
+ try:
20
+ # Télécharge et charge le fichier users.json
21
+ users_file = api.hf_hub_download(
22
+ repo_id=REPO_ID,
23
+ filename="users.json",
24
+ repo_type="dataset" # <-- CORRECTION: Spécifier le type de dépôt
25
+ )
26
+ with open(users_file, "r", encoding="utf-8") as f:
27
+ return json.load(f)
28
+ except Exception as e:
29
+ # Si le fichier n'existe pas encore (premier lancement), on démarre avec un dictionnaire vide
30
+ if "Repository Not Found" in str(e) or "Cannot find file" in str(e):
31
+ print(f"Dépôt ou fichier users.json non trouvé (ce qui est normal au premier lancement): {e}")
32
+ return {}
33
+ print(f"Erreur lors du chargement des données utilisateurs: {e}")
34
+ return {}
35
+
36
+
37
+ # Chemin du fichier temporaire pour l'écriture locale (correction pour PermissionError)
38
+ LOCAL_USERS_FILE_PATH = "/tmp/users.json"
39
+
40
+ def save_users_data(users_data, commit_message="Update users data"):
41
+ """
42
+ Sauvegarde les données utilisateurs dans users.json
43
+ et les commite sur le dépôt Hugging Face.
44
+ """
45
+ try:
46
+ # Écriture du fichier users.json en local dans le répertoire /tmp
47
+ with open(LOCAL_USERS_FILE_PATH, "w", encoding="utf-8") as f:
48
+ json.dump(users_data, f, indent=4)
49
+
50
+ # Définition de l'opération de commit
51
+ operations = [
52
+ CommitOperationAdd(path_in_repo="users.json", path_or_fileobj=LOCAL_USERS_FILE_PATH)
53
+ ]
54
+
55
+ # Commit sur le dépôt
56
+ api.create_commit(
57
+ repo_id=REPO_ID,
58
+ operations=operations,
59
+ commit_message=commit_message,
60
+ repo_type="dataset" # <-- CORRECTION: Spécifier le type de dépôt
61
+ )
62
+ print(f"Fichier users.json commité avec succès. Message: '{commit_message}'")
63
+ return True
64
+ except Exception as e:
65
+ import traceback
66
+ print("---------- LOG D'ERREUR HUGGING FACE ----------")
67
+ print(f"Échec de l'opération de commit sur le dépôt {REPO_ID}.")
68
+ print(f"Erreur complète : {e}")
69
+ traceback.print_exc()
70
+ print("---------------------------------------------")
71
+ return False
72
+
73
+ def create_user_data_structure(user_id):
74
+ """
75
+ Crée la structure initiale des conversations pour un nouvel utilisateur
76
+ dans le dépôt Hugging Face.
77
+ """
78
+ try:
79
+ # Crée un fichier 'conversations/<user_id>.json' vide
80
+ file_path = f"conversations/{user_id}.json"
81
+
82
+ operations = [
83
+ CommitOperationAdd(
84
+ path_in_repo=file_path,
85
+ path_or_fileobj=json.dumps({"conversations": {}}, indent=4)
86
+ )
87
+ ]
88
+
89
+ # Commit sur le dépôt
90
+ api.create_commit(
91
+ repo_id=REPO_ID,
92
+ operations=operations,
93
+ commit_message=f"feat: Add new user conversation file: {user_id}",
94
+ repo_type="dataset" # <-- CORRECTION: Spécifier le type de dépôt
95
+ )
96
+ print(f"Structure de données pour l'utilisateur {user_id} créée.")
97
+ return True
98
+ except Exception as e:
99
+ print(f"Erreur lors de la création de la structure de données pour {user_id}: {e}")
100
+ return False
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ flask
2
+ huggingface-hub
3
+ bcrypt
4
+ requests
5
+ gunicorn
6
+ Flask-CORS