mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-27 15:17:18 +09:00
Comments, docstrings and a few strings cited plan numbers ("plan 29
#9", "plan 86 §B2") from a git-ignored folder, so they pointed at
nothing; one reached users in a bottleneck hint. Code and frontend
strings are unchanged apart from those citations (checked by AST and
token comparison). A vhost still carrying the old asset comment reads
as undrifted, and a guard test keeps new citations out.
204 lines
8.1 KiB
Python
204 lines
8.1 KiB
Python
from datetime import datetime
|
|
from app import db
|
|
from app.models.mixins import TimestampMixin
|
|
|
|
|
|
class EmailDomain(TimestampMixin, db.Model):
|
|
__tablename__ = 'email_domains'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(255), unique=True, nullable=False, index=True)
|
|
is_active = db.Column(db.Boolean, default=True)
|
|
|
|
# DKIM
|
|
dkim_selector = db.Column(db.String(63), default='default')
|
|
dkim_private_key_path = db.Column(db.String(500))
|
|
dkim_public_key = db.Column(db.Text)
|
|
|
|
# SPF / DMARC
|
|
spf_record = db.Column(db.String(500))
|
|
dmarc_record = db.Column(db.String(500))
|
|
|
|
# DNS provider linkage
|
|
dns_provider_id = db.Column(db.Integer, db.ForeignKey('dns_provider_configs.id'), nullable=True, index=True)
|
|
dns_zone_id = db.Column(db.String(255))
|
|
|
|
|
|
# Relationships
|
|
accounts = db.relationship('EmailAccount', backref='domain', lazy=True, cascade='all, delete-orphan')
|
|
aliases = db.relationship('EmailAlias', backref='domain', lazy=True, cascade='all, delete-orphan')
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'is_active': self.is_active,
|
|
'dkim_selector': self.dkim_selector,
|
|
'dkim_public_key': self.dkim_public_key,
|
|
'spf_record': self.spf_record,
|
|
'dmarc_record': self.dmarc_record,
|
|
'dns_provider_id': self.dns_provider_id,
|
|
'dns_zone_id': self.dns_zone_id,
|
|
'accounts_count': len(self.accounts) if self.accounts else 0,
|
|
'aliases_count': len(self.aliases) if self.aliases else 0,
|
|
'created_at': self.created_at.isoformat() if self.created_at else None,
|
|
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
|
}
|
|
|
|
def __repr__(self):
|
|
return f'<EmailDomain {self.name}>'
|
|
|
|
|
|
class EmailAccount(TimestampMixin, db.Model):
|
|
__tablename__ = 'email_accounts'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
email = db.Column(db.String(255), unique=True, nullable=False, index=True)
|
|
username = db.Column(db.String(255), nullable=False)
|
|
password_hash = db.Column(db.String(500), nullable=False)
|
|
domain_id = db.Column(db.Integer, db.ForeignKey('email_domains.id'), nullable=False, index=True)
|
|
quota_mb = db.Column(db.Integer, default=1024)
|
|
quota_used_mb = db.Column(db.Integer, default=0)
|
|
is_active = db.Column(db.Boolean, default=True)
|
|
|
|
forwarding_rules = db.relationship('EmailForwardingRule', backref='account', lazy=True, cascade='all, delete-orphan')
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'email': self.email,
|
|
'username': self.username,
|
|
'domain_id': self.domain_id,
|
|
'domain_name': self.domain.name if self.domain else None,
|
|
'quota_mb': self.quota_mb,
|
|
'quota_used_mb': self.quota_used_mb,
|
|
'is_active': self.is_active,
|
|
'forwarding_count': len(self.forwarding_rules) if self.forwarding_rules else 0,
|
|
'created_at': self.created_at.isoformat() if self.created_at else None,
|
|
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
|
}
|
|
|
|
def __repr__(self):
|
|
return f'<EmailAccount {self.email}>'
|
|
|
|
|
|
class EmailAlias(db.Model):
|
|
__tablename__ = 'email_aliases'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
source = db.Column(db.String(255), nullable=False, index=True)
|
|
destination = db.Column(db.String(255), nullable=False)
|
|
domain_id = db.Column(db.Integer, db.ForeignKey('email_domains.id'), nullable=False, index=True)
|
|
is_active = db.Column(db.Boolean, default=True)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'source': self.source,
|
|
'destination': self.destination,
|
|
'domain_id': self.domain_id,
|
|
'domain_name': self.domain.name if self.domain else None,
|
|
'is_active': self.is_active,
|
|
'created_at': self.created_at.isoformat() if self.created_at else None,
|
|
}
|
|
|
|
def __repr__(self):
|
|
return f'<EmailAlias {self.source} -> {self.destination}>'
|
|
|
|
|
|
class EmailForwardingRule(db.Model):
|
|
__tablename__ = 'email_forwarding_rules'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
account_id = db.Column(db.Integer, db.ForeignKey('email_accounts.id'), nullable=False, index=True)
|
|
destination = db.Column(db.String(255), nullable=False)
|
|
keep_copy = db.Column(db.Boolean, default=True)
|
|
is_active = db.Column(db.Boolean, default=True)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'account_id': self.account_id,
|
|
'account_email': self.account.email if self.account else None,
|
|
'destination': self.destination,
|
|
'keep_copy': self.keep_copy,
|
|
'is_active': self.is_active,
|
|
'created_at': self.created_at.isoformat() if self.created_at else None,
|
|
}
|
|
|
|
def __repr__(self):
|
|
return f'<EmailForwardingRule {self.account_id} -> {self.destination}>'
|
|
|
|
|
|
class DNSProviderConfig(db.Model):
|
|
__tablename__ = 'dns_provider_configs'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(100), nullable=False)
|
|
provider = db.Column(db.String(50), nullable=False) # 'cloudflare' | 'route53'
|
|
api_key = db.Column(db.String(500))
|
|
api_secret = db.Column(db.String(500))
|
|
api_email = db.Column(db.String(255))
|
|
is_default = db.Column(db.Boolean, default=False)
|
|
# Cloudflare Origin CA — the private key the panel generated for the CSR when
|
|
# issuing an Origin certificate for this provider's zones.
|
|
origin_ca_key = db.Column(db.Text, nullable=True)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
domains = db.relationship('EmailDomain', backref='dns_provider', lazy=True)
|
|
|
|
def to_dict(self, mask_secrets=True):
|
|
result = {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'provider': self.provider,
|
|
'api_email': self.api_email,
|
|
'is_default': self.is_default,
|
|
'domains_count': len(self.domains) if self.domains else 0,
|
|
'created_at': self.created_at.isoformat() if self.created_at else None,
|
|
}
|
|
if mask_secrets:
|
|
result['api_key'] = '****' + (self.api_key[-4:] if self.api_key and len(self.api_key) > 4 else '')
|
|
result['api_secret'] = '****' if self.api_secret else None
|
|
else:
|
|
result['api_key'] = self.api_key
|
|
result['api_secret'] = self.api_secret
|
|
return result
|
|
|
|
def __repr__(self):
|
|
return f'<DNSProviderConfig {self.name} ({self.provider})>'
|
|
|
|
|
|
class EmailRelayConfig(db.Model):
|
|
"""Outbound SMTP relay (smarthost) for the mail server — routes outgoing mail
|
|
through a third-party provider (Postmark, SES, Mailgun, …). Single-row config;
|
|
the password is Fernet-encrypted (see app.utils.crypto)."""
|
|
__tablename__ = 'email_relay_config'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
enabled = db.Column(db.Boolean, default=False)
|
|
host = db.Column(db.String(255))
|
|
port = db.Column(db.Integer, default=587)
|
|
username = db.Column(db.String(255))
|
|
password_encrypted = db.Column(db.Text)
|
|
use_tls = db.Column(db.Boolean, default=True)
|
|
provider_hint = db.Column(db.String(40)) # 'postmark' | 'ses' | 'mailgun' | 'sendgrid' | 'custom'
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'enabled': bool(self.enabled),
|
|
'host': self.host or '',
|
|
'port': self.port or 587,
|
|
'username': self.username or '',
|
|
'use_tls': self.use_tls if self.use_tls is not None else True,
|
|
'provider_hint': self.provider_hint,
|
|
'password_set': bool(self.password_encrypted),
|
|
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
|
}
|
|
|
|
def __repr__(self):
|
|
return f'<EmailRelayConfig {self.host or "(unset)"}>'
|