package themis import ( "context" "database/sql" "errors" "fmt" "time" "github.com/rs/zerolog/log" ) type EventType int const ( EventFlush EventType = iota EventClaim EventUnclaim EventAbsence ) func (et EventType) String() string { switch et { case EventFlush: return "FLUSH" case EventClaim: return "CLAIM" case EventUnclaim: return "UNCLAIM" case EventAbsence: return "ABSENT" default: return "" } } func EventTypeFromString(ev string) (EventType, error) { switch ev { case "FLUSH": return EventFlush, nil case "CLAIM": return EventClaim, nil case "UNCLAIM": return EventUnclaim, nil case "ABSENT": return EventAbsence, nil default: return EventType(9999), errors.New("no such event type") } } type AuditableEvent struct { userId string eventType EventType Timestamp time.Time } // Audit writes to the audit table, returns nothing because it is meant to be // used in a defered statement on functions that write to the database. func (s *Store) Audit(ev *AuditableEvent) { ctx := context.Background() tx, err := s.db.Begin() if err != nil { log.Error().Err(err).Msg("failed to start transaction") } defer tx.Commit() //nolint:errcheck stmt, err := s.db.PrepareContext(ctx, "INSERT INTO audit_log (userid, event_type, ts) VALUES (?, ?, ?)") if err != nil { log.Error().Err(err).Msg("failed to prepare audit log insert") } if _, err := stmt.ExecContext(ctx, ev.userId, ev.eventType.String(), time.Now()); err != nil { log.Error().Err(err).Msg("failed to insert audit log") } } func (s *Store) LastOf(ctx context.Context, t EventType) (AuditableEvent, error) { stmt, err := s.db.PrepareContext(ctx, `SELECT userid, event_type, ts FROM audit_log WHERE event_type = ? ORDER BY ts DESC LIMIT 1`) if err != nil { return AuditableEvent{}, fmt.Errorf("failed to get last event of type %s: %w", t.String(), err) } row := stmt.QueryRowContext(ctx, t.String()) ev := AuditableEvent{} var rawEventType string err = row.Scan(&ev.userId, &rawEventType, &ev.Timestamp) if err == sql.ErrNoRows { return AuditableEvent{}, errors.New("") } if err != nil { return AuditableEvent{}, fmt.Errorf("failed to scan row: %w", err) } ev.eventType, err = EventTypeFromString(rawEventType) if err != nil { return AuditableEvent{}, fmt.Errorf("failed to parse event type %s: %w", rawEventType, err) } return ev, nil }