From 34393b0807ef6bc025e160f2ca29939344a39c79 Mon Sep 17 00:00:00 2001 From: Andreas Knuth Date: Sat, 14 Jun 2025 20:50:01 -0500 Subject: [PATCH] /stats/ --- email_api/email_api/app.py | 100 +++++++++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 14 deletions(-) diff --git a/email_api/email_api/app.py b/email_api/email_api/app.py index e4aa8d1..2006c1f 100644 --- a/email_api/email_api/app.py +++ b/email_api/email_api/app.py @@ -7,8 +7,6 @@ import logging import os import time import boto3 -from pathlib import Path -from dotenv import load_dotenv from email.parser import BytesParser from email.policy import default from email.utils import getaddresses @@ -16,40 +14,114 @@ from email.utils import getaddresses if sys.version_info < (3, 12): raise RuntimeError("Python 3.12 oder höher erforderlich") -load_dotenv() -app = Flask(__name__) -logging.basicConfig(level=logging.INFO) +# --- Logging mit Timestamp --- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s %(message)s", + datefmt="%Y-%m-%d %H:%M:%S" +) logger = logging.getLogger(__name__) +load_dotenv = None +try: + from dotenv import load_dotenv as _ld + load_dotenv = _ld +except ImportError: + pass + +if load_dotenv: + load_dotenv() + +app = Flask(__name__) + SMTP_HOST = "localhost" SMTP_PORT = 25 API_TOKEN = os.environ.get('API_TOKEN') AWS_REGION = os.environ.get('AWS_REGION', 'us-east-1') s3_client = boto3.client('s3', region_name=AWS_REGION) -processed_requests = {} - -def load_domains_config(): - return {"andreasknuth.de": {"bucket": "andreasknuth-de-emails"}} - -def mark_email_as_processed(bucket, key): +def mark_email_as_processed(bucket, key, status, processor='rest-api'): + """Setzt processed-Metadaten auf einen beliebigen Status.""" try: s3_client.copy_object( Bucket=bucket, Key=key, CopySource={'Bucket': bucket, 'Key': key}, Metadata={ - 'processed': 'true', + 'processed': status, 'processed_timestamp': str(int(time.time())), - 'processor': 'rest-api' + 'processor': processor }, MetadataDirective='REPLACE' ) return True except Exception as e: - logger.error(f"Fehler beim Markieren: {e}") + logger.error(f"Fehler beim Markieren {bucket}/{key}: {e}") return False +@app.route('/stats/', methods=['GET']) +def stats_domain(domain): + # Auth + auth = request.headers.get('Authorization') + if auth != f'Bearer {API_TOKEN}': + return jsonify({'error': 'Unauthorized'}), 401 + + bucket = domain.replace('.', '-') + '-emails' + paginator = s3_client.get_paginator('list_objects_v2') + + total = 0 + counts = { + 'true': 0, + 'unknownDomain': 0, + 'unknownUser': 0 + } + details = { + 'unknownDomain': [], + 'unknownUser': [] + } + + for page in paginator.paginate(Bucket=bucket): + for obj in page.get('Contents', []): + key = obj['Key'] + total += 1 + + head = s3_client.head_object(Bucket=bucket, Key=key) + meta = head.get('Metadata', {}) + status = meta.get('processed', 'none') + if status in counts: + counts[status] += 1 + else: + # wir ignorieren andere Status + continue + + # Für unknownDomain und unknownUser zusätzlich E-Mail parsen + if status in ('unknownDomain', 'unknownUser'): + body = s3_client.get_object(Bucket=bucket, Key=key)['Body'].read() + try: + msg = BytesParser(policy=default).parsebytes(body) + from_addr = getaddresses(msg.get_all('from', []))[0][1] if msg.get_all('from') else None + to_addrs = [addr for _n, addr in getaddresses(msg.get_all('to', []))] + except Exception as e: + logger.error(f"Fehler beim Parsen {bucket}/{key}: {e}") + from_addr = None + to_addrs = [] + details[status].append({ + 'key': key, + 'from': from_addr, + 'to': to_addrs + }) + + result = { + 'domain': domain, + 'total_messages': total, + 'successful': counts['true'], + 'wrong_domain': counts['unknownDomain'], + 'unknown_user': counts['unknownUser'], + 'details': details + } + logger.info(f"Stats for {domain}: {result}") + return jsonify(result), 200 + @app.route('/process/', methods=['POST']) def process_email(domain): auth = request.headers.get('Authorization')