package db import ( "context" "database/sql" "time" ) type AuditLogEntry struct { ID int64 AdminID int64 Action string EntityType string EntityID sql.NullInt64 OldValue sql.NullString NewValue sql.NullString IPAddress string CreatedAt time.Time } // Audit action constants const ( AuditCreatePanel = "create_panel" AuditUpdatePanel = "update_panel" AuditDeletePanel = "delete_panel" AuditCreateType = "create_type" AuditUpdateType = "update_type" AuditDeleteType = "delete_type" AuditReorderTypes = "reorder_types" AuditUpdateConvoc = "update_convocation" AuditSendPanelDiscord = "send_panel_discord" AuditDeletePanelMsg = "delete_panel_discord" AuditRevokeSession = "revoke_session" AuditRevokeAllSess = "revoke_all_sessions" AuditAdminLogin = "admin_login" AuditAdminLoginFail = "admin_login_failed" AuditAdminLocked = "admin_locked" ) type AuditFilter struct { AdminID int64 Action string EntityType string From, To time.Time } type AuditLogRepo struct{ db *sql.DB } func NewAuditLogRepo(db *sql.DB) *AuditLogRepo { return &AuditLogRepo{db: db} } func (r *AuditLogRepo) Insert(ctx context.Context, e *AuditLogEntry) error { e.CreatedAt = time.Now().UTC() _, err := r.db.ExecContext(ctx, `INSERT INTO audit_log(admin_id,action,entity_type,entity_id,old_value,new_value,ip_address,created_at) VALUES(?,?,?,?,?,?,?,?)`, e.AdminID, e.Action, e.EntityType, e.EntityID, e.OldValue, e.NewValue, e.IPAddress, e.CreatedAt) return err } func (r *AuditLogRepo) List(ctx context.Context, f AuditFilter, page, pageSize int) ([]*AuditLogEntry, int, error) { where := "WHERE 1=1" args := []any{} if f.AdminID != 0 { where += " AND admin_id=?" args = append(args, f.AdminID) } if f.Action != "" { where += " AND action=?" args = append(args, f.Action) } if f.EntityType != "" { where += " AND entity_type=?" args = append(args, f.EntityType) } if !f.From.IsZero() { where += " AND created_at>=?" args = append(args, f.From) } if !f.To.IsZero() { where += " AND created_at<=?" args = append(args, f.To) } var total int countArgs := make([]any, len(args)) copy(countArgs, args) if err := r.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM audit_log `+where, countArgs...).Scan(&total); err != nil { return nil, 0, err } if page < 1 { page = 1 } offset := (page - 1) * pageSize args = append(args, pageSize, offset) rows, err := r.db.QueryContext(ctx, `SELECT id,admin_id,action,entity_type,entity_id,old_value,new_value,ip_address,created_at FROM audit_log `+where+` ORDER BY created_at DESC LIMIT ? OFFSET ?`, args...) if err != nil { return nil, 0, err } defer rows.Close() var out []*AuditLogEntry for rows.Next() { var e AuditLogEntry if err := rows.Scan(&e.ID, &e.AdminID, &e.Action, &e.EntityType, &e.EntityID, &e.OldValue, &e.NewValue, &e.IPAddress, &e.CreatedAt); err != nil { return nil, 0, err } out = append(out, &e) } return out, total, rows.Err() }