diff --git a/config/config.yaml b/config/config.yaml index 5f58ab5..e70353d 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -14,11 +14,9 @@ server: companyname: "99tales Gmbh" res_id_source_context: "99tales" - - logger: - level: "INFO" # Set to DEBUG for more verbose output - file: "alpinebits.log" # Log file path, or null for console only + level: "INFO" # Set to DEBUG for more verbose output + file: "alpinebits.log" # Log file path, or null for console only alpine_bits_auth: - hotel_id: "39054_001" @@ -40,48 +38,48 @@ alpine_bits_auth: username: "erika" password: !secret ERIKA_PASSWORD +api_tokens: + - tLTI8wXF1OVEvUX7kdZRhSW3Qr5feBCz0mHo-kbnEp0 + # Email configuration for monitoring and alerts email: # SMTP server configuration smtp: - host: "smtp.titan.email" # Your SMTP server - port: 465 # Usually 587 for TLS, 465 for SSL - username: info@99tales.net # SMTP username - password: !secret EMAIL_PASSWORD # SMTP password - use_tls: false # Use STARTTLS - use_ssl: true # Use SSL/TLS from start + host: "smtp.titan.email" # Your SMTP server + port: 465 # Usually 587 for TLS, 465 for SSL + username: info@99tales.net # SMTP username + password: !secret EMAIL_PASSWORD # SMTP password + use_tls: false # Use STARTTLS + use_ssl: true # Use SSL/TLS from start # Email addresses - from_address: "info@99tales.net" # Sender address - from_name: "AlpineBits Monitor" # Sender display name - - api_tokens: - - tLTI8wXF1OVEvUX7kdZRhSW3Qr5feBCz0mHo-kbnEp0 + from_address: "info@99tales.net" # Sender address + from_name: "AlpineBits Monitor" # Sender display name # Monitoring and alerting monitoring: # Daily report configuration daily_report: - enabled: false # Set to true to enable daily reports + enabled: false # Set to true to enable daily reports recipients: - "jonas@vaius.ai" #- "dev@99tales.com" - send_time: "08:00" # Time to send daily report (24h format, local time) - include_stats: true # Include reservation/customer stats - include_errors: true # Include error summary + send_time: "08:00" # Time to send daily report (24h format, local time) + include_stats: true # Include reservation/customer stats + include_errors: true # Include error summary # Error alert configuration (hybrid approach) error_alerts: - enabled: false # Set to true to enable error alerts + enabled: false # Set to true to enable error alerts recipients: - "jonas@vaius.ai" #- "oncall@99tales.com" # Alert is sent immediately if threshold is reached - error_threshold: 5 # Send immediate alert after N errors + error_threshold: 5 # Send immediate alert after N errors # Otherwise, alert is sent after buffer time expires - buffer_minutes: 15 # Wait N minutes before sending buffered errors + buffer_minutes: 15 # Wait N minutes before sending buffered errors # Cooldown period to prevent alert spam - cooldown_minutes: 15 # Wait N min before sending another alert + cooldown_minutes: 15 # Wait N min before sending another alert # Error severity levels to monitor log_levels: - "ERROR" diff --git a/src/alpine_bits_python/api.py b/src/alpine_bits_python/api.py index 983ab32..0af607b 100644 --- a/src/alpine_bits_python/api.py +++ b/src/alpine_bits_python/api.py @@ -299,10 +299,11 @@ async def lifespan(app: FastAPI): report_scheduler.set_stats_collector(stats_collector.collect_stats) _LOGGER.info("Stats collector initialized and hooked up to report scheduler") - # Send a test daily report on startup for testing - _LOGGER.info("Sending test daily report on startup") + # Send a test daily report on startup for testing (with 24-hour lookback) + _LOGGER.info("Sending test daily report on startup (last 24 hours)") try: - stats = await stats_collector.collect_stats() + # Use lookback_hours=24 to get stats from last 24 hours + stats = await stats_collector.collect_stats(lookback_hours=24) success = await email_service.send_daily_report( recipients=report_scheduler.recipients, stats=stats, diff --git a/src/alpine_bits_python/email_monitoring.py b/src/alpine_bits_python/email_monitoring.py index 8777748..0c7ce55 100644 --- a/src/alpine_bits_python/email_monitoring.py +++ b/src/alpine_bits_python/email_monitoring.py @@ -492,16 +492,27 @@ class ReservationStatsCollector: len(self._hotel_map), ) - async def collect_stats(self) -> dict[str, Any]: + async def collect_stats(self, lookback_hours: int | None = None) -> dict[str, Any]: """Collect reservation statistics for the reporting period. + Args: + lookback_hours: Optional override to look back N hours from now. + If None, uses time since last report. + Returns: Dictionary with statistics including reservations per hotel """ now = datetime.now() - period_start = self._last_report_time - period_end = now + + if lookback_hours is not None: + # Override mode: look back N hours from now + period_start = now - timedelta(hours=lookback_hours) + period_end = now + else: + # Normal mode: since last report + period_start = self._last_report_time + period_end = now _LOGGER.info( "Collecting reservation stats from %s to %s", @@ -538,8 +549,9 @@ class ReservationStatsCollector: # Sort by reservation count descending hotels_stats.sort(key=lambda x: x["reservations"], reverse=True) - # Update last report time - self._last_report_time = now + # Update last report time only in normal mode (not lookback mode) + if lookback_hours is None: + self._last_report_time = now stats = { "reporting_period": { diff --git a/test_smtp.py b/test_smtp.py new file mode 100644 index 0000000..69dba22 --- /dev/null +++ b/test_smtp.py @@ -0,0 +1,294 @@ +#!/usr/bin/env python3 +"""Test script to diagnose SMTP connection issues. + +This script tests SMTP connectivity with different configurations to help +identify whether the issue is with credentials, network, ports, or TLS settings. +""" + +import smtplib +import ssl +import sys +from datetime import datetime +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + +# Load configuration from config.yaml +try: + from alpine_bits_python.config_loader import load_config + + print("Loading configuration from config.yaml...") + config = load_config() + email_config = config.get("email", {}) + smtp_config = email_config.get("smtp", {}) + + SMTP_HOST = smtp_config.get("host", "smtp.titan.email") + SMTP_PORT = smtp_config.get("port", 465) + SMTP_USERNAME = smtp_config.get("username", "") + SMTP_PASSWORD = smtp_config.get("password", "") + USE_TLS = smtp_config.get("use_tls", False) + USE_SSL = smtp_config.get("use_ssl", True) + FROM_ADDRESS = email_config.get("from_address", "info@99tales.net") + FROM_NAME = email_config.get("from_name", "AlpineBits Monitor") + + # Get test recipient + monitoring_config = email_config.get("monitoring", {}) + daily_report = monitoring_config.get("daily_report", {}) + recipients = daily_report.get("recipients", []) + TEST_RECIPIENT = recipients[0] if recipients else "jonas@vaius.ai" + + print(f"✓ Configuration loaded successfully") + print(f" SMTP Host: {SMTP_HOST}") + print(f" SMTP Port: {SMTP_PORT}") + print(f" Username: {SMTP_USERNAME}") + print(f" Password: {'***' if SMTP_PASSWORD else '(not set)'}") + print(f" Use SSL: {USE_SSL}") + print(f" Use TLS: {USE_TLS}") + print(f" From: {FROM_ADDRESS}") + print(f" Test Recipient: {TEST_RECIPIENT}") + print() + +except Exception as e: + print(f"✗ Failed to load configuration: {e}") + print("Using default values for testing...") + SMTP_HOST = "smtp.titan.email" + SMTP_PORT = 465 + SMTP_USERNAME = input("Enter SMTP username: ") + SMTP_PASSWORD = input("Enter SMTP password: ") + USE_TLS = False + USE_SSL = True + FROM_ADDRESS = "info@99tales.net" + FROM_NAME = "AlpineBits Monitor" + TEST_RECIPIENT = input("Enter test recipient email: ") + print() + + +def create_test_message(subject: str) -> MIMEMultipart: + """Create a test email message.""" + msg = MIMEMultipart("alternative") + msg["Subject"] = subject + msg["From"] = f"{FROM_NAME} <{FROM_ADDRESS}>" + msg["To"] = TEST_RECIPIENT + msg["Date"] = datetime.now().strftime("%a, %d %b %Y %H:%M:%S %z") + + body = f"""SMTP Connection Test - {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} + +This is a test email to verify SMTP connectivity. + +Configuration: +- SMTP Host: {SMTP_HOST} +- SMTP Port: {SMTP_PORT} +- Use SSL: {USE_SSL} +- Use TLS: {USE_TLS} + +If you received this email, the SMTP configuration is working correctly! +""" + + msg.attach(MIMEText(body, "plain")) + return msg + + +def test_smtp_connection(host: str, port: int, timeout: int = 10) -> bool: + """Test basic TCP connection to SMTP server.""" + import socket + + print(f"Test 1: Testing TCP connection to {host}:{port}...") + try: + sock = socket.create_connection((host, port), timeout=timeout) + sock.close() + print(f"✓ TCP connection successful to {host}:{port}") + return True + except socket.timeout: + print(f"✗ Connection timed out after {timeout} seconds") + print(f" This suggests a network/firewall issue blocking access to {host}:{port}") + return False + except socket.error as e: + print(f"✗ Connection failed: {e}") + return False + + +def test_smtp_ssl(host: str, port: int, username: str, password: str, timeout: int = 30) -> bool: + """Test SMTP connection with SSL.""" + print(f"\nTest 2: Testing SMTP with SSL (port {port})...") + try: + context = ssl.create_default_context() + with smtplib.SMTP_SSL(host, port, timeout=timeout, context=context) as server: + print(f"✓ Connected to SMTP server with SSL") + + # Try to get server info + server.ehlo() + print(f"✓ EHLO successful") + + # Try authentication if credentials provided + if username and password: + print(f" Attempting authentication as: {username}") + server.login(username, password) + print(f"✓ Authentication successful") + else: + print(f"⚠ No credentials provided, skipping authentication") + + return True + + except smtplib.SMTPAuthenticationError as e: + print(f"✗ Authentication failed: {e}") + print(f" Check your username and password") + return False + except socket.timeout: + print(f"✗ Connection timed out after {timeout} seconds") + print(f" Try increasing timeout or check network/firewall") + return False + except Exception as e: + print(f"✗ SMTP SSL failed: {e}") + return False + + +def test_smtp_tls(host: str, port: int, username: str, password: str, timeout: int = 30) -> bool: + """Test SMTP connection with STARTTLS.""" + print(f"\nTest 3: Testing SMTP with STARTTLS (port {port})...") + try: + with smtplib.SMTP(host, port, timeout=timeout) as server: + print(f"✓ Connected to SMTP server") + + # Try STARTTLS + context = ssl.create_default_context() + server.starttls(context=context) + print(f"✓ STARTTLS successful") + + # Try authentication if credentials provided + if username and password: + print(f" Attempting authentication as: {username}") + server.login(username, password) + print(f"✓ Authentication successful") + else: + print(f"⚠ No credentials provided, skipping authentication") + + return True + + except smtplib.SMTPAuthenticationError as e: + print(f"✗ Authentication failed: {e}") + return False + except socket.timeout: + print(f"✗ Connection timed out after {timeout} seconds") + return False + except Exception as e: + print(f"✗ SMTP TLS failed: {e}") + return False + + +def send_test_email(host: str, port: int, username: str, password: str, + use_ssl: bool, use_tls: bool, timeout: int = 30) -> bool: + """Send an actual test email.""" + print(f"\nTest 4: Sending test email...") + try: + msg = create_test_message("SMTP Test Email - AlpineBits") + + if use_ssl: + context = ssl.create_default_context() + with smtplib.SMTP_SSL(host, port, timeout=timeout, context=context) as server: + if username and password: + server.login(username, password) + server.send_message(msg, FROM_ADDRESS, [TEST_RECIPIENT]) + + else: + with smtplib.SMTP(host, port, timeout=timeout) as server: + if use_tls: + context = ssl.create_default_context() + server.starttls(context=context) + + if username and password: + server.login(username, password) + + server.send_message(msg, FROM_ADDRESS, [TEST_RECIPIENT]) + + print(f"✓ Test email sent successfully to {TEST_RECIPIENT}") + print(f" Check your inbox!") + return True + + except Exception as e: + print(f"✗ Failed to send email: {e}") + return False + + +def main(): + """Run all SMTP tests.""" + print("=" * 70) + print("SMTP Connection Test Script") + print("=" * 70) + print() + + # Test 1: Basic TCP connection + tcp_ok = test_smtp_connection(SMTP_HOST, SMTP_PORT, timeout=10) + + if not tcp_ok: + print("\n" + "=" * 70) + print("DIAGNOSIS: Cannot establish TCP connection to SMTP server") + print("=" * 70) + print("\nPossible causes:") + print("1. The SMTP server is down or unreachable") + print("2. A firewall is blocking the connection") + print("3. The host or port is incorrect") + print("4. Network connectivity issues from your container/server") + print("\nTroubleshooting:") + print(f"- Verify the server is correct: {SMTP_HOST}") + print(f"- Verify the port is correct: {SMTP_PORT}") + print("- Check if your container/server has outbound internet access") + print("- Try from a different network or machine") + print(f"- Use telnet/nc to test: telnet {SMTP_HOST} {SMTP_PORT}") + return 1 + + # Test 2 & 3: Try both SSL and TLS + ssl_ok = False + tls_ok = False + + if USE_SSL: + ssl_ok = test_smtp_ssl(SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, timeout=30) + + # Also try common alternative ports + if not ssl_ok and SMTP_PORT == 465: + print("\n⚠ Port 465 failed, trying port 587 with STARTTLS...") + tls_ok = test_smtp_tls(SMTP_HOST, 587, SMTP_USERNAME, SMTP_PASSWORD, timeout=30) + + if USE_TLS: + tls_ok = test_smtp_tls(SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, timeout=30) + + if not ssl_ok and not tls_ok: + print("\n" + "=" * 70) + print("DIAGNOSIS: Cannot authenticate or establish secure connection") + print("=" * 70) + print("\nPossible causes:") + print("1. Wrong username or password") + print("2. Wrong port for the encryption method") + print("3. SSL/TLS version mismatch") + print("\nTroubleshooting:") + print("- Verify your credentials are correct") + print("- Port 465 typically uses SSL") + print("- Port 587 typically uses STARTTLS") + print("- Port 25 is usually unencrypted (not recommended)") + return 1 + + # Test 4: Send actual email + send_ok = send_test_email( + SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, + USE_SSL, USE_TLS, timeout=30 + ) + + print("\n" + "=" * 70) + if send_ok: + print("✓ ALL TESTS PASSED!") + print("=" * 70) + print("\nYour SMTP configuration is working correctly.") + print(f"Check {TEST_RECIPIENT} for the test email.") + else: + print("⚠ PARTIAL SUCCESS") + print("=" * 70) + print("\nConnection and authentication work, but email sending failed.") + print("This might be a temporary issue. Try again.") + + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except KeyboardInterrupt: + print("\n\nTest cancelled by user") + sys.exit(1)