ernestmindres commited on
Commit
8d5d616
·
verified ·
1 Parent(s): 0dc5c88

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +334 -486
app.py CHANGED
@@ -1,486 +1,334 @@
1
- # app.py
2
-
3
- import json
4
- import os
5
- import sys
6
- import io
7
- import uuid
8
- from functools import wraps
9
- from datetime import datetime
10
- from flask import Flask, request, jsonify, Response, session
11
- from flask_cors import CORS
12
- import baserow_storage # Assurez-vous que ceci est présent
13
-
14
- # Importation des modules backend
15
- from auth_backend import (
16
- register_user,
17
- login_user,
18
- get_user_by_id,
19
- get_plan_limit,
20
- reset_password_via_security_question,
21
- generate_password_hash,
22
- # Nouvelles fonctions pour la gestion des utilisateurs finaux
23
- register_end_user,
24
- login_end_user,
25
- reset_end_user_password_by_client
26
- )
27
- from decorators import api_key_required # <-- NOUVEL IMPORT
28
-
29
- # Importation des Blueprints
30
- from web_routes import web_bp
31
- from user_routes import user_bp
32
- from billing_routes import billing_bp
33
-
34
-
35
- # Valeur par défaut pour la taille max de contenu
36
- DEFAULT_MAX_CONTENT_LENGTH = 16 * 1024 * 1024
37
-
38
-
39
- # --- Initialisation de l'Application Flask ---
40
- app = Flask(__name__)
41
-
42
- from werkzeug.middleware.proxy_fix import ProxyFix
43
- app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1)
44
- # ------------------------------------------------------------------
45
-
46
- # Configuration
47
- app.secret_key = os.environ.get("FLASK_SECRET_KEY", "super_secret_dev_key")
48
- app.config['MAX_CONTENT_LENGTH'] = DEFAULT_MAX_CONTENT_LENGTH
49
-
50
- # Permettre les requêtes cross-origin (CORS)
51
- CORS(app, supports_credentials=True, origins="*", allow_headers=["Content-Type", "X-User-API-Key"])
52
-
53
-
54
- # Permettre les requêtes cross-origin pour l'API
55
- CORS(app)
56
-
57
- # --- Enregistrement des Blueprints (Nouveau) ---
58
- app.register_blueprint(web_bp)
59
- app.register_blueprint(user_bp)
60
- app.register_blueprint(billing_bp) # <-- NOUVEL ENREGISTREMENT
61
-
62
-
63
- # --- Décorateurs d'Authentification (Conservés) ---
64
- def login_required(f):
65
- @wraps(f)
66
- def decorated_function(*args, **kwargs):
67
- if 'user_id' not in session:
68
- # Redirection HTTP 302 vers la page de connexion pour les requêtes non-API
69
- if not request.path.startswith('/api/'):
70
- from flask import redirect, url_for
71
- return redirect(url_for('user_bp.connexion'))
72
- # Réponse JSON pour les API
73
- return jsonify({"status": "Error", "message": "Accès non autorisé. Veuillez vous connecter.", "code": "AUTH_REQUIRED"}), 401
74
- return f(*args, **kwargs)
75
- return decorated_function
76
-
77
- def user_api_key_required(f):
78
- """Décorateur pour exiger la clé API dynamique via URL ('api_key') ou en-tête ('X-User-API-Key')."""
79
- @wraps(f)
80
- def decorated_function(*args, **kwargs):
81
- api_key = request.args.get('api_key') or request.headers.get('X-User-API-Key')
82
-
83
- if not api_key:
84
- return jsonify({
85
- "status": "Error",
86
- "message": "Clé API utilisateur ('api_key' dans l'URL ou 'X-User-API-Key' dans l'en-tête) manquante.",
87
- "code": "USER_API_KEY_MISSING"
88
- }), 403
89
-
90
- user = get_user_by_api_key(api_key)
91
-
92
- if not user:
93
- return jsonify({
94
- "status": "Error",
95
- "message": "Clé API utilisateur invalide.",
96
- "code": "USER_API_KEY_INVALID"
97
- }), 403
98
-
99
- request.user_client_id = user['user_id']
100
- request.user_client_data = user
101
-
102
- return f(*args, **kwargs)
103
- return decorated_function
104
-
105
-
106
- # --- Routes d'Authentification (API - Conservées) ---
107
-
108
- @app.route("/api/register", methods=["POST"])
109
- def register():
110
- data = request.get_json()
111
- username = data.get("username")
112
- email = data.get("email")
113
- password = data.get("password")
114
- confirm_password = data.get("confirm_password")
115
- security_question = data.get("security_question")
116
- security_answer = data.get("security_answer")
117
-
118
- # CORRECTION ICI: Déballage des 3 valeurs retournées par register_user
119
- user_id, message, new_user_data = register_user(username, email, password, confirm_password, security_question, security_answer)
120
-
121
- if user_id and new_user_data: # Vérifier l'ID et les données pour s'assurer du succès
122
- session['user_id'] = user_id
123
-
124
- # Réponse JSON pour l'API, incluant la clé API
125
- return jsonify({
126
- "message": message,
127
- "status": "Success",
128
- "user_id": user_id,
129
- # On récupère la clé API directement des données utilisateur
130
- "api_key": new_user_data.get("api_key")
131
- }), 201
132
- else:
133
- # Échec de l'inscription (message d'erreur de register_user)
134
- return jsonify({"message": message, "status": "Error"}), 400
135
-
136
-
137
-
138
- @app.route("/api/login", methods=["POST"])
139
- def login():
140
- """
141
- Route de connexion de l'utilisateur principal.
142
- Prend le nom d'utilisateur/email et le mot de passe.
143
- """
144
- data = request.get_json()
145
- username = data.get("username")
146
- password = data.get("password")
147
-
148
- # CORRECTION DE L'ERREUR :
149
- # Nous déballons maintenant 3 valeurs (ID, Message, Données Utilisateur)
150
- # car la fonction login_user() dans auth_backend.py a été modifiée pour
151
- # retourner les 3 valeurs.
152
- user_id, message, user_data = login_user(username, password)
153
-
154
- # Note: user_data est la 3ème valeur (Dict des données utilisateur ou None)
155
- if user_id and user_data:
156
- # La connexion est réussie
157
- session['user_id'] = user_id
158
-
159
- # Réponse API avec la clé API de l'utilisateur pour les futures requêtes
160
- return jsonify({
161
- "message": message,
162
- "status": "Success",
163
- "user_id": user_id,
164
- # On utilise les données utilisateur (user_data) que nous avons déjà récupérées
165
- "api_key": user_data.get("api_key")
166
- }), 200
167
- else:
168
- # La connexion a échoué (identifiants invalides ou autre erreur)
169
- return jsonify({"message": message, "status": "Error"}), 401
170
-
171
- @app.route("/api/logout", methods=["POST"])
172
- def logout():
173
- session.pop('user_id', None)
174
- return jsonify({"message": "Déconnexion réussie.", "status": "Success"}), 200
175
-
176
- @app.route("/api/forgot-password", methods=["POST"])
177
- def forgot_password_api(): # Renommée pour éviter conflit avec la route HTML
178
- data = request.get_json()
179
- username_or_email = data.get("username_or_email")
180
- security_answer = data.get("security_answer")
181
- new_password = data.get("new_password")
182
-
183
- if not username_or_email or not security_answer or not new_password:
184
- return jsonify({"message": "Champs manquants.", "status": "Error"}), 400
185
-
186
- success, message = reset_password_via_security_question(username_or_email, security_answer, new_password)
187
-
188
- if success:
189
- return jsonify({
190
- "message": message,
191
- "status": "Success"
192
- }), 200
193
- else:
194
- return jsonify({
195
- "message": message,
196
- "status": "Error"
197
- }), 400
198
-
199
-
200
- # --- Routes de Gestion de Compte (API - Conservées) ---
201
-
202
- @app.route("/api/user/generate-key", methods=["POST"])
203
- @login_required
204
- def generate_user_api_key():
205
- user_id = session.get('user_id')
206
-
207
- new_api_key = create_dynamic_api_key()
208
-
209
- success, message = update_user_data(user_id, {"api_key": new_api_key})
210
-
211
- if success:
212
- return jsonify({
213
- "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'.**",
214
- "status": "Success",
215
- "api_key": new_api_key
216
- }), 200
217
- else:
218
- return jsonify({
219
- "message": f"Erreur lors de la génération de la clé : {message}",
220
- "status": "Error"
221
- }), 500
222
-
223
- @app.route("/api/user/update-info", methods=["POST"])
224
- @login_required
225
- def update_user_info():
226
- user_id = session.get('user_id')
227
- data = request.get_json()
228
-
229
- updates = {}
230
- if 'username' in data:
231
- updates['username'] = data['username']
232
- if 'email' in data:
233
- updates['email'] = data['email']
234
- if 'plan' in data:
235
- updates['plan'] = data['plan']
236
-
237
- if not updates:
238
- return jsonify({
239
- "message": "Aucune information à mettre à jour fournie.",
240
- "status": "Error"
241
- }), 400
242
-
243
- success, message = update_user_data(user_id, updates)
244
-
245
- if success:
246
- return jsonify({
247
- "message": message,
248
- "status": "Success"
249
- }), 200
250
- else:
251
- return jsonify({
252
- "message": f"Échec de la mise à jour : {message}",
253
- "status": "Error"
254
- }), 400
255
-
256
-
257
- # --- Nouvelle Route pour les Clients (API - Conservée) ---
258
-
259
- @app.route("/api/user-register", methods=["POST"])
260
- @user_api_key_required
261
- def user_register_via_api_key():
262
- client_user_id = request.user_client_id
263
- client_data = request.user_client_data
264
-
265
- data = request.get_json()
266
- username = data.get("username")
267
- email = data.get("email")
268
- password = data.get("password")
269
-
270
- current_count = client_data.get("created_accounts_count", 0)
271
- plan_limit = get_plan_limit(client_data.get("plan", "free"))
272
-
273
- if current_count >= plan_limit:
274
-
275
- if client_data.get("plan", "free") == "free" and plan_limit == 500:
276
- return jsonify({
277
- "message": "Erreur: Le plan gratuit ne peut pas prendre plus de 500 comptes utilisateur.",
278
- "status": "Error",
279
- "code": "PLAN_LIMIT_EXCEEDED"
280
- }), 402
281
-
282
- return jsonify({
283
- "message": f"Erreur: Votre plan actuel ({client_data.get('plan').upper()}) limite la création de comptes à {plan_limit}. Veuillez passer à un plan supérieur.",
284
- "status": "Error",
285
- "code": "PLAN_LIMIT_EXCEEDED"
286
- }), 402
287
-
288
- if not username or not email or len(password) < 8:
289
- return jsonify({"message": "Nom d'utilisateur, email ou mot de passe invalide (min 8 caractères).", "status": "Error"}), 400
290
-
291
- users = load_users_data()
292
- if any(u.get("email") == email for u in users.values()) or any(u.get("username") == username for u in users.values()):
293
- return jsonify({"message": "Cet email ou nom d'utilisateur est déjà enregistré.", "status": "Error"}), 400
294
-
295
- end_user_id = str(uuid.uuid4())
296
- hashed_password = generate_password_hash(password)
297
-
298
- new_end_user = {
299
- "username": username,
300
- "email": email,
301
- "password_hash": hashed_password,
302
- "user_id": end_user_id,
303
- "created_at": datetime.now().isoformat(),
304
- "api_key": None,
305
- "plan": "end_user",
306
- "created_accounts_count": 0,
307
- "security_question": None,
308
- "security_answer": None
309
- }
310
- users[end_user_id] = new_end_user
311
-
312
- new_count = current_count + 1
313
- client_data["created_accounts_count"] = new_count
314
- users[client_user_id] = client_data
315
-
316
- commit_msg = f"feat: End-user registration via API Key. Client: {client_data.get('username')}. New count: {new_count}"
317
- success_save = save_users_data(users, commit_message=commit_msg)
318
-
319
- if not success_save:
320
- return jsonify({"message": "Erreur critique lors de la sauvegarde (Git).", "status": "Error"}), 500
321
-
322
- return jsonify({
323
- "message": "Inscription de l'utilisateur final réussie.",
324
- "status": "Success",
325
- "user_id": end_user_id,
326
- "accounts_remaining": plan_limit - new_count
327
- }), 201
328
-
329
-
330
- # ----------------------------------------------------------------------
331
- # --- NOUVELLES ROUTES API POUR LA GESTION DES UTILISATEURS FINAUX ---
332
- # ----------------------------------------------------------------------
333
-
334
- @app.route("/api/enduser/register", methods=["POST"])
335
- @api_key_required
336
- def api_enduser_register(client_user):
337
- """
338
- Route API pour l'inscription d'un utilisateur final par un client (via sa clé API).
339
- Le 'client_user' est injecté par le décorateur api_key_required.
340
- """
341
- data = request.get_json()
342
- username = data.get("username")
343
- email = data.get("email")
344
- password = data.get("password")
345
-
346
- client_user_id = client_user['user_id']
347
-
348
- end_user_id, success, message = register_end_user(
349
- client_user_id,
350
- username,
351
- email,
352
- password
353
- )
354
-
355
- if success:
356
- return jsonify({
357
- "message": message,
358
- "status": "Success",
359
- "end_user_id": end_user_id,
360
- }), 201
361
- else:
362
- return jsonify({"message": message, "status": "Error"}), 400
363
-
364
- @app.route("/api/enduser/login", methods=["POST"])
365
- @api_key_required
366
- def api_enduser_login(client_user):
367
- """
368
- Route API pour la connexion d'un utilisateur final par un client (via sa clé API).
369
- Le 'client_user' est injecté par le décorateur api_key_required.
370
- """
371
- data = request.get_json()
372
- username_or_email = data.get("username_or_email") # Accepte username ou email
373
- password = data.get("password")
374
-
375
- client_user_id = client_user['user_id']
376
-
377
- if not all([username_or_email, password]):
378
- return jsonify({"message": "L'identifiant de l'utilisateur final et le mot de passe sont requis.", "status": "Error"}), 400
379
-
380
- # CORRECTION : La fonction retourne (end_user_id, message, user_info)
381
- end_user_id, message, user_info = login_end_user(
382
- client_user_id,
383
- username_or_email,
384
- password
385
- )
386
-
387
- if end_user_id and user_info: # Si l'ID est présent (succès)
388
- return jsonify({
389
- "message": message,
390
- "status": "Success",
391
- "user": user_info
392
- }), 200
393
- else:
394
- return jsonify({"message": message, "status": "Error"}), 401
395
-
396
- @app.route("/api/enduser/recover-password", methods=["POST"])
397
- @api_key_required
398
- def api_enduser_recover_password(client_user):
399
- """
400
- Route API pour la récupération/réinitialisation du mot de passe d'un utilisateur final
401
- par le client (via sa clé API).
402
- Le 'client_user' est injecté par le décorateur api_key_required.
403
- """
404
- data = request.get_json()
405
- end_user_identifier = data.get("username_or_email") # Identifiant de l'utilisateur final à réinitialiser
406
- new_password = data.get("new_password")
407
-
408
- client_user_id = client_user['user_id']
409
-
410
- if not all([end_user_identifier, new_password]):
411
- return jsonify({"message": "L'identifiant de l'utilisateur final et le nouveau mot de passe sont requis.", "status": "Error"}), 400
412
-
413
- success, message = reset_end_user_password_by_client(
414
- client_user_id,
415
- end_user_identifier,
416
- new_password
417
- )
418
-
419
- if success:
420
- return jsonify({"message": message, "status": "Success"}), 200
421
- else:
422
- return jsonify({"message": message, "status": "Error"}), 400
423
-
424
- # app.py
425
-
426
-
427
- @app.route("/api/user-info", methods=["GET"])
428
- @api_key_required
429
- def api_user_info(client_user):
430
- """
431
- Route API pour récupérer les informations de l'utilisateur principal (client)
432
- à partir de la clé API fournie.
433
- Le 'client_user' est injecté par le décorateur api_key_required.
434
- """
435
- # client_user est l'objet utilisateur complet injecté par le décorateur
436
-
437
- # Sécurité : créer une copie et supprimer les données sensibles avant l'envoi
438
- user_info_safe = client_user.copy()
439
- user_info_safe.pop('password_hash', None)
440
- user_info_safe.pop('security_answer_hash', None)
441
-
442
- return jsonify({
443
- "message": "Informations utilisateur récupérées avec succès.",
444
- "status": "Success",
445
- "user": user_info_safe
446
- }), 200
447
-
448
-
449
-
450
- @app.route("/api/health", methods=["GET"])
451
- def health_check():
452
- """Vérifie l'état du service en utilisant le statut Baserow."""
453
-
454
- # 1. Tenter d'obtenir le statut Baserow réel
455
- try:
456
- # Appelle la fonction de baserow_storage pour vérifier l'état
457
- health_status = baserow_storage.get_health_status()
458
-
459
- # Le statut 'data_storage' est la clé pour le frontend
460
- db_status_message = health_status.get('data_storage', 'Unknown')
461
-
462
- # Si la DB est 'operational', on envoie 'Ready'
463
- if db_status_message == 'operational':
464
- data_status = "Ready"
465
- else:
466
- data_status = f"Failed (Baserow: {db_status_message})"
467
-
468
- except Exception as e:
469
- # Erreur générale, Baserow inaccessible ou problème de configuration critique
470
- data_status = f"Failed (Exception: {str(e)})"
471
-
472
- return jsonify({
473
- "status": "Online",
474
- "data_storage": data_status
475
- }), 200
476
-
477
- @app.route("/", methods=["GET"])
478
- def read_root():
479
- """Endpoint racine pour le Health Check (Flask version)."""
480
-
481
- if baserow_storage.is_baserow_up():
482
- # Statut OK (200) avec le message
483
- return jsonify({"status": "ok", "message": "Backend and Baserow API are reachable."}), 200
484
- else:
485
- # Statut de service non disponible (503) avec le message d'erreur
486
- return jsonify({"detail": "Baserow service unavailable (Health Check failed)."}), 503
 
1
+ # app.py
2
+ import requests
3
+ import json
4
+ import os
5
+ import sys
6
+ import io
7
+ import uuid
8
+ from functools import wraps
9
+ from datetime import datetime
10
+ from flask import Flask, request, jsonify, Response, session
11
+ from flask_cors import CORS
12
+ import baserow_storage # Assurez-vous que ceci est présent
13
+
14
+ # Importation des modules backend
15
+ from auth_backend import (
16
+ get_user_by_id,
17
+ get_plan_limit,
18
+ reset_password_via_security_question,
19
+ generate_password_hash,
20
+ )
21
+ from decorators import api_key_required # <-- NOUVEL IMPORT
22
+
23
+ # Importation des Blueprints
24
+ from web_routes import web_bp
25
+ from user_routes import user_bp
26
+ from billing_routes import billing_bp
27
+
28
+
29
+ # Valeur par défaut pour la taille max de contenu
30
+ DEFAULT_MAX_CONTENT_LENGTH = 16 * 1024 * 1024
31
+
32
+
33
+ # --- Initialisation de l'Application Flask ---
34
+ app = Flask(__name__)
35
+
36
+ from werkzeug.middleware.proxy_fix import ProxyFix
37
+ app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1)
38
+ # ------------------------------------------------------------------
39
+
40
+ # Configuration
41
+ app.secret_key = os.environ.get("FLASK_SECRET_KEY", "super_secret_dev_key")
42
+ app.config['MAX_CONTENT_LENGTH'] = DEFAULT_MAX_CONTENT_LENGTH
43
+
44
+ # Permettre les requêtes cross-origin (CORS)
45
+ CORS(app, supports_credentials=True, origins="*", allow_headers=["Content-Type", "X-User-API-Key"])
46
+
47
+
48
+ # Permettre les requêtes cross-origin pour l'API
49
+ CORS(app)
50
+
51
+ # --- Enregistrement des Blueprints (Nouveau) ---
52
+ app.register_blueprint(web_bp)
53
+ app.register_blueprint(user_bp)
54
+ app.register_blueprint(billing_bp) # <-- NOUVEL ENREGISTREMENT
55
+
56
+
57
+ # --- Décorateurs d'Authentification (Conservés) ---
58
+ def login_required(f):
59
+ @wraps(f)
60
+ def decorated_function(*args, **kwargs):
61
+ if 'user_id' not in session:
62
+ # Redirection HTTP 302 vers la page de connexion pour les requêtes non-API
63
+ if not request.path.startswith('/api/'):
64
+ from flask import redirect, url_for
65
+ return redirect(url_for('user_bp.connexion'))
66
+ # Réponse JSON pour les API
67
+ return jsonify({"status": "Error", "message": "Accès non autorisé. Veuillez vous connecter.", "code": "AUTH_REQUIRED"}), 401
68
+ return f(*args, **kwargs)
69
+ return decorated_function
70
+
71
+
72
+ # --- Routes d'Authentification (API - Conservées) ---
73
+
74
+ @app.route("/api/register", methods=["POST"])
75
+ def register():
76
+ data = request.get_json()
77
+ username = data.get("username")
78
+ email = data.get("email")
79
+ password = data.get("password")
80
+ confirm_password = data.get("confirm_password")
81
+ security_question = data.get("security_question")
82
+ security_answer = data.get("security_answer")
83
+
84
+ # CORRECTION ICI: Déballage des 3 valeurs retournées par register_user
85
+ user_id, message, new_user_data = register_user(username, email, password, confirm_password, security_question, security_answer)
86
+
87
+ if user_id and new_user_data: # Vérifier l'ID et les données pour s'assurer du succès
88
+ session['user_id'] = user_id
89
+
90
+ # Réponse JSON pour l'API, incluant la clé API
91
+ return jsonify({
92
+ "message": message,
93
+ "status": "Success",
94
+ "user_id": user_id,
95
+ # On récupère la clé API directement des données utilisateur
96
+ "api_key": new_user_data.get("api_key")
97
+ }), 201
98
+ else:
99
+ # Échec de l'inscription (message d'erreur de register_user)
100
+ return jsonify({"message": message, "status": "Error"}), 400
101
+
102
+
103
+
104
+ @app.route("/api/login", methods=["POST"])
105
+ def login():
106
+ """
107
+ Route de connexion de l'utilisateur principal.
108
+ Prend le nom d'utilisateur/email et le mot de passe.
109
+ """
110
+ data = request.get_json()
111
+ username = data.get("username")
112
+ password = data.get("password")
113
+
114
+ # CORRECTION DE L'ERREUR :
115
+ # Nous déballons maintenant 3 valeurs (ID, Message, Données Utilisateur)
116
+ # car la fonction login_user() dans auth_backend.py a été modifiée pour
117
+ # retourner les 3 valeurs.
118
+ user_id, message, user_data = login_user(username, password)
119
+
120
+ # Note: user_data est la 3ème valeur (Dict des données utilisateur ou None)
121
+ if user_id and user_data:
122
+ # La connexion est réussie
123
+ session['user_id'] = user_id
124
+
125
+ # Réponse API avec la clé API de l'utilisateur pour les futures requêtes
126
+ return jsonify({
127
+ "message": message,
128
+ "status": "Success",
129
+ "user_id": user_id,
130
+ # On utilise les données utilisateur (user_data) que nous avons déjà récupérées
131
+ "api_key": user_data.get("api_key")
132
+ }), 200
133
+ else:
134
+ # La connexion a échoué (identifiants invalides ou autre erreur)
135
+ return jsonify({"message": message, "status": "Error"}), 401
136
+
137
+ @app.route("/api/logout", methods=["POST"])
138
+ def logout():
139
+ session.pop('user_id', None)
140
+ return jsonify({"message": "Déconnexion réussie.", "status": "Success"}), 200
141
+
142
+ @app.route("/api/forgot-password", methods=["POST"])
143
+ def forgot_password_api(): # Renommée pour éviter conflit avec la route HTML
144
+ data = request.get_json()
145
+ username_or_email = data.get("username_or_email")
146
+ security_answer = data.get("security_answer")
147
+ new_password = data.get("new_password")
148
+
149
+ if not username_or_email or not security_answer or not new_password:
150
+ return jsonify({"message": "Champs manquants.", "status": "Error"}), 400
151
+
152
+ success, message = reset_password_via_security_question(username_or_email, security_answer, new_password)
153
+
154
+ if success:
155
+ return jsonify({
156
+ "message": message,
157
+ "status": "Success"
158
+ }), 200
159
+ else:
160
+ return jsonify({
161
+ "message": message,
162
+ "status": "Error"
163
+ }), 400
164
+
165
+
166
+ # --- Routes de Gestion de Compte (API - Conservées) ---
167
+
168
+ @app.route("/api/user/generate-key", methods=["POST"])
169
+ @login_required
170
+ def generate_user_api_key():
171
+ user_id = session.get('user_id')
172
+
173
+ new_api_key = create_dynamic_api_key()
174
+
175
+ success, message = update_user_data(user_id, {"api_key": new_api_key})
176
+
177
+ if success:
178
+ return jsonify({
179
+ "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'.**",
180
+ "status": "Success",
181
+ "api_key": new_api_key
182
+ }), 200
183
+ else:
184
+ return jsonify({
185
+ "message": f"Erreur lors de la génération de la clé : {message}",
186
+ "status": "Error"
187
+ }), 500
188
+
189
+ @app.route("/api/user/update-info", methods=["POST"])
190
+ @login_required
191
+ def update_user_info():
192
+ user_id = session.get('user_id')
193
+ data = request.get_json()
194
+
195
+ updates = {}
196
+ if 'username' in data:
197
+ updates['username'] = data['username']
198
+ if 'email' in data:
199
+ updates['email'] = data['email']
200
+ if 'plan' in data:
201
+ updates['plan'] = data['plan']
202
+
203
+ if not updates:
204
+ return jsonify({
205
+ "message": "Aucune information à mettre à jour fournie.",
206
+ "status": "Error"
207
+ }), 400
208
+
209
+ success, message = update_user_data(user_id, updates)
210
+
211
+ if success:
212
+ return jsonify({
213
+ "message": message,
214
+ "status": "Success"
215
+ }), 200
216
+ else:
217
+ return jsonify({
218
+ "message": f"Échec de la mise à jour : {message}",
219
+ "status": "Error"
220
+ }), 400
221
+
222
+
223
+
224
+ @app.route("/api/user-info", methods=["GET"])
225
+ @api_key_required
226
+ def api_user_info(client_user):
227
+ """
228
+ Route API pour récupérer les informations de l'utilisateur principal (client)
229
+ à partir de la clé API fournie.
230
+ Le 'client_user' est injecté par le décorateur api_key_required.
231
+ """
232
+ # client_user est l'objet utilisateur complet injecté par le décorateur
233
+
234
+ # Sécurité : créer une copie et supprimer les données sensibles avant l'envoi
235
+ user_info_safe = client_user.copy()
236
+ user_info_safe.pop('password_hash', None)
237
+ user_info_safe.pop('security_answer_hash', None)
238
+
239
+ return jsonify({
240
+ "message": "Informations utilisateur récupérées avec succès.",
241
+ "status": "Success",
242
+ "user": user_info_safe
243
+ }), 200
244
+
245
+
246
+
247
+ @app.route("/api/health", methods=["GET"])
248
+ def health_check():
249
+ """Vérifie l'état du service en utilisant le statut Baserow."""
250
+
251
+ # 1. Tenter d'obtenir le statut Baserow réel
252
+ try:
253
+ # Appelle la fonction de baserow_storage pour vérifier l'état
254
+ health_status = baserow_storage.get_health_status()
255
+
256
+ # Le statut 'data_storage' est la clé pour le frontend
257
+ db_status_message = health_status.get('data_storage', 'Unknown')
258
+
259
+ # Si la DB est 'operational', on envoie 'Ready'
260
+ if db_status_message == 'operational':
261
+ data_status = "Ready"
262
+ else:
263
+ data_status = f"Failed (Baserow: {db_status_message})"
264
+
265
+ except Exception as e:
266
+ # Erreur générale, Baserow inaccessible ou problème de configuration critique
267
+ data_status = f"Failed (Exception: {str(e)})"
268
+
269
+ return jsonify({
270
+ "status": "Online",
271
+ "data_storage": data_status
272
+ }), 200
273
+
274
+ @app.route("/", methods=["GET"])
275
+ def read_root():
276
+ """Endpoint racine pour le Health Check (Flask version)."""
277
+
278
+ if baserow_storage.is_baserow_up():
279
+ # Statut OK (200) avec le message
280
+ return jsonify({"status": "ok", "message": "Backend and Baserow API are reachable."}), 200
281
+ else:
282
+ # Statut de service non disponible (503) avec le message d'erreur
283
+ return jsonify({"detail": "Baserow service unavailable (Health Check failed)."}), 503
284
+
285
+ # ----------------------------------------------------------------------
286
+ # --- Nouvelle Fonctionnalité : Validation d'Email en Temps Réel ---
287
+ # ----------------------------------------------------------------------
288
+
289
+ @app.route("/api/validate-email", methods=["GET"])
290
+ @api_key_required
291
+ def validate_email_realtime(client_user):
292
+ """
293
+ Endpoint API pour la validation d'email en temps réel.
294
+ L'authentification se fait via la clé API (dans 'api_key' ou 'X-API-Key').
295
+ L'email est lu depuis le paramètre d'URL 'email'.
296
+ """
297
+ email_to_validate = request.args.get('email')
298
+
299
+ # 1. Vérification de la présence de l'email
300
+ if not email_to_validate:
301
+ return jsonify({
302
+ "status": "Error",
303
+ "message": "Le paramètre 'email' est manquant dans l'URL.",
304
+ "code": "EMAIL_PARAM_MISSING"
305
+ }), 400
306
+
307
+ # 2. Validation d'appel au service externe (À REMPLACER par votre code réel)
308
+
309
+ # Placeholder pour un service de vérification d'email en temps réel
310
+ # Assurez-vous d'implémenter ici la logique d'appel à l'API de validation
311
+ # (ex: ZeroBounce, MailboxValidator, etc.) en utilisant la librairie 'requests'.
312
+
313
+ is_valid = False
314
+ message = "Vérification indisponible : Remplacez ce code par l'appel à votre service de validation d'email."
315
+
316
+ # --- DÉBUT DU PLACEHOLDER SIMPLE ---
317
+ import re
318
+ if re.match(r"[^@]+@[^@]+\.[^@]+", email_to_validate):
319
+ is_valid = True
320
+ message = "Le format de l'email est valide (Validation de base). Intégrez ici votre API de validation en temps réel."
321
+ else:
322
+ is_valid = False
323
+ message = "Format de l'email invalide."
324
+ # --- FIN DU PLACEHOLDER SIMPLE ---
325
+
326
+ # 3. Réponse API
327
+ return jsonify({
328
+ "status": "Success" if is_valid else "Error",
329
+ "message": message,
330
+ "email": email_to_validate,
331
+ "is_valid": is_valid,
332
+ # Optionnel: l'ID de l'utilisateur qui fait la requête
333
+ "client_user_id": client_user.get('user_id')
334
+ }), 200 if is_valid else 400