356 lines
14 KiB
Python
356 lines
14 KiB
Python
"""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/<int:account_id>', methods=['POST'])
|
|
@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 not current_user.is_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/<int:account_id>/<folder_id>', methods=['POST'])
|
|
@sync_bp.route('/api/sync-mails/<int:account_id>', defaults={'folder_id': 'inbox'}, methods=['POST'])
|
|
@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 not current_user.is_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.filter_by(id=folder_id, account_id=account_id).first()
|
|
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/<mail_id>', methods=['POST'])
|
|
@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)
|
|
if account.user_id != current_user.id and not current_user.is_admin():
|
|
return jsonify({'error': 'Message non trouvé'}), 404
|
|
|
|
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/<att_id>/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)
|
|
if account.user_id != current_user.id and not current_user.is_admin():
|
|
return jsonify({'error': 'Pièce jointe non trouvée'}), 404
|
|
|
|
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))
|