"""System settings model for key-value configuration storage.""" from datetime import datetime, timezone from sqlalchemy.exc import IntegrityError from shopdb.extensions import db def _utcnow(): """Naive UTC now for column defaults (matches the app's naive datetime cols).""" return datetime.now(timezone.utc).replace(tzinfo=None) class Setting(db.Model): """ Key-value store for system settings. Settings can be managed via the admin UI and are cached for performance. """ __tablename__ = 'settings' settingid = db.Column(db.Integer, primary_key=True, autoincrement=True) key = db.Column(db.String(100), unique=True, nullable=False, index=True) value = db.Column(db.Text, nullable=True) valuetype = db.Column(db.String(20), default='string') # string, boolean, integer, json category = db.Column(db.String(50), default='general') # For grouping in UI description = db.Column(db.Text, nullable=True) createddate = db.Column(db.DateTime, default=_utcnow) modifieddate = db.Column(db.DateTime, default=_utcnow, onupdate=_utcnow) def to_dict(self): return { 'settingid': self.settingid, 'key': self.key, 'value': self.get_typed_value(), 'valuetype': self.valuetype, 'category': self.category, 'description': self.description, 'createddate': self.createddate.isoformat() + 'Z' if self.createddate else None, 'modifieddate': self.modifieddate.isoformat() + 'Z' if self.modifieddate else None, } def get_typed_value(self): """Return value converted to its proper type.""" if self.value is None: return None if self.valuetype == 'boolean': return self.value.lower() in ('true', '1', 'yes') if self.valuetype == 'integer': try: return int(self.value) except (ValueError, TypeError): return 0 return self.value @classmethod def get(cls, key: str, default=None): """Get a setting value by key.""" setting = cls.query.filter_by(key=key).first() if setting: return setting.get_typed_value() return default @staticmethod def _stringify(value): """Convert a value to its stored string form.""" if isinstance(value, bool): return 'true' if value else 'false' return str(value) if value is not None else None @classmethod def set(cls, key: str, value, valuetype: str = 'string', category: str = 'general', description: str = None): """Set a setting value, creating if it doesn't exist. Handles the create race: two concurrent callers can both find no row and both try to INSERT the same unique key. The loser's commit raises IntegrityError; we roll back, re-fetch the row the winner created, and apply our value to it. """ setting = cls.query.filter_by(key=key).first() if setting: setting.value = cls._stringify(value) db.session.commit() return setting setting = cls(key=key, valuetype=valuetype, category=category, description=description, value=cls._stringify(value)) db.session.add(setting) try: db.session.commit() return setting except IntegrityError: db.session.rollback() # Another transaction inserted this key first; update that row. setting = cls.query.filter_by(key=key).first() if setting is None: raise setting.value = cls._stringify(value) db.session.commit() return setting