Docfile commited on
Commit
e6d4827
·
verified ·
1 Parent(s): 3693d77

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +317 -111
app.py CHANGED
@@ -11,20 +11,24 @@ from reportlab.lib.units import inch
11
  from datetime import datetime
12
  import secrets
13
  import re
 
 
 
 
14
 
15
  app = Flask(__name__)
16
  app.secret_key = secrets.token_hex(16)
17
  app.config['UPLOAD_FOLDER'] = 'uploads'
18
  app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 50MB max
 
19
 
20
- # Créer le dossier uploads
21
  os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
22
 
23
  # Configuration Gemini
24
  GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY', 'YOUR_API_KEY_HERE')
25
  client = genai.Client(api_key=GEMINI_API_KEY)
26
 
27
- # Limite de tokens pour les vidéos
28
  MAX_VIDEO_TOKENS = 600000
29
 
30
  ALLOWED_EXTENSIONS = {
@@ -43,6 +47,113 @@ SUMMARY_TYPES = {
43
  'detaille': 'Fais un résumé exhaustif et détaillé avec tous les points importants, citations clés et analyse approfondie.'
44
  }
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  def allowed_file(filename):
47
  return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
48
 
@@ -55,47 +166,68 @@ def is_youtube_url(url):
55
  youtube_regex = r'(https?://)?(www\.)?(youtube\.com/watch\?v=|youtu\.be/)[\w-]+'
56
  return bool(re.match(youtube_regex, url))
57
 
58
- def estimate_video_tokens(video_duration_seconds, fps=1, use_low_resolution=False):
59
- """Estime le nombre de tokens pour une vidéo"""
60
- # Tokens par frame
61
- tokens_per_frame = 66 if use_low_resolution else 258
62
- # Tokens audio par seconde
63
- tokens_per_second_audio = 32
64
- # Total par seconde
65
- tokens_per_second = (tokens_per_frame * fps) + tokens_per_second_audio
66
- return video_duration_seconds * tokens_per_second
67
-
68
- def generate_summary(file_path, filename, summary_type):
69
- """Génère un résumé avec l'API Gemini"""
70
- mime_type = get_mime_type(filename)
71
- prompt = SUMMARY_TYPES.get(summary_type, SUMMARY_TYPES['moyen'])
72
-
73
  try:
74
- # Upload le fichier via File API
 
 
 
 
 
 
 
75
  uploaded_file = client.files.upload(file=file_path)
76
 
 
 
77
  # Génère le résumé
78
  response = client.models.generate_content(
79
  model="gemini-2.5-flash",
80
  contents=[uploaded_file, prompt]
81
  )
82
 
83
- return response.text
 
 
 
84
  except Exception as e:
85
- return f"Erreur lors de la génération du résumé: {str(e)}"
86
-
87
- def generate_summary_from_youtube(youtube_url, summary_type):
88
- """Génère un résumé à partir d'une URL YouTube"""
89
- prompt = SUMMARY_TYPES.get(summary_type, SUMMARY_TYPES['moyen'])
90
 
 
 
 
 
 
 
 
 
 
 
91
  try:
 
 
 
 
 
 
92
  # Vérifier les informations du modèle
93
  model_info = client.models.get(model="gemini-2.5-flash")
94
  context_window = model_info.input_token_limit
95
 
96
- print(f"Context window: {context_window} tokens")
97
 
98
- # Générer le contenu avec l'URL YouTube
99
  response = client.models.generate_content(
100
  model='gemini-2.5-flash',
101
  contents=types.Content(
@@ -108,13 +240,19 @@ def generate_summary_from_youtube(youtube_url, summary_type):
108
  )
109
  )
110
 
111
- return response.text
 
 
 
112
  except Exception as e:
113
  error_msg = str(e)
114
- # Vérifier si l'erreur est liée à la limite de tokens
115
  if 'token' in error_msg.lower() or 'too large' in error_msg.lower():
116
- return f"Erreur: La vidéo est trop longue (dépasse la limite de {MAX_VIDEO_TOKENS:,} tokens). Veuillez utiliser une vidéo plus courte."
117
- return f"Erreur lors de la génération du résumé: {error_msg}"
 
 
 
 
118
 
119
  def create_pdf(summary_text, original_filename, summary_type):
120
  """Crée un PDF du résumé"""
@@ -123,7 +261,6 @@ def create_pdf(summary_text, original_filename, summary_type):
123
  styles = getSampleStyleSheet()
124
  story = []
125
 
126
- # Titre
127
  title_style = ParagraphStyle(
128
  'CustomTitle',
129
  parent=styles['Heading1'],
@@ -133,13 +270,11 @@ def create_pdf(summary_text, original_filename, summary_type):
133
  title = Paragraph(f"Résumé: {original_filename}", title_style)
134
  story.append(title)
135
 
136
- # Info
137
  info_style = styles['Normal']
138
  info = Paragraph(f"Type: {summary_type.upper()} | Date: {datetime.now().strftime('%d/%m/%Y %H:%M')}", info_style)
139
  story.append(info)
140
  story.append(Spacer(1, 0.3*inch))
141
 
142
- # Contenu du résumé
143
  for paragraph in summary_text.split('\n\n'):
144
  if paragraph.strip():
145
  p = Paragraph(paragraph.replace('\n', '<br/>'), styles['Normal'])
@@ -150,14 +285,17 @@ def create_pdf(summary_text, original_filename, summary_type):
150
  buffer.seek(0)
151
  return buffer
152
 
 
 
 
 
153
  @app.route('/')
154
  def index():
155
- if 'history' not in session:
156
- session['history'] = []
157
  return render_template('index.html')
158
 
159
  @app.route('/upload', methods=['POST'])
160
  def upload_file():
 
161
  youtube_url = request.form.get('youtube_url', '').strip()
162
  summary_type = request.form.get('summary_type', 'moyen')
163
 
@@ -166,39 +304,30 @@ def upload_file():
166
  if not is_youtube_url(youtube_url):
167
  return jsonify({'error': 'URL YouTube invalide'}), 400
168
 
169
- try:
170
- summary = generate_summary_from_youtube(youtube_url, summary_type)
171
-
172
- # Vérifier si c'est un message d'erreur
173
- if summary.startswith("Erreur:"):
174
- return jsonify({'error': summary}), 400
175
-
176
- # Ajouter à l'historique
177
- if 'history' not in session:
178
- session['history'] = []
179
-
180
- history_item = {
181
- 'id': len(session['history']),
182
- 'filename': 'Vidéo YouTube',
183
- 'summary_type': summary_type,
184
- 'summary': summary,
185
- 'date': datetime.now().strftime('%d/%m/%Y %H:%M'),
186
- 'filepath': youtube_url
187
- }
188
-
189
- session['history'].append(history_item)
190
- session.modified = True
191
-
192
- return jsonify({
193
- 'success': True,
194
- 'summary': summary,
195
- 'history_id': history_item['id']
196
- })
197
 
198
- except Exception as e:
199
- return jsonify({'error': str(e)}), 500
 
 
 
200
 
201
- # Traitement fichier normal
202
  if 'file' not in request.files:
203
  return jsonify({'error': 'Aucun fichier ou URL YouTube fourni'}), 400
204
 
@@ -211,65 +340,142 @@ def upload_file():
211
  return jsonify({'error': 'Type de fichier non supporté'}), 400
212
 
213
  filename = secure_filename(file.filename)
214
- filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
215
  file.save(filepath)
216
 
217
- try:
218
- # Génère le résumé
219
- summary = generate_summary(filepath, filename, summary_type)
220
-
221
- # Ajoute à l'historique
222
- if 'history' not in session:
223
- session['history'] = []
224
-
225
- history_item = {
226
- 'id': len(session['history']),
227
- 'filename': filename,
228
- 'summary_type': summary_type,
229
- 'summary': summary,
230
- 'date': datetime.now().strftime('%d/%m/%Y %H:%M'),
231
- 'filepath': filepath
232
- }
233
-
234
- session['history'].append(history_item)
235
- session.modified = True
236
-
237
- return jsonify({
238
- 'success': True,
239
- 'summary': summary,
240
- 'history_id': history_item['id']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  })
242
 
243
- except Exception as e:
244
- return jsonify({'error': str(e)}), 500
245
- finally:
246
- # Nettoie le fichier après traitement
247
- if os.path.exists(filepath):
248
- os.remove(filepath)
249
-
250
- @app.route('/download/<int:history_id>')
251
- def download_pdf(history_id):
252
- if 'history' not in session or history_id >= len(session['history']):
253
- return jsonify({'error': 'Résumé non trouvé'}), 404
254
 
255
- item = session['history'][history_id]
256
- pdf_buffer = create_pdf(item['summary'], item['filename'], item['summary_type'])
 
 
 
 
 
257
 
258
  return send_file(
259
  pdf_buffer,
260
  mimetype='application/pdf',
261
  as_attachment=True,
262
- download_name=f"resume_{item['filename']}.pdf"
263
  )
264
 
265
- @app.route('/history')
266
- def get_history():
267
- return jsonify(session.get('history', []))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
 
269
- @app.route('/clear-history', methods=['POST'])
270
- def clear_history():
271
- session['history'] = []
 
 
 
 
 
 
 
272
  return jsonify({'success': True})
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  if __name__ == '__main__':
275
  app.run(debug=True)
 
11
  from datetime import datetime
12
  import secrets
13
  import re
14
+ import sqlite3
15
+ import threading
16
+ import time
17
+ import uuid
18
 
19
  app = Flask(__name__)
20
  app.secret_key = secrets.token_hex(16)
21
  app.config['UPLOAD_FOLDER'] = 'uploads'
22
  app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 50MB max
23
+ app.config['DATABASE'] = 'tasks.db'
24
 
25
+ # Créer les dossiers nécessaires
26
  os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
27
 
28
  # Configuration Gemini
29
  GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY', 'YOUR_API_KEY_HERE')
30
  client = genai.Client(api_key=GEMINI_API_KEY)
31
 
 
32
  MAX_VIDEO_TOKENS = 600000
33
 
34
  ALLOWED_EXTENSIONS = {
 
47
  'detaille': 'Fais un résumé exhaustif et détaillé avec tous les points importants, citations clés et analyse approfondie.'
48
  }
49
 
50
+ # =======================
51
+ # Base de données
52
+ # =======================
53
+
54
+ def init_db():
55
+ """Initialise la base de données"""
56
+ conn = sqlite3.connect(app.config['DATABASE'])
57
+ c = conn.cursor()
58
+ c.execute('''
59
+ CREATE TABLE IF NOT EXISTS tasks (
60
+ task_id TEXT PRIMARY KEY,
61
+ user_session TEXT,
62
+ filename TEXT,
63
+ summary_type TEXT,
64
+ status TEXT,
65
+ progress INTEGER,
66
+ summary TEXT,
67
+ error TEXT,
68
+ created_at TEXT,
69
+ completed_at TEXT,
70
+ source_type TEXT,
71
+ source_path TEXT
72
+ )
73
+ ''')
74
+ conn.commit()
75
+ conn.close()
76
+
77
+ def get_db_connection():
78
+ """Retourne une connexion à la base de données"""
79
+ conn = sqlite3.connect(app.config['DATABASE'])
80
+ conn.row_factory = sqlite3.Row
81
+ return conn
82
+
83
+ def create_task(user_session, filename, summary_type, source_type, source_path):
84
+ """Crée une nouvelle tâche dans la base de données"""
85
+ task_id = str(uuid.uuid4())
86
+ conn = get_db_connection()
87
+ conn.execute('''
88
+ INSERT INTO tasks (task_id, user_session, filename, summary_type, status,
89
+ progress, created_at, source_type, source_path)
90
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
91
+ ''', (task_id, user_session, filename, summary_type, 'pending', 0,
92
+ datetime.now().isoformat(), source_type, source_path))
93
+ conn.commit()
94
+ conn.close()
95
+ return task_id
96
+
97
+ def update_task_status(task_id, status, progress=None, summary=None, error=None):
98
+ """Met à jour le statut d'une tâche"""
99
+ conn = get_db_connection()
100
+
101
+ if status == 'completed' or status == 'failed':
102
+ if progress is None:
103
+ progress = 100 if status == 'completed' else 0
104
+ conn.execute('''
105
+ UPDATE tasks
106
+ SET status = ?, progress = ?, summary = ?, error = ?, completed_at = ?
107
+ WHERE task_id = ?
108
+ ''', (status, progress, summary, error, datetime.now().isoformat(), task_id))
109
+ else:
110
+ query = 'UPDATE tasks SET status = ?'
111
+ params = [status]
112
+
113
+ if progress is not None:
114
+ query += ', progress = ?'
115
+ params.append(progress)
116
+
117
+ query += ' WHERE task_id = ?'
118
+ params.append(task_id)
119
+
120
+ conn.execute(query, params)
121
+
122
+ conn.commit()
123
+ conn.close()
124
+
125
+ def get_task(task_id):
126
+ """Récupère une tâche par son ID"""
127
+ conn = get_db_connection()
128
+ task = conn.execute('SELECT * FROM tasks WHERE task_id = ?', (task_id,)).fetchone()
129
+ conn.close()
130
+ return task
131
+
132
+ def get_user_tasks(user_session):
133
+ """Récupère toutes les tâches d'un utilisateur"""
134
+ conn = get_db_connection()
135
+ tasks = conn.execute(
136
+ 'SELECT * FROM tasks WHERE user_session = ? ORDER BY created_at DESC',
137
+ (user_session,)
138
+ ).fetchall()
139
+ conn.close()
140
+ return tasks
141
+
142
+ def delete_old_tasks():
143
+ """Supprime les tâches de plus de 7 jours"""
144
+ conn = get_db_connection()
145
+ seven_days_ago = datetime.now().timestamp() - (7 * 24 * 60 * 60)
146
+ conn.execute('''
147
+ DELETE FROM tasks
148
+ WHERE datetime(created_at) < datetime(?, 'unixepoch')
149
+ ''', (seven_days_ago,))
150
+ conn.commit()
151
+ conn.close()
152
+
153
+ # =======================
154
+ # Fonctions utilitaires
155
+ # =======================
156
+
157
  def allowed_file(filename):
158
  return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
159
 
 
166
  youtube_regex = r'(https?://)?(www\.)?(youtube\.com/watch\?v=|youtu\.be/)[\w-]+'
167
  return bool(re.match(youtube_regex, url))
168
 
169
+ def get_session_id():
170
+ """Récupère ou crée un ID de session"""
171
+ if 'session_id' not in session:
172
+ session['session_id'] = str(uuid.uuid4())
173
+ return session['session_id']
174
+
175
+ # =======================
176
+ # Traitement en arrière-plan
177
+ # =======================
178
+
179
+ def process_file_background(task_id, file_path, filename, summary_type):
180
+ """Traite un fichier en arrière-plan"""
 
 
 
181
  try:
182
+ update_task_status(task_id, 'processing', progress=10)
183
+
184
+ mime_type = get_mime_type(filename)
185
+ prompt = SUMMARY_TYPES.get(summary_type, SUMMARY_TYPES['moyen'])
186
+
187
+ update_task_status(task_id, 'processing', progress=30)
188
+
189
+ # Upload le fichier
190
  uploaded_file = client.files.upload(file=file_path)
191
 
192
+ update_task_status(task_id, 'processing', progress=60)
193
+
194
  # Génère le résumé
195
  response = client.models.generate_content(
196
  model="gemini-2.5-flash",
197
  contents=[uploaded_file, prompt]
198
  )
199
 
200
+ summary = response.text
201
+
202
+ update_task_status(task_id, 'completed', progress=100, summary=summary)
203
+
204
  except Exception as e:
205
+ update_task_status(task_id, 'failed', error=str(e))
 
 
 
 
206
 
207
+ finally:
208
+ # Nettoie le fichier
209
+ if os.path.exists(file_path):
210
+ try:
211
+ os.remove(file_path)
212
+ except:
213
+ pass
214
+
215
+ def process_youtube_background(task_id, youtube_url, summary_type):
216
+ """Traite une vidéo YouTube en arrière-plan"""
217
  try:
218
+ update_task_status(task_id, 'processing', progress=10)
219
+
220
+ prompt = SUMMARY_TYPES.get(summary_type, SUMMARY_TYPES['moyen'])
221
+
222
+ update_task_status(task_id, 'processing', progress=30)
223
+
224
  # Vérifier les informations du modèle
225
  model_info = client.models.get(model="gemini-2.5-flash")
226
  context_window = model_info.input_token_limit
227
 
228
+ update_task_status(task_id, 'processing', progress=50)
229
 
230
+ # Générer le contenu
231
  response = client.models.generate_content(
232
  model='gemini-2.5-flash',
233
  contents=types.Content(
 
240
  )
241
  )
242
 
243
+ summary = response.text
244
+
245
+ update_task_status(task_id, 'completed', progress=100, summary=summary)
246
+
247
  except Exception as e:
248
  error_msg = str(e)
 
249
  if 'token' in error_msg.lower() or 'too large' in error_msg.lower():
250
+ error_msg = f"La vidéo est trop longue (dépasse la limite de {MAX_VIDEO_TOKENS:,} tokens). Veuillez utiliser une vidéo plus courte."
251
+ update_task_status(task_id, 'failed', error=error_msg)
252
+
253
+ # =======================
254
+ # Fonction PDF
255
+ # =======================
256
 
257
  def create_pdf(summary_text, original_filename, summary_type):
258
  """Crée un PDF du résumé"""
 
261
  styles = getSampleStyleSheet()
262
  story = []
263
 
 
264
  title_style = ParagraphStyle(
265
  'CustomTitle',
266
  parent=styles['Heading1'],
 
270
  title = Paragraph(f"Résumé: {original_filename}", title_style)
271
  story.append(title)
272
 
 
273
  info_style = styles['Normal']
274
  info = Paragraph(f"Type: {summary_type.upper()} | Date: {datetime.now().strftime('%d/%m/%Y %H:%M')}", info_style)
275
  story.append(info)
276
  story.append(Spacer(1, 0.3*inch))
277
 
 
278
  for paragraph in summary_text.split('\n\n'):
279
  if paragraph.strip():
280
  p = Paragraph(paragraph.replace('\n', '<br/>'), styles['Normal'])
 
285
  buffer.seek(0)
286
  return buffer
287
 
288
+ # =======================
289
+ # Routes
290
+ # =======================
291
+
292
  @app.route('/')
293
  def index():
 
 
294
  return render_template('index.html')
295
 
296
  @app.route('/upload', methods=['POST'])
297
  def upload_file():
298
+ user_session = get_session_id()
299
  youtube_url = request.form.get('youtube_url', '').strip()
300
  summary_type = request.form.get('summary_type', 'moyen')
301
 
 
304
  if not is_youtube_url(youtube_url):
305
  return jsonify({'error': 'URL YouTube invalide'}), 400
306
 
307
+ # Créer la tâche
308
+ task_id = create_task(
309
+ user_session=user_session,
310
+ filename='Vidéo YouTube',
311
+ summary_type=summary_type,
312
+ source_type='youtube',
313
+ source_path=youtube_url
314
+ )
315
+
316
+ # Lancer le traitement en arrière-plan
317
+ thread = threading.Thread(
318
+ target=process_youtube_background,
319
+ args=(task_id, youtube_url, summary_type)
320
+ )
321
+ thread.daemon = True
322
+ thread.start()
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
+ return jsonify({
325
+ 'success': True,
326
+ 'task_id': task_id,
327
+ 'message': 'Traitement en cours...'
328
+ })
329
 
330
+ # Traitement fichier
331
  if 'file' not in request.files:
332
  return jsonify({'error': 'Aucun fichier ou URL YouTube fourni'}), 400
333
 
 
340
  return jsonify({'error': 'Type de fichier non supporté'}), 400
341
 
342
  filename = secure_filename(file.filename)
343
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], f"{uuid.uuid4()}_{filename}")
344
  file.save(filepath)
345
 
346
+ # Créer la tâche
347
+ task_id = create_task(
348
+ user_session=user_session,
349
+ filename=filename,
350
+ summary_type=summary_type,
351
+ source_type='file',
352
+ source_path=filepath
353
+ )
354
+
355
+ # Lancer le traitement en arrière-plan
356
+ thread = threading.Thread(
357
+ target=process_file_background,
358
+ args=(task_id, filepath, filename, summary_type)
359
+ )
360
+ thread.daemon = True
361
+ thread.start()
362
+
363
+ return jsonify({
364
+ 'success': True,
365
+ 'task_id': task_id,
366
+ 'message': 'Traitement en cours...'
367
+ })
368
+
369
+ @app.route('/task/<task_id>')
370
+ def get_task_status(task_id):
371
+ """Récupère le statut d'une tâche"""
372
+ task = get_task(task_id)
373
+
374
+ if not task:
375
+ return jsonify({'error': 'Tâche non trouvée'}), 404
376
+
377
+ return jsonify({
378
+ 'task_id': task['task_id'],
379
+ 'filename': task['filename'],
380
+ 'summary_type': task['summary_type'],
381
+ 'status': task['status'],
382
+ 'progress': task['progress'],
383
+ 'summary': task['summary'],
384
+ 'error': task['error'],
385
+ 'created_at': task['created_at'],
386
+ 'completed_at': task['completed_at']
387
+ })
388
+
389
+ @app.route('/tasks')
390
+ def get_all_tasks():
391
+ """Récupère toutes les tâches de l'utilisateur"""
392
+ user_session = get_session_id()
393
+ tasks = get_user_tasks(user_session)
394
+
395
+ task_list = []
396
+ for task in tasks:
397
+ task_list.append({
398
+ 'task_id': task['task_id'],
399
+ 'filename': task['filename'],
400
+ 'summary_type': task['summary_type'],
401
+ 'status': task['status'],
402
+ 'progress': task['progress'],
403
+ 'summary': task['summary'],
404
+ 'error': task['error'],
405
+ 'created_at': task['created_at'],
406
+ 'completed_at': task['completed_at']
407
  })
408
 
409
+ return jsonify(task_list)
410
+
411
+ @app.route('/download/<task_id>')
412
+ def download_pdf(task_id):
413
+ """Télécharge le PDF d'une tâche"""
414
+ task = get_task(task_id)
 
 
 
 
 
415
 
416
+ if not task:
417
+ return jsonify({'error': 'Tâche non trouvée'}), 404
418
+
419
+ if task['status'] != 'completed':
420
+ return jsonify({'error': 'Résumé non encore disponible'}), 400
421
+
422
+ pdf_buffer = create_pdf(task['summary'], task['filename'], task['summary_type'])
423
 
424
  return send_file(
425
  pdf_buffer,
426
  mimetype='application/pdf',
427
  as_attachment=True,
428
+ download_name=f"resume_{task['filename']}.pdf"
429
  )
430
 
431
+ @app.route('/delete-task/<task_id>', methods=['DELETE'])
432
+ def delete_task(task_id):
433
+ """Supprime une tâche"""
434
+ user_session = get_session_id()
435
+ task = get_task(task_id)
436
+
437
+ if not task:
438
+ return jsonify({'error': 'Tâche non trouvée'}), 404
439
+
440
+ if task['user_session'] != user_session:
441
+ return jsonify({'error': 'Non autorisé'}), 403
442
+
443
+ conn = get_db_connection()
444
+ conn.execute('DELETE FROM tasks WHERE task_id = ?', (task_id,))
445
+ conn.commit()
446
+ conn.close()
447
+
448
+ return jsonify({'success': True})
449
 
450
+ @app.route('/clear-tasks', methods=['POST'])
451
+ def clear_all_tasks():
452
+ """Supprime toutes les tâches de l'utilisateur"""
453
+ user_session = get_session_id()
454
+
455
+ conn = get_db_connection()
456
+ conn.execute('DELETE FROM tasks WHERE user_session = ?', (user_session,))
457
+ conn.commit()
458
+ conn.close()
459
+
460
  return jsonify({'success': True})
461
 
462
+ # =======================
463
+ # Initialisation
464
+ # =======================
465
+
466
+ # Initialiser la base de données au démarrage
467
+ with app.app_context():
468
+ init_db()
469
+
470
+ # Nettoyage périodique des anciennes tâches
471
+ def cleanup_old_tasks():
472
+ while True:
473
+ time.sleep(86400) # Toutes les 24 heures
474
+ delete_old_tasks()
475
+
476
+ cleanup_thread = threading.Thread(target=cleanup_old_tasks)
477
+ cleanup_thread.daemon = True
478
+ cleanup_thread.start()
479
+
480
  if __name__ == '__main__':
481
  app.run(debug=True)