parent
638083a755
commit
b040694c2a
@ -0,0 +1,103 @@
|
||||
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
|
||||
}
|
@ -0,0 +1 @@
|
||||
DROP TABLE audit_log;
|
@ -0,0 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_type TEXT,
|
||||
userid TEXT,
|
||||
ts TIMESTAMP
|
||||
);
|
Loading…
Reference in new issue