from datetime import datetime, timezone from sqlalchemy.dialects.mysql import INTEGER from app import db class AuditLog(db.Model): """ Server-side audit trail for create, edit, and delete actions. Captures who did what, when, and on which resource. Sensitive field values are never logged — only resource IDs and action types. """ __tablename__ = 'audit_logs' id = db.Column(INTEGER(unsigned=True), autoincrement=True, primary_key=True) user_id = db.Column(INTEGER(unsigned=True), nullable=False, index=True) action = db.Column(db.String(64), nullable=False) # e.g. "vault_item.create" resource_type = db.Column(db.String(64), nullable=False) # e.g. "vault_item" resource_id = db.Column(INTEGER(unsigned=True), nullable=True) # FK to the affected row detail = db.Column(db.String(512), nullable=True) # human-readable summary (no secrets) ip_address = db.Column(db.String(45), nullable=True) # IPv4 or IPv6 created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc).replace(tzinfo=None), nullable=False, index=True) @classmethod def log(cls, user_id: int, action: str, resource_type: str, resource_id: int = None, detail: str = None, ip_address: str = None): """ Convenience factory that creates and flushes an audit entry. Caller is responsible for calling db.session.commit() (or the surrounding request handler will do so together with the business-logic commit). """ entry = cls( user_id=user_id, action=action, resource_type=resource_type, resource_id=resource_id, detail=detail, ip_address=ip_address, ) db.session.add(entry) return entry def to_dict(self): return { 'id': self.id, 'user_id': self.user_id, 'action': self.action, 'resource_type': self.resource_type, 'resource_id': self.resource_id, 'detail': self.detail, 'ip_address': self.ip_address, 'created_at': self.created_at.isoformat() if self.created_at else None, } def __repr__(self): return f''