from flask import Flask, request, abort import os, requests from graph import graph_request TENANT_ID = os.getenv("TENANT_ID") SITE_PATH = os.getenv("SITE_PATH") GRAPH_BASE = "https://graph.microsoft.com/v1.0" WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET") if not WEBHOOK_SECRET: raise RuntimeError("WEBHOOK_SECRET not set") # Détection de la taille du fichier / Upload thresholds MAX_SIMPLE_UPLOAD = 4 * 1024 * 1024 # 4 MB CHUNK_SIZE = 10 * 1024 * 1024 # 10 MB app = Flask(__name__) # ───────────────────────────── # Sécurité webhook MinIO # ───────────────────────────── def verify_minio_webhook(req): auth = req.headers.get("Authorization") if not auth or not auth.startswith("Bearer "): abort(401, "Missing Authorization header") token = auth[len("Bearer "):].strip() if token != WEBHOOK_SECRET: abort(403, "Invalid MinIO webhook token") # ───────────────────────────── # SharePoint drive # ───────────────────────────── def get_drive_id(): site = graph_request( "GET", f"{GRAPH_BASE}/sites/{SITE_PATH}" ) drive = graph_request( "GET", f"{GRAPH_BASE}/sites/{site['id']}/drive" ) return drive["id"] # INITIALISATION AU DÉMARRAGE print("Initializing SharePoint drive…") DRIVE_ID = get_drive_id() print(f"Drive ID initialized: {DRIVE_ID}") # ───────────────────────────── # Upload helpers # ───────────────────────────── def create_upload_session(sp_path): res = graph_request( "POST", f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{sp_path}:/createUploadSession", json={ "item": { "@microsoft.graph.conflictBehavior": "replace" } } ) return res["uploadUrl"] def upload_simple(sp_path, data): graph_request( "PUT", f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{sp_path}:/content", data=data ) def upload_chunked(sp_path, file_path): upload_url = create_upload_session(sp_path) size = os.path.getsize(file_path) with open(file_path, "rb") as f: start = 0 while start < size: chunk = f.read(CHUNK_SIZE) end = start + len(chunk) - 1 headers = { "Content-Length": str(len(chunk)), "Content-Range": f"bytes {start}-{end}/{size}" } r = requests.put(upload_url, headers=headers, data=chunk) r.raise_for_status() start += len(chunk) # ───────────────────────────── # Event handlers # ───────────────────────────── def upload_object(key): print(f"Uploading {key} to SharePoint", flush=True) # 🔴 TODO : ici télécharger le fichier depuis MinIO vers /tmp file_path = f"/tmp/{os.path.basename(key)}" # (temporaire pour tests) with open(file_path, "wb") as f: f.write(b"TEST") size = os.path.getsize(file_path) if size <= MAX_SIMPLE_UPLOAD: with open(file_path, "rb") as f: upload_simple(key, f.read()) else: upload_chunked(key, file_path) os.remove(file_path) def delete_object(key): print(f"Deleting {key} from SharePoint", flush=True) graph_request( "DELETE", f"{GRAPH_BASE}/drives/{DRIVE_ID}/root:/{key}" ) # ───────────────────────────── # Webhook endpoint # ───────────────────────────── @app.route("/s3event", methods=["POST"]) def s3_event(): verify_minio_webhook(request) event = request.get_json() record = event["Records"][0] event_name = record["eventName"] key = record["s3"]["object"]["key"] if event_name.startswith("s3:ObjectCreated"): upload_object(key) elif event_name.startswith("s3:ObjectRemoved"): delete_object(key) return "", 204 if __name__ == "__main__": app.run(host="0.0.0.0", port=8080)