132 lines
5.7 KiB
Python
132 lines
5.7 KiB
Python
from app import db
|
|
from datetime import datetime
|
|
from app.services.crypto_service import CryptoService
|
|
|
|
class SystemSetting(db.Model):
|
|
"""
|
|
Parámetros de configuración del sistema persistentes en base de datos.
|
|
Permite almacenar enlaces de integración (e.g. Google Sheets, Moodle),
|
|
flags globales, credenciales SMTP encriptadas y secretos OAuth.
|
|
"""
|
|
__tablename__ = 'system_settings'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
key = db.Column(db.String(100), unique=True, nullable=False, index=True)
|
|
value = db.Column(db.Text, nullable=True)
|
|
description = db.Column(db.String(255), nullable=True)
|
|
category = db.Column(db.String(50), default='system', nullable=True)
|
|
is_encrypted = db.Column(db.Boolean, default=False, nullable=True)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
@classmethod
|
|
def get_value(cls, key, default=None):
|
|
"""Retorna el valor tal como está almacenado en BD."""
|
|
try:
|
|
record = cls.query.filter_by(key=key).first()
|
|
if record and record.value is not None and record.value.strip() != '':
|
|
return record.value
|
|
except Exception:
|
|
pass
|
|
return default
|
|
|
|
def get_decrypted_value(self_or_cls, key_or_default=None, default=None):
|
|
"""Retorna el valor desencriptado. Compatible como método de clase o de instancia."""
|
|
if isinstance(self_or_cls, type):
|
|
# Llamado como clase: SystemSetting.get_decrypted_value('key', default)
|
|
key = key_or_default
|
|
try:
|
|
record = self_or_cls.query.filter_by(key=key).first()
|
|
if record:
|
|
return record.get_decrypted_value(default)
|
|
except Exception:
|
|
pass
|
|
return default
|
|
else:
|
|
# Llamado como instancia: setting.get_decrypted_value(default)
|
|
actual_default = key_or_default
|
|
try:
|
|
if self_or_cls.value is not None and self_or_cls.value.strip() != '':
|
|
if self_or_cls.is_encrypted:
|
|
return CryptoService.decrypt(self_or_cls.value)
|
|
return self_or_cls.value
|
|
except Exception:
|
|
pass
|
|
return actual_default
|
|
|
|
def get_masked_value(self_or_cls, key_or_default=None, default=''):
|
|
"""Retorna una versión enmascarada si es un secreto o contraseña. Compatible como clase o instancia."""
|
|
if isinstance(self_or_cls, type):
|
|
# Llamado como clase: SystemSetting.get_masked_value('key', default)
|
|
key = key_or_default
|
|
try:
|
|
record = self_or_cls.query.filter_by(key=key).first()
|
|
if record:
|
|
return record.get_masked_value(default)
|
|
except Exception:
|
|
pass
|
|
return default
|
|
else:
|
|
# Llamado como instancia: setting.get_masked_value(default)
|
|
actual_default = key_or_default or ''
|
|
try:
|
|
if self_or_cls.value and self_or_cls.value.strip() != '':
|
|
if self_or_cls.is_encrypted or 'secret' in self_or_cls.key.lower() or 'password' in self_or_cls.key.lower() or 'token' in self_or_cls.key.lower():
|
|
return '••••••••••••'
|
|
return self_or_cls.value
|
|
except Exception:
|
|
pass
|
|
return actual_default
|
|
|
|
@classmethod
|
|
def set_value(cls, key, value, description=None, category='system', is_encrypted=False):
|
|
"""Guarda o actualiza un parámetro de configuración."""
|
|
record = cls.query.filter_by(key=key).first()
|
|
final_value = value
|
|
if is_encrypted and value:
|
|
# Si el valor ya viene cifrado o es un placeholder de no-cambio '••••••••••••', no recifrar
|
|
if value != '••••••••••••':
|
|
final_value = CryptoService.encrypt(value)
|
|
|
|
if not record:
|
|
record = cls(
|
|
key=key,
|
|
value=final_value,
|
|
description=description,
|
|
category=category,
|
|
is_encrypted=is_encrypted
|
|
)
|
|
db.session.add(record)
|
|
else:
|
|
# Si el valor ingresado es el placeholder, no sobrescribir la contraseña existente
|
|
if not (is_encrypted and value == '••••••••••••'):
|
|
record.value = final_value
|
|
if description:
|
|
record.description = description
|
|
if category:
|
|
record.category = category
|
|
record.is_encrypted = is_encrypted
|
|
record.updated_at = datetime.utcnow()
|
|
db.session.commit()
|
|
return record
|
|
|
|
@classmethod
|
|
def set_encrypted_value(cls, key, value, description=None, category='system'):
|
|
"""Helper para guardar directamente valores sensibles cifrados."""
|
|
return cls.set_value(key, value, description=description, category=category, is_encrypted=True)
|
|
|
|
def to_dict(self, mask_secrets=True):
|
|
val = self.value
|
|
if mask_secrets and (self.is_encrypted or 'password' in self.key.lower() or 'secret' in self.key.lower() or 'token' in self.key.lower()):
|
|
val = '••••••••••••' if val else ''
|
|
return {
|
|
'key': self.key,
|
|
'value': val,
|
|
'description': self.description,
|
|
'category': self.category or 'system',
|
|
'is_encrypted': bool(self.is_encrypted),
|
|
'updated_at': self.updated_at.isoformat() if self.updated_at else None
|
|
}
|
|
|
|
def __repr__(self):
|
|
return f'<SystemSetting {self.key}>'
|