ernestmindres commited on
Commit
d2345e2
·
verified ·
1 Parent(s): cde2256

Update web_routes.py

Browse files
Files changed (1) hide show
  1. web_routes.py +268 -20
web_routes.py CHANGED
@@ -6,8 +6,8 @@ from werkzeug.utils import secure_filename # <-- NOUVEL IMPORT (pourrait ne pas
6
  import os
7
  import uuid # <-- NOUVEL IMPORT
8
  from auth_backend import get_plan_details
9
- from baserow_storage import get_health_status
10
-
11
  # NOUVEAU : Fonction pour vérifier les extensions autorisées pour le déploiement statique
12
  def allowed_static_file(filename):
13
  """Vérifie si le fichier a une extension autorisée (HTML, CSS, JS)."""
@@ -105,7 +105,6 @@ def html_launcher():
105
  return render_template("html_launcher.html")
106
 
107
 
108
- # --- NOUVELLE ROUTE : Page de Déploiement Statique ---
109
  @web_bp.route("/static-deploy")
110
  def static_deploy_page():
111
  """
@@ -116,7 +115,8 @@ def static_deploy_page():
116
 
117
  # Si un ID existe, on peut récupérer la liste des fichiers pour l'afficher
118
  files = []
119
- base_dir = None
 
120
  if deploy_id:
121
  base_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], deploy_id)
122
  if os.path.exists(base_dir):
@@ -129,11 +129,17 @@ def static_deploy_page():
129
 
130
  # L'état du bouton "Lancer"
131
  index_file_present = 'index.html' in files
 
 
 
 
 
132
 
133
  return render_template("static_deploy.html",
134
  files=files,
135
  index_file_present=index_file_present,
136
- deploy_id=deploy_id)
 
137
 
138
  # --- NOUVELLE ROUTE : Upload de Fichiers Statiques Multiples ---
139
  @web_bp.route("/upload-static", methods=['POST'])
@@ -204,32 +210,274 @@ def upload_static_files():
204
  "deploy_id": deploy_id
205
  }), 200
206
 
207
- # --- NOUVELLE ROUTE : Servir les Fichiers Statiques ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  @web_bp.route("/serve-static/<deploy_id>/<path:filename>")
209
  def serve_static(deploy_id, filename):
210
  """
211
- Sert les fichiers statiques (HTML, CSS, JS, images) à partir du dossier
212
- temporaire identifié par deploy_id.
213
  """
214
- # 1. Sécurité: Rejeter toute tentative de traversée de répertoire (..)
215
  if '..' in filename:
216
  return "Accès refusé: Tentative de traversée de répertoire", 403
217
 
218
- # 2. Définir le répertoire de base de l'ID de déploiement
219
  root_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], deploy_id)
 
220
 
221
- # 3. Vérifier que le fichier est bien autorisé (pour la sécurité, même si on utilise send_from_directory)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  if not allowed_static_file(filename):
223
  return "Type de fichier non autorisé", 403
224
 
225
- # 4. Utiliser send_from_directory pour servir le fichier
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  try:
227
- # send_from_directory va gérer les mimetypes
228
- return send_from_directory(
229
- root_dir,
230
- filename,
231
- conditional=True # Support des requêtes conditionnelles (mise en cache)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  )
233
- except Exception:
234
- # Fichier non trouvé ou autre erreur
235
- return "Fichier non trouvé", 404
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  import os
7
  import uuid # <-- NOUVEL IMPORT
8
  from auth_backend import get_plan_details
9
+ from baserow_storage import get_health_status, update_user_deployment_data, get_deployment_by_id
10
+ import huggingface_storage
11
  # NOUVEAU : Fonction pour vérifier les extensions autorisées pour le déploiement statique
12
  def allowed_static_file(filename):
13
  """Vérifie si le fichier a une extension autorisée (HTML, CSS, JS)."""
 
105
  return render_template("html_launcher.html")
106
 
107
 
 
108
  @web_bp.route("/static-deploy")
109
  def static_deploy_page():
110
  """
 
115
 
116
  # Si un ID existe, on peut récupérer la liste des fichiers pour l'afficher
117
  files = []
118
+ launch_url_for_display = None # Initialisation de la variable pour l'URL partageable
119
+
120
  if deploy_id:
121
  base_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], deploy_id)
122
  if os.path.exists(base_dir):
 
129
 
130
  # L'état du bouton "Lancer"
131
  index_file_present = 'index.html' in files
132
+
133
+ # --- NOUVEAU : Générer l'URL partageable si un déploiement est actif ---
134
+ if deploy_id and index_file_present:
135
+ # Générer l'URL complète. _external=True crée un lien absolu (https://...)
136
+ launch_url_for_display = url_for('web_bp.serve_static', deploy_id=deploy_id, filename='index.html', _external=True)
137
 
138
  return render_template("static_deploy.html",
139
  files=files,
140
  index_file_present=index_file_present,
141
+ deploy_id=deploy_id,
142
+ launch_url=launch_url_for_display) # <-- Passage de l'URL au template
143
 
144
  # --- NOUVELLE ROUTE : Upload de Fichiers Statiques Multiples ---
145
  @web_bp.route("/upload-static", methods=['POST'])
 
210
  "deploy_id": deploy_id
211
  }), 200
212
 
213
+ @web_bp.route("/upload-files", methods=['POST'])
214
+ # @login_required # Vous devez vous assurer que l'utilisateur est connecté et session['user_id'] est disponible
215
+ def upload_files():
216
+ """
217
+ Gère le téléversement des fichiers utilisateur, les enregistre dans un dossier
218
+ temporaire, puis les téléverse en un seul commit sur le Dataset Hugging Face.
219
+ """
220
+ if 'user_id' not in session:
221
+ return jsonify({
222
+ "status": "error",
223
+ "message": "Authentification requise pour téléverser."
224
+ }), 401
225
+
226
+ if not request.files:
227
+ return jsonify({
228
+ "status": "error",
229
+ "message": "Aucun fichier n'a été reçu."
230
+ }), 400
231
+
232
+ user_id = session['user_id']
233
+ # Générer un ID unique qui sera le nom du dossier sur Hugging Face et l'ID dans Baserow
234
+ deploy_id = str(uuid.uuid4())
235
+
236
+ # 1. Créer le dossier temporaire local pour la collecte des fichiers
237
+ # Le dossier temporaire est nécessaire pour utiliser upload_folder() de huggingface_hub
238
+ upload_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], deploy_id)
239
+ os.makedirs(upload_dir, exist_ok=True)
240
+
241
+ uploaded_files_list = []
242
+ index_html_present = False
243
+
244
+ try:
245
+ # 2. Enregistrer tous les fichiers dans le dossier temporaire local
246
+ for file_key in request.files:
247
+ file = request.files[file_key]
248
+ if file and allowed_static_file(file.filename):
249
+ filename = secure_filename(file.filename)
250
+ file_path = os.path.join(upload_dir, filename)
251
+ file.save(file_path)
252
+ uploaded_files_list.append(filename)
253
+
254
+ if filename.lower() == 'index.html':
255
+ index_html_present = True
256
+
257
+ # Vérification si des fichiers ont été réellement enregistrés
258
+ if not uploaded_files_list:
259
+ shutil.rmtree(upload_dir) # Nettoyer le dossier vide
260
+ return jsonify({
261
+ "status": "error",
262
+ "message": "Aucun fichier valide n'a été téléversé."
263
+ }), 400
264
+
265
+ # --- LOGIQUE PERSISTANTE HUGGING FACE ---
266
+
267
+ # 3. Téléverser le dossier complet vers Hugging Face (1 seule commit)
268
+ success_hf, message_hf, full_repo_path = huggingface_storage.upload_project_folder_to_hf(
269
+ local_folder_path=upload_dir,
270
+ deploy_id=deploy_id
271
+ )
272
+
273
+ if not success_hf:
274
+ # Échec du téléversement HF. On nettoie le dossier temporaire local.
275
+ shutil.rmtree(upload_dir)
276
+ return jsonify({
277
+ "status": "error",
278
+ "message": f"Échec de la persistance des fichiers sur Hugging Face: {message_hf}"
279
+ }), 500
280
+
281
+ # 4. Enregistrer l'ID de déploiement dans Baserow
282
+ success_db, message_db, baserow_row_id = save_new_deployment(
283
+ user_id=user_id,
284
+ deploy_id=deploy_id,
285
+ index_file_present=index_html_present,
286
+ full_repo_path=full_repo_path
287
+ )
288
+
289
+ # 5. Nettoyage et vérification finale
290
+ shutil.rmtree(upload_dir) # Suppression du dossier temporaire local
291
+
292
+ if not success_db:
293
+ # Si Baserow échoue, on annule en supprimant le projet HF (pour éviter les orphelins)
294
+ current_app.logger.warning(f"Annulation du déploiement {deploy_id} suite à l'échec Baserow.")
295
+ huggingface_storage.delete_project_folder_from_hf(deploy_id)
296
+ return jsonify({
297
+ "status": "error",
298
+ "message": f"Fichiers téléversés mais échec d'enregistrement dans Baserow. Annulation: {message_db}"
299
+ }), 500
300
+
301
+ # 6. Succès
302
+ launch_url = url_for('web_bp.serve_static', deploy_id=deploy_id, filename='index.html', _external=True)
303
+
304
+ return jsonify({
305
+ "status": "success",
306
+ "message": "Fichiers téléversés et persistés avec succès sur Hugging Face.",
307
+ "file_count": len(uploaded_files_list),
308
+ "index_present": index_html_present,
309
+ "launch_url": launch_url,
310
+ "deploy_id": deploy_id
311
+ }), 200
312
+
313
+ except Exception as e:
314
+ # Tenter de nettoyer le dossier local en cas d'erreur inattendue
315
+ if os.path.isdir(upload_dir):
316
+ shutil.rmtree(upload_dir)
317
+ current_app.logger.error(f"Erreur lors du traitement du téléversement: {e}")
318
+ return jsonify({
319
+ "status": "error",
320
+ "message": f"Une erreur inattendue est survenue: {str(e)}"
321
+ }), 500
322
+
323
+
324
+ # --- ROUTE MODIFIÉE : Servir les Fichiers Persistants ---
325
  @web_bp.route("/serve-static/<deploy_id>/<path:filename>")
326
  def serve_static(deploy_id, filename):
327
  """
328
+ Sert les fichiers statiques (HTML, CSS, JS, images).
329
+ Télécharge le dossier du projet depuis Hugging Face si il n'est pas déjà en cache local.
330
  """
331
+ # 1. Sécurité
332
  if '..' in filename:
333
  return "Accès refusé: Tentative de traversée de répertoire", 403
334
 
335
+ # 2. Définir le répertoire de base de l'ID de déploiement local (cache)
336
  root_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], deploy_id)
337
+ file_path = os.path.join(root_dir, filename)
338
 
339
+ # 3. Vérifier la présence du fichier en cache local. Si absent, tenter le téléchargement.
340
+ if not os.path.exists(file_path):
341
+
342
+ # Vérifier l'ID dans Baserow avant de tenter le téléchargement (vérifie la validité)
343
+ deployment_data = get_deployment_by_id(deploy_id)
344
+ if deployment_data is None:
345
+ return "Projet non trouvé (ID invalide)", 404
346
+
347
+ # Tenter de télécharger le dossier complet depuis Hugging Face
348
+ success, message = huggingface_storage.get_project_files_from_hf(
349
+ deploy_id=deploy_id,
350
+ local_target_path=root_dir
351
+ )
352
+
353
+ if not success:
354
+ current_app.logger.error(f"Échec du téléchargement HF pour {deploy_id}: {message}")
355
+ return "Fichiers du projet indisponibles", 503
356
+
357
+ # 4. Sécurité du type de fichier
358
  if not allowed_static_file(filename):
359
  return "Type de fichier non autorisé", 403
360
 
361
+ # 5. Servir le fichier depuis le cache local
362
+ try:
363
+ return send_from_directory(root_dir, filename)
364
+ except FileNotFoundError:
365
+ return "Fichier non trouvé dans le projet", 404
366
+ except Exception as e:
367
+ current_app.logger.error(f"Erreur lors du service du fichier {filename} pour {deploy_id}: {e}")
368
+ return "Erreur interne du serveur", 500
369
+
370
+ @web_bp.route("/deploy/static", methods=["POST"])
371
+ def deploy_static_file():
372
+ """
373
+ Gère le téléversement de fichiers statiques, les persiste sur Hugging Face,
374
+ et enregistre les métadonnées de déploiement sur la ligne de l'utilisateur
375
+ dans la table PRIMARY_USERS_TABLE_ID de Baserow.
376
+ """
377
+
378
+ # 1. Vérification de la session utilisateur et de l'authentification
379
+ user_data = session.get('user_data')
380
+ if not user_data:
381
+ current_app.logger.error("Tentative de déploiement sans session utilisateur.")
382
+ return jsonify({"success": False, "message": "Session utilisateur introuvable. Veuillez vous reconnecter."}), 401
383
+
384
+ # Récupérer l'ID de ligne Baserow de l'utilisateur et l'ID interne
385
+ user_baserow_row_id = user_data.get('baserow_row_id')
386
+ user_id = user_data.get('id') # L'ID interne de l'utilisateur (utilisé comme User Link)
387
+
388
+ if not user_baserow_row_id or not user_id:
389
+ current_app.logger.error(f"ID de base de données (Row ID: {user_baserow_row_id}) ou ID interne (ID: {user_id}) manquant.")
390
+ return jsonify({"success": False, "message": "Erreur critique: Données utilisateur incomplètes."}), 500
391
+
392
+ # 2. Vérification de la présence de fichiers
393
+ if not request.files:
394
+ return jsonify({"success": False, "message": "Aucun fichier n'a été reçu."}), 400
395
+
396
+ # 3. Préparation du déploiement
397
+ # Générer un ID unique qui sera le nom du dossier sur Hugging Face et l'ID de déploiement
398
+ deploy_id = str(uuid.uuid4())
399
+
400
+ # Créer le dossier temporaire local pour la collecte des fichiers
401
+ upload_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], deploy_id)
402
+ os.makedirs(upload_dir, exist_ok=True)
403
+
404
+ uploaded_files_list = []
405
+ index_html_present = False
406
+
407
  try:
408
+ # 4. Enregistrement des fichiers dans le dossier temporaire local
409
+ for file_key in request.files:
410
+ file = request.files[file_key]
411
+ if file and allowed_static_file(file.filename):
412
+ filename = secure_filename(file.filename)
413
+ file_path = os.path.join(upload_dir, filename)
414
+ file.save(file_path)
415
+ uploaded_files_list.append(filename)
416
+
417
+ if filename.lower() == 'index.html':
418
+ index_html_present = True
419
+
420
+ # Vérification si des fichiers ont été réellement enregistrés
421
+ if not uploaded_files_list:
422
+ shutil.rmtree(upload_dir) # Nettoyer le dossier vide
423
+ return jsonify({"success": False, "message": "Aucun fichier valide n'a été téléversé."}), 400
424
+
425
+ # --- LOGIQUE PERSISTANTE HUGGING FACE ---
426
+
427
+ # 5. Téléverser le dossier complet vers Hugging Face (1 seule commit)
428
+ success_hf, message_hf, full_repo_path = huggingface_storage.upload_project_folder_to_hf(
429
+ local_folder_path=upload_dir,
430
+ deploy_id=deploy_id
431
+ )
432
+
433
+ if not success_hf:
434
+ # Échec du téléversement HF. On nettoie le dossier temporaire local.
435
+ shutil.rmtree(upload_dir)
436
+ return jsonify({
437
+ "success": False,
438
+ "message": f"Échec de la persistance des fichiers sur Hugging Face: {message_hf}"
439
+ }), 500
440
+
441
+ # 6. Mise à jour des données de déploiement sur la ligne de l'utilisateur Baserow
442
+ hf_repo_path = huggingface_storage.HF_DATASET_REPO_ID
443
+
444
+ # Le User Link (FIELD_USER_REF) est ici défini comme l'ID interne de l'utilisateur (user_id)
445
+ success_db, message_db = update_user_deployment_data(
446
+ user_baserow_row_id=user_baserow_row_id,
447
+ deploy_id=deploy_id,
448
+ user_link=user_id,
449
+ hf_repo_path=hf_repo_path
450
  )
451
+
452
+ # 7. Nettoyage et vérification finale
453
+ shutil.rmtree(upload_dir) # Suppression du dossier temporaire local
454
+
455
+ if not success_db:
456
+ # Si l'enregistrement Baserow échoue, on annule tout (suppression du dossier HF)
457
+ huggingface_storage.delete_project_folder_from_hf(deploy_id)
458
+ current_app.logger.error(f"Échec de l'enregistrement Baserow pour {deploy_id} (User ID: {user_id}): {message_db}")
459
+ return jsonify({"success": False, "message": f"Erreur critique lors de l'enregistrement du projet: {message_db}. Annulation du déploiement."}), 500
460
+
461
+ # 8. Réponse réussie
462
+ # Si un index.html est présent, on utilise serve_static_file_default
463
+ route_to_serve = 'web_bp.serve_static_file_default' if index_html_present else 'web_bp.serve_static'
464
+
465
+ deployment_url = url_for(route_to_serve, deploy_id=deploy_id, filename='index.html', _external=True)
466
+
467
+ return jsonify({
468
+ "success": True,
469
+ "message": "Déploiement statique réussi !",
470
+ "deploy_id": deploy_id,
471
+ "url": deployment_url,
472
+ "index_present": index_html_present
473
+ }), 200
474
+
475
+ except Exception as e:
476
+ # Tenter de nettoyer le dossier local en cas d'erreur inattendue
477
+ if os.path.isdir(upload_dir):
478
+ shutil.rmtree(upload_dir)
479
+ current_app.logger.error(f"Erreur lors du traitement du d��ploiement: {e}")
480
+ return jsonify({
481
+ "success": False,
482
+ "message": f"Une erreur inattendue est survenue: {str(e)}"
483
+ }), 500