mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-09 21:43:05 +09:00
Introduce team invitation support and a revamped email subsystem plus admin features. Adds Invitation model and API endpoints (create, list, revoke, resend, validate) and integrates invitation validation into user registration. Replaces old email account model with richer email models (EmailDomain, EmailAccount, EmailAlias, EmailForwardingRule, DNSProviderConfig) and updates models __init__. Refactors email API to manage domains, accounts, aliases, forwarding, DNS providers, spamassassin, Roundcube/Postfix services and queue/log endpoints. Adds several backend services (invitation, DNS provider, DKIM, Dovecot, Postfix, Roundcube, SpamAssassin, permission service) and a DB migration for permissions/invitations. Enhances admin API with permission management and activity dashboard endpoints, extends audit log actions, and adds a permission_required RBAC decorator. Includes frontend additions for settings/invitations UI and miscellaneous config/service updates and tests.
172 lines
6.7 KiB
Python
172 lines
6.7 KiB
Python
from datetime import datetime
|
|
from app import db
|
|
|
|
|
|
class EmailDomain(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)
|
|
dns_zone_id = db.Column(db.String(255))
|
|
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
# 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(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)
|
|
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)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
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)
|
|
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)
|
|
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)
|
|
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})>'
|