Refactor backend and add registry build workflow
Build and publish Docker image / build-and-publish (push) Successful in 2m45s
Build and publish Docker image / build-and-publish (push) Successful in 2m45s
This commit is contained in:
commit
c177d7981d
43 files changed
+18124
No files matched your search
@@ -0,0 +1,513 @@
|
||||
from datetime import datetime
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
import logging
|
||||
import os
|
||||
import smtplib
|
||||
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EmailService:
|
||||
def __init__(self):
|
||||
# Load SMTP configuration from environment
|
||||
self.smtp_host = os.environ.get('SMTP_HOST', '')
|
||||
self.smtp_port = int(os.environ.get('SMTP_PORT', 587))
|
||||
self.smtp_user = os.environ.get('SMTP_USER', '')
|
||||
self.smtp_password = os.environ.get('SMTP_PASSWORD', '')
|
||||
self.smtp_from = os.environ.get('SMTP_FROM', '')
|
||||
self.smtp_from_name = os.environ.get('SMTP_FROM_NAME', 'AnchorHost')
|
||||
|
||||
# TLS configuration - IMPORTANT!
|
||||
use_tls_str = os.environ.get('SMTP_USE_TLS', '').lower()
|
||||
# Default to True for port 587, False for other ports
|
||||
if use_tls_str:
|
||||
self.use_tls = use_tls_str in ('true', '1', 'yes', 'on')
|
||||
else:
|
||||
# Default: Use TLS on port 587, not on other ports
|
||||
self.use_tls = (self.smtp_port == 587)
|
||||
|
||||
# Base URL for verification links
|
||||
self.base_url = os.environ.get('BASE_URL', 'http://localhost:8080')
|
||||
|
||||
# Validate configuration
|
||||
if not self.smtp_password:
|
||||
logger.warning("⚠️ SMTP_PASSWORD not set - email sending will fail!")
|
||||
|
||||
logger.info(f"Email Service initialized")
|
||||
logger.info(f"SMTP Host: {self.smtp_host}:{self.smtp_port}")
|
||||
logger.info(f"SMTP User: {self.smtp_user}")
|
||||
logger.info(f"From: {self.smtp_from_name} <{self.smtp_from}>")
|
||||
logger.info(f"Use TLS: {self.use_tls}")
|
||||
|
||||
def send_email(self, to_email: str, subject: str, html_body: str, text_body: str = None) -> bool:
|
||||
"""
|
||||
Send an email using SMTP
|
||||
|
||||
Args:
|
||||
to_email: Recipient email address
|
||||
subject: Email subject
|
||||
html_body: HTML content of the email
|
||||
text_body: Plain text fallback (optional)
|
||||
|
||||
Returns:
|
||||
bool: True if email was sent successfully
|
||||
"""
|
||||
if not all((self.smtp_host, self.smtp_user, self.smtp_password, self.smtp_from)):
|
||||
logger.warning("Email not sent because SMTP settings are incomplete")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Create message
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = f"{self.smtp_from_name} <{self.smtp_from}>"
|
||||
msg['To'] = to_email
|
||||
|
||||
# Add text part (fallback)
|
||||
if text_body:
|
||||
part1 = MIMEText(text_body, 'plain')
|
||||
msg.attach(part1)
|
||||
|
||||
# Add HTML part
|
||||
part2 = MIMEText(html_body, 'html')
|
||||
msg.attach(part2)
|
||||
|
||||
# Connect and send
|
||||
logger.info(f"Connecting to SMTP server: {self.smtp_host}:{self.smtp_port}")
|
||||
|
||||
with smtplib.SMTP(self.smtp_host, self.smtp_port) as server:
|
||||
server.ehlo()
|
||||
|
||||
# Check if TLS should be used (ONLY if explicitly enabled)
|
||||
# Default behavior: Use TLS on port 587 UNLESS SMTP_USE_TLS=false
|
||||
use_tls = self.smtp_port == 587 and self.use_tls
|
||||
|
||||
if use_tls:
|
||||
logger.info("Starting TLS...")
|
||||
try:
|
||||
server.starttls()
|
||||
server.ehlo()
|
||||
except smtplib.SMTPNotSupportedError:
|
||||
logger.warning("TLS not supported by server, continuing without encryption")
|
||||
else:
|
||||
logger.info("Skipping TLS (not enabled or not port 587)")
|
||||
|
||||
# Login
|
||||
logger.info(f"Logging in as: {self.smtp_user}")
|
||||
server.login(self.smtp_user, self.smtp_password)
|
||||
|
||||
# Send email
|
||||
logger.info(f"Sending email to: {to_email}")
|
||||
server.send_message(msg)
|
||||
|
||||
logger.info(f"✅ Email sent successfully to {to_email}")
|
||||
return True
|
||||
|
||||
except smtplib.SMTPAuthenticationError as e:
|
||||
logger.error(f"❌ SMTP Authentication failed: {e}")
|
||||
return False
|
||||
except smtplib.SMTPException as e:
|
||||
logger.error(f"❌ SMTP error: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error sending email: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
def send_verification_email(self, to_email: str, username: str, token: str) -> bool:
|
||||
"""
|
||||
Send email verification email
|
||||
|
||||
Args:
|
||||
to_email: User's email address
|
||||
username: Username
|
||||
token: Verification token
|
||||
|
||||
Returns:
|
||||
bool: True if email was sent successfully
|
||||
"""
|
||||
verification_url = f"{self.base_url}/verify-email?token={token}"
|
||||
|
||||
subject = "Verify your AnchorHost account"
|
||||
|
||||
# HTML email template
|
||||
html_body = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Verify Your Email</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #0f0f0f;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #0f0f0f; padding: 40px 20px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #1a1a1a; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);">
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background: linear-gradient(135deg, #5a7ca7 0%, #4a6385 100%); padding: 40px 30px; text-align: center;">
|
||||
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: bold;">
|
||||
AnchorHost
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Body -->
|
||||
<tr>
|
||||
<td style="padding: 40px 30px;">
|
||||
<h2 style="margin: 0 0 20px 0; color: #cbcbcd; font-size: 24px;">
|
||||
Welcome to AnchorHost, {username}!
|
||||
</h2>
|
||||
|
||||
<p style="margin: 0 0 20px 0; color: #8a8a8a; font-size: 16px; line-height: 1.6;">
|
||||
Thank you for registering with AnchorHost. To complete your registration and start using our services, please verify your email address.
|
||||
</p>
|
||||
|
||||
<p style="margin: 0 0 30px 0; color: #8a8a8a; font-size: 16px; line-height: 1.6;">
|
||||
Click the button below to verify your email:
|
||||
</p>
|
||||
|
||||
<!-- Verification Button -->
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center" style="padding: 20px 0;">
|
||||
<a href="{verification_url}" style="display: inline-block; padding: 16px 40px; background: linear-gradient(135deg, #5a7ca7 0%, #4a6385 100%); color: #ffffff; text-decoration: none; font-size: 16px; font-weight: bold; border-radius: 8px; box-shadow: 0 4px 6px rgba(90, 124, 167, 0.3);">
|
||||
Verify Email Address
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin: 30px 0 20px 0; color: #8a8a8a; font-size: 14px; line-height: 1.6;">
|
||||
If the button doesn't work, copy and paste this link into your browser:
|
||||
</p>
|
||||
|
||||
<p style="margin: 0 0 30px 0; padding: 15px; background-color: #262626; border-radius: 6px; color: #5a7ca7; font-size: 13px; word-break: break-all; border-left: 3px solid #5a7ca7;">
|
||||
{verification_url}
|
||||
</p>
|
||||
|
||||
<p style="margin: 0 0 10px 0; color: #8a8a8a; font-size: 14px; line-height: 1.6;">
|
||||
<strong style="color: #cbcbcd;">Important:</strong> This verification link will expire in 24 hours.
|
||||
</p>
|
||||
|
||||
<p style="margin: 0; color: #8a8a8a; font-size: 14px; line-height: 1.6;">
|
||||
If you didn't create an account with AnchorHost, please ignore this email.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color: #161616; padding: 30px; text-align: center; border-top: 1px solid #262626;">
|
||||
<p style="margin: 0 0 10px 0; color: #8a8a8a; font-size: 12px;">
|
||||
© 2024 AnchorHost. All rights reserved.
|
||||
</p>
|
||||
<p style="margin: 0; color: #8a8a8a; font-size: 12px;">
|
||||
<a href="https://anchorhost.eu" style="color: #5a7ca7; text-decoration: none;">AnchorHost.eu</a> |
|
||||
<a href="mailto:support@anchorhost.eu" style="color: #5a7ca7; text-decoration: none;">Support</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Plain text fallback
|
||||
text_body = f"""
|
||||
Welcome to AnchorHost, {username}!
|
||||
|
||||
Thank you for registering. Please verify your email address by clicking the link below:
|
||||
|
||||
{verification_url}
|
||||
|
||||
This link will expire in 24 hours.
|
||||
|
||||
If you didn't create an account with AnchorHost, please ignore this email.
|
||||
|
||||
---
|
||||
AnchorHost.eu
|
||||
"""
|
||||
|
||||
return self.send_email(to_email, subject, html_body, text_body)
|
||||
|
||||
def send_password_reset_email(self, to_email: str, username: str, token: str) -> bool:
|
||||
"""
|
||||
Send password reset email
|
||||
|
||||
Args:
|
||||
to_email: User's email address
|
||||
username: Username
|
||||
token: Reset token
|
||||
|
||||
Returns:
|
||||
bool: True if email was sent successfully
|
||||
"""
|
||||
reset_url = f"{self.base_url}/reset-password?token={token}"
|
||||
|
||||
subject = "Reset your AnchorHost password"
|
||||
|
||||
html_body = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Reset Your Password</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #0f0f0f;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #0f0f0f; padding: 40px 20px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #1a1a1a; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);">
|
||||
<tr>
|
||||
<td style="background: linear-gradient(135deg, #8b2d32 0%, #a83a40 100%); padding: 40px 30px; text-align: center;">
|
||||
<h1 style="margin: 0; color: #ffffff; font-size: 28px; font-weight: bold;">
|
||||
Password Reset
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding: 40px 30px;">
|
||||
<h2 style="margin: 0 0 20px 0; color: #cbcbcd; font-size: 24px;">
|
||||
Reset your password, {username}
|
||||
</h2>
|
||||
|
||||
<p style="margin: 0 0 20px 0; color: #8a8a8a; font-size: 16px; line-height: 1.6;">
|
||||
We received a request to reset your password. Click the button below to create a new password:
|
||||
</p>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center" style="padding: 20px 0;">
|
||||
<a href="{reset_url}" style="display: inline-block; padding: 16px 40px; background: linear-gradient(135deg, #8b2d32 0%, #a83a40 100%); color: #ffffff; text-decoration: none; font-size: 16px; font-weight: bold; border-radius: 8px;">
|
||||
Reset Password
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin: 30px 0 20px 0; color: #8a8a8a; font-size: 14px; line-height: 1.6;">
|
||||
Or copy this link: {reset_url}
|
||||
</p>
|
||||
|
||||
<p style="margin: 0; color: #8a8a8a; font-size: 14px; line-height: 1.6;">
|
||||
This link expires in 1 hour. If you didn't request this, please ignore this email.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="background-color: #161616; padding: 30px; text-align: center; border-top: 1px solid #262626;">
|
||||
<p style="margin: 0; color: #8a8a8a; font-size: 12px;">
|
||||
© 2024 AnchorHost. All rights reserved.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
text_body = f"""
|
||||
Reset your password, {username}
|
||||
|
||||
We received a request to reset your password. Click the link below:
|
||||
|
||||
{reset_url}
|
||||
|
||||
This link expires in 1 hour. If you didn't request this, ignore this email.
|
||||
|
||||
---
|
||||
AnchorHost.eu
|
||||
"""
|
||||
|
||||
return self.send_email(to_email, subject, html_body, text_body)
|
||||
|
||||
def send_welcome_email(self, to_email: str, username: str) -> bool:
|
||||
"""Send welcome email after successful verification"""
|
||||
subject = "Welcome to AnchorHost!"
|
||||
|
||||
html_body = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #0f0f0f;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #0f0f0f; padding: 40px 20px;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #1a1a1a; border-radius: 12px; overflow: hidden;">
|
||||
<tr>
|
||||
<td style="background: linear-gradient(135deg, #5a6b47 0%, #6b7d55 100%); padding: 40px 30px; text-align: center;">
|
||||
<h1 style="margin: 0; color: #ffffff; font-size: 28px;">🎉 Welcome to AnchorHost!</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="padding: 40px 30px;">
|
||||
<h2 style="margin: 0 0 20px 0; color: #cbcbcd; font-size: 24px;">
|
||||
You're all set, {username}!
|
||||
</h2>
|
||||
|
||||
<p style="margin: 0 0 20px 0; color: #8a8a8a; font-size: 16px; line-height: 1.6;">
|
||||
Your email has been verified and your account is now active. You can start deploying servers right away!
|
||||
</p>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center" style="padding: 20px 0;">
|
||||
<a href="https://anchorhost.eu/dashboard" style="display: inline-block; padding: 16px 40px; background: linear-gradient(135deg, #5a6b47 0%, #6b7d55 100%); color: #ffffff; text-decoration: none; font-size: 16px; font-weight: bold; border-radius: 8px;">
|
||||
Go to Dashboard
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="background-color: #161616; padding: 30px; text-align: center;">
|
||||
<p style="margin: 0; color: #8a8a8a; font-size: 12px;">
|
||||
© 2024 AnchorHost. All rights reserved.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
return self.send_email(to_email, subject, html_body)
|
||||
|
||||
def send_monthly_fee_email(self, to_email: str, username: str, server_name: str, amount: int, new_balance: int) -> bool:
|
||||
"""Send monthly fee confirmation email"""
|
||||
try:
|
||||
subject = f"AnchorHost - Monthly Fee for {server_name}"
|
||||
|
||||
# Convert credits to the displayed euro amount.
|
||||
amount_euro = amount / 100
|
||||
|
||||
body = f"""
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<div style="max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 5px;">
|
||||
<h2 style="color: #4a6fa5;">Monthly Server Fee</h2>
|
||||
<p>Hello {username},</p>
|
||||
<p>Your monthly fee for server <strong>{server_name}</strong> has been processed.</p>
|
||||
|
||||
<div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0;">
|
||||
<h3 style="margin-top: 0;">Payment Details:</h3>
|
||||
<p><strong>Server:</strong> {server_name}</p>
|
||||
<p><strong>Amount:</strong> {amount} credits ({amount_euro:.2f} EUR)</p>
|
||||
<p><strong>Date:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
|
||||
<p><strong>New Balance:</strong> {new_balance} credits</p>
|
||||
</div>
|
||||
|
||||
<p>Your server will remain active as long as you maintain sufficient credits.</p>
|
||||
<p>You can add more credits at any time from your dashboard.</p>
|
||||
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
|
||||
|
||||
<p style="font-size: 12px; color: #666;">
|
||||
If you have any questions, please contact our support team.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
return self.send_email(to_email, subject, body)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending monthly fee email: {e}")
|
||||
return False
|
||||
|
||||
def send_server_suspended_email(self, to_email: str, username: str, server_name: str, required_amount: int, current_balance: int) -> bool:
|
||||
"""Send server suspension email due to insufficient credits"""
|
||||
try:
|
||||
subject = f"AnchorHost - Server {server_name} Suspended"
|
||||
|
||||
required_euro = required_amount / 100
|
||||
|
||||
body = f"""
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<div style="max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 5px;">
|
||||
<h2 style="color: #d9534f;">Server Suspended</h2>
|
||||
<p>Hello {username},</p>
|
||||
<p>Your server <strong>{server_name}</strong> has been <strong>suspended</strong> due to insufficient credits.</p>
|
||||
|
||||
<div style="background-color: #f2dede; padding: 15px; border-radius: 5px; margin: 20px 0; border: 1px solid #ebccd1;">
|
||||
<h3 style="margin-top: 0; color: #a94442;">Important Notice:</h3>
|
||||
<p><strong>Required:</strong> {required_amount} credits ({required_euro:.2f} EUR)</p>
|
||||
<p><strong>Current Balance:</strong> {current_balance} credits</p>
|
||||
<p><strong>Missing:</strong> {required_amount - current_balance} credits</p>
|
||||
</div>
|
||||
|
||||
<p>To reactivate your server:</p>
|
||||
<ol>
|
||||
<li>Log in to your AnchorHost account</li>
|
||||
<li>Add credits to your account (minimum {required_amount - current_balance} credits)</li>
|
||||
<li>Start your server from the dashboard</li>
|
||||
</ol>
|
||||
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="https://your-domain.com/dashboard" style="background-color: #5a7ca7; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold;">
|
||||
Add Credits Now
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p><strong>Note:</strong> Your server data is preserved for 30 days. After this period, it may be permanently deleted.</p>
|
||||
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
|
||||
|
||||
<p style="font-size: 12px; color: #666;">
|
||||
If you believe this is an error, please contact our support team immediately.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
return self.send_email(to_email, subject, body)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending suspension email: {e}")
|
||||
return False
|
||||
|
||||
# Initialize global email service instance
|
||||
email_service = None
|
||||
|
||||
def init_email_service():
|
||||
"""Initialize the email service"""
|
||||
global email_service
|
||||
try:
|
||||
email_service = EmailService()
|
||||
logger.info("✅ Email service initialized")
|
||||
return email_service
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to initialize email service: {e}")
|
||||
return None
|
||||
|
||||
def get_email_service():
|
||||
"""Get the email service instance"""
|
||||
global email_service
|
||||
if email_service is None:
|
||||
return init_email_service()
|
||||
return email_service
|
||||
Reference in new issue
Block a user