"""Outlook Sync Routes - GMAO Collège""" from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify, send_file from flask_login import login_required, current_user from app_new.extensions import db from ..outlook.models import OutlookAccount, OutlookMail, OutlookFolder, OutlookAttachment, OutlookMailInterpretation, GmaoContext from ..outlook.auth import get_access_token import os import subprocess import json import tempfile sync_bp = Blueprint('outlook_sync', __name__, template_folder='templates') @sync_bp.route('/api/sync-folders/') @login_required def api_sync_folders(account_id): """Synchronise les dossiers Outlook (avec sous-dossiers récursifs).""" import requests from datetime import datetime, timezone account = OutlookAccount.query.get(account_id) if not account or (account.user_id != current_user.id and current_user.role != 'admin'): return jsonify({'error': 'Compte non trouvé'}), 404 access_token, error = get_access_token(account) if error: return jsonify({'error': error}), 401 try: headers = {'Authorization': f'Bearer {access_token}'} # Fonction récursive pour récupérer les dossiers def fetch_folders(parent_id=None, depth=0): if depth > 5: # Limiter la profondeur return [] folders = [] if parent_id: url = f'https://outlook.office365.com/api/v2.0/me/MailFolders/{parent_id}/ChildFolders' else: url = 'https://outlook.office365.com/api/v2.0/me/MailFolders' while url: resp = requests.get(url, headers=headers) if resp.status_code != 200: break data = resp.json() for folder_data in data.get('value', []): folders.append(folder_data) # Récupérer les sous-dossiers récursivement child_folders = fetch_folders(folder_data.get('Id'), depth + 1) folders.extend(child_folders) url = data.get('@odata.nextLink') return folders all_folders = fetch_folders() synced = 0 for folder_data in all_folders: folder_id = folder_data.get('Id') existing = OutlookFolder.query.filter_by(id=folder_id).first() if not existing: folder_name = folder_data.get('DisplayName') or folder_data.get('Name') or 'Inconnu' folder_type = folder_data.get('WellKnownFolderName', 'custom') default_folders = { 'inbox': 'Boîte de réception', 'sentitems': 'Éléments envoyés', 'drafts': 'Brouillons', 'deleteditems': 'Éléments supprimés', 'junkemail': 'Courrier indésirable', 'outbox': "Boîte d'envoi", 'archive': 'Archive' } display_name = default_folders.get(folder_type, folder_name) folder = OutlookFolder( id=folder_id, account_id=account.id, name=folder_name, display_name=display_name, parent_id=folder_data.get('ParentFolderId'), total_items=folder_data.get('TotalItemCount', 0), unread_items=folder_data.get('UnreadItemCount', 0), folder_type=folder_type ) db.session.add(folder) synced += 1 else: existing.total_items = folder_data.get('TotalItemCount', 0) existing.unread_items = folder_data.get('UnreadItemCount', 0) folder_name = folder_data.get('DisplayName') or folder_data.get('Name') or existing.name existing.name = folder_name folder_type = folder_data.get('WellKnownFolderName', 'custom') default_folders = { 'inbox': 'Boîte de réception', 'sentitems': 'Éléments envoyés', 'drafts': 'Brouillons', 'deleteditems': 'Éléments supprimés', 'junkemail': 'Courrier indésirable', 'outbox': "Boîte d'envoi", 'archive': 'Archive' } existing.display_name = default_folders.get(folder_type, folder_name) db.session.commit() return jsonify({ 'success': True, 'message': f'{synced} nouveaux dossiers synchronisés', 'total': len(all_folders) }) except Exception as e: import traceback traceback.print_exc() return jsonify({'error': f'Erreur: {str(e)}'}), 500 @sync_bp.route('/api/sync-mails//') @login_required def api_sync_mails(account_id, folder_id='inbox'): """Synchronise les mails d'un dossier avec pagination complète.""" import requests from datetime import datetime, timezone account = OutlookAccount.query.get(account_id) if not account or (account.user_id != current_user.id and current_user.role != 'admin'): return jsonify({'error': 'Compte non trouvé'}), 404 access_token, error = get_access_token(account) if error: return jsonify({'error': error}), 401 # Paramètres de pagination max_messages = int(request.args.get('max', 200)) # Max messages à récupérer try: # Trouver le dossier folder = None if folder_id != 'inbox': folder = OutlookFolder.query.get(folder_id) else: # Chercher par folder_type ou par nom (Boîte de réception) folder = OutlookFolder.query.filter_by(account_id=account_id, folder_type='inbox').first() if not folder: folder = OutlookFolder.query.filter( OutlookFolder.account_id == account_id, OutlookFolder.name.like('%ception%') ).first() if not folder: return jsonify({'error': 'Dossier non trouvé'}), 404 headers = {'Authorization': f'Bearer {access_token}'} # Récupérer tous les messages avec pagination all_mails = [] url = f'https://outlook.office365.com/api/v2.0/me/MailFolders/{folder.id}/messages?$top=100&$orderby=ReceivedDateTime desc&$select=Id,Subject,From,ReceivedDateTime,IsRead,HasAttachments,BodyPreview,Body,ToRecipients,CcRecipients,Importance' while url and len(all_mails) < max_messages: resp = requests.get(url, headers=headers) if resp.status_code != 200: error_data = resp.json() error_msg = error_data.get('error', {}).get('message', resp.text) if isinstance(error_data, dict) else resp.text return jsonify({'error': f'Erreur API Outlook: {error_msg}'}), 500 data = resp.json() mails_data = data.get('value', []) all_mails.extend(mails_data) # Vérifier s'il y a une page suivante url = data.get('@odata.nextLink') # Arrêter si on a atteint la limite if len(all_mails) >= max_messages: break saved_count = 0 updated_count = 0 for mail_data in all_mails: mail_id = mail_data.get('Id') existing = OutlookMail.query.filter_by(id=mail_id).first() from_addr = mail_data.get('From', {}).get('EmailAddress', {}) body = mail_data.get('Body', {}) received_str = mail_data.get('ReceivedDateTime', '') try: received_at = datetime.fromisoformat(received_str.replace('Z', '+00:00')) except: received_at = datetime.now(timezone.utc) # Destinataires import json to_recipients = json.dumps([r.get('EmailAddress', {}).get('Address', '') for r in mail_data.get('ToRecipients', [])]) cc_recipients = json.dumps([r.get('EmailAddress', {}).get('Address', '') for r in mail_data.get('CcRecipients', [])]) if not existing: mail = OutlookMail( id=mail_id, account_id=account.id, folder_id=folder.id, subject=mail_data.get('Subject', ''), from_name=from_addr.get('Name', ''), from_email=from_addr.get('Address', ''), body_preview=mail_data.get('BodyPreview', ''), body_content=body.get('Content', ''), body_type=body.get('ContentType', 'text'), received_at=received_at, is_read=mail_data.get('IsRead', False), importance=mail_data.get('Importance', 'normal'), has_attachments=mail_data.get('HasAttachments', False), to_recipients=to_recipients, cc_recipients=cc_recipients ) db.session.add(mail) saved_count += 1 else: # Mettre à jour le statut de lecture if existing.is_read != mail_data.get('IsRead', False): existing.is_read = mail_data.get('IsRead', False) updated_count += 1 # Mettre à jour les infos du dossier folder.total_items = len(all_mails) folder.unread_items = sum(1 for m in all_mails if not m.get('IsRead', False)) account.last_connected_at = datetime.now(timezone.utc) db.session.commit() return jsonify({ 'success': True, 'message': f'{saved_count} nouveaux mails, {updated_count} mis à jour sur {len(all_mails)} récupérés', 'total': len(all_mails), 'new': saved_count, 'updated': updated_count }) except Exception as e: import traceback traceback.print_exc() return jsonify({'error': f'Erreur: {str(e)}'}), 500 # ─── Pièces jointes ───────────────────────────────────────────────────────── @sync_bp.route('/api/sync-attachments/') @login_required def api_sync_attachments(mail_id): """Synchronise les pièces jointes d'un email.""" import requests mail = OutlookMail.query.get_or_404(mail_id) account = OutlookAccount.query.get_or_404(mail.account_id) access_token, error = get_access_token(account) if error: return jsonify({'error': error}), 401 try: headers = {'Authorization': f'Bearer {access_token}'} resp = requests.get( f'https://outlook.office365.com/api/v2.0/me/messages/{mail_id}/attachments', headers=headers ) if resp.status_code != 200: return jsonify({'error': f'Erreur API: {resp.text}'}), 500 attachments_data = resp.json().get('value', []) synced = 0 for att_data in attachments_data: att_id = att_data.get('Id') existing = OutlookAttachment.query.filter_by(id=att_id).first() if not existing: attachment = OutlookAttachment( id=att_id, mail_id=mail_id, name=att_data.get('Name', 'sans_nom'), content_type=att_data.get('ContentType'), size=att_data.get('Size', 0), is_inline=att_data.get('IsInline', False), content_id=att_data.get('ContentId') ) db.session.add(attachment) synced += 1 db.session.commit() return jsonify({ 'success': True, 'message': f'{synced} nouvelles pièces jointes', 'total': len(attachments_data) }) except Exception as e: import traceback traceback.print_exc() return jsonify({'error': f'Erreur: {str(e)}'}), 500 @sync_bp.route('/attachment//download') @login_required def download_attachment(att_id): """Télécharge une pièce jointe.""" import requests attachment = OutlookAttachment.query.get_or_404(att_id) mail = OutlookMail.query.get_or_404(attachment.mail_id) account = OutlookAccount.query.get_or_404(mail.account_id) access_token, error = get_access_token(account) if error: flash(error, 'danger') return redirect(url_for('outlook_pages.mail_view', account_id=account.id, mail_id=mail.id)) try: headers = {'Authorization': f'Bearer {access_token}'} resp = requests.get( f'https://outlook.office365.com/api/v2.0/me/messages/{mail.id}/attachments/{att_id}/$value', headers=headers, stream=True ) if resp.status_code != 200: flash('Erreur lors du téléchargement', 'danger') return redirect(url_for('outlook_pages.mail_view', account_id=account.id, mail_id=mail.id)) # Sauvegarder temporairement import tempfile temp_dir = tempfile.gettempdir() temp_path = os.path.join(temp_dir, attachment.name) with open(temp_path, 'wb') as f: for chunk in resp.iter_content(chunk_size=8192): f.write(chunk) return send_file(temp_path, as_attachment=True, download_name=attachment.name) except Exception as e: flash(f'Erreur: {str(e)}', 'danger') return redirect(url_for('outlook_pages.mail_view', account_id=account.id, mail_id=mail.id))