diff --git a/internal/actors/counter/actor.go b/internal/actors/counter/actor.go index 5c4e832..dca7914 100644 --- a/internal/actors/counter/actor.go +++ b/internal/actors/counter/actor.go @@ -7,6 +7,7 @@ import ( "net/http" "strconv" "strings" + "time" "github.com/gorilla/mux" "github.com/pkg/errors" @@ -193,7 +194,7 @@ func Register(args plugins.RegistrationArguments) (err error) { mod = val[0] } - if err := updateCounter(db, name, mod, false); err != nil { + if err := updateCounter(db, name, mod, false, time.Now()); err != nil { return 0, errors.Wrap(err, "updating counter") } @@ -230,7 +231,7 @@ func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, even } return false, errors.Wrap( - updateCounter(db, counterName, counterValue, true), + updateCounter(db, counterName, counterValue, true, time.Now()), "set counter", ) } @@ -249,7 +250,7 @@ func (actorCounter) Execute(_ *irc.Client, m *irc.Message, r *plugins.Rule, even } return false, errors.Wrap( - updateCounter(db, counterName, counterStep, false), + updateCounter(db, counterName, counterStep, false, time.Now()), "update counter", ) } @@ -298,7 +299,7 @@ func routeActorCounterSetValue(w http.ResponseWriter, r *http.Request) { return } - if err = updateCounter(db, mux.Vars(r)["name"], value, absolute); err != nil { + if err = updateCounter(db, mux.Vars(r)["name"], value, absolute, time.Now()); err != nil { http.Error(w, errors.Wrap(err, "updating value").Error(), http.StatusInternalServerError) return } diff --git a/internal/actors/counter/database.go b/internal/actors/counter/database.go index b6e71c5..9a37494 100644 --- a/internal/actors/counter/database.go +++ b/internal/actors/counter/database.go @@ -1,6 +1,8 @@ package counter import ( + "time" + "github.com/pkg/errors" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -11,8 +13,10 @@ import ( type ( counter struct { - Name string `gorm:"primaryKey"` - Value int64 + Name string `gorm:"primaryKey"` + Value int64 + FirstSeen time.Time + LastModified time.Time } ) @@ -32,7 +36,7 @@ func getCounterValue(db database.Connector, counterName string) (int64, error) { } //revive:disable-next-line:flag-parameter -func updateCounter(db database.Connector, counterName string, value int64, absolute bool) error { +func updateCounter(db database.Connector, counterName string, value int64, absolute bool, atTime time.Time) error { if !absolute { cv, err := getCounterValue(db, counterName) if err != nil { @@ -46,8 +50,8 @@ func updateCounter(db database.Connector, counterName string, value int64, absol helpers.RetryTransaction(db.DB(), func(tx *gorm.DB) error { return tx.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "name"}}, - DoUpdates: clause.AssignmentColumns([]string{"value"}), - }).Create(counter{Name: counterName, Value: value}).Error + DoUpdates: clause.AssignmentColumns([]string{"last_modified", "value"}), + }).Create(counter{Name: counterName, Value: value, FirstSeen: atTime.UTC(), LastModified: atTime.UTC()}).Error }), "storing counter value", ) diff --git a/internal/actors/counter/database_test.go b/internal/actors/counter/database_test.go index 496d381..f4c75e5 100644 --- a/internal/actors/counter/database_test.go +++ b/internal/actors/counter/database_test.go @@ -3,6 +3,7 @@ package counter import ( "fmt" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,12 +21,19 @@ func TestCounterStoreLoop(t *testing.T) { assert.NoError(t, err, "reading non-existent counter") assert.Equal(t, int64(0), v, "expecting 0 counter value on non-existent counter") - err = updateCounter(dbc, counterName, 5, true) + err = updateCounter(dbc, counterName, 5, true, time.Now()) assert.NoError(t, err, "inserting counter") - err = updateCounter(dbc, counterName, 1, false) + var rawCounter counter + assert.NoError(t, dbc.DB().First(&rawCounter, "name = ?", counterName).Error) + assert.Equal(t, rawCounter.FirstSeen, rawCounter.LastModified) + + err = updateCounter(dbc, counterName, 1, false, time.Now()) assert.NoError(t, err, "updating counter") + assert.NoError(t, dbc.DB().First(&rawCounter, "name = ?", counterName).Error) + assert.NotEqual(t, rawCounter.FirstSeen, rawCounter.LastModified) + v, err = getCounterValue(dbc, counterName) assert.NoError(t, err, "reading existent counter") assert.Equal(t, int64(6), v, "expecting counter value on existing counter") @@ -35,11 +43,13 @@ func TestCounterTopListAndRank(t *testing.T) { dbc := database.GetTestDatabase(t) require.NoError(t, dbc.DB().AutoMigrate(&counter{})) + testTime := time.Now().UTC() + counterTemplate := `#example:test:%v` for i := 0; i < 6; i++ { require.NoError( t, - updateCounter(dbc, fmt.Sprintf(counterTemplate, i), int64(i), true), + updateCounter(dbc, fmt.Sprintf(counterTemplate, i), int64(i), true, testTime), "inserting counter %d", i, ) } @@ -49,9 +59,9 @@ func TestCounterTopListAndRank(t *testing.T) { assert.Len(t, cc, 3) assert.Equal(t, []counter{ - {Name: "#example:test:5", Value: 5}, - {Name: "#example:test:4", Value: 4}, - {Name: "#example:test:3", Value: 3}, + {Name: "#example:test:5", Value: 5, FirstSeen: testTime, LastModified: testTime}, + {Name: "#example:test:4", Value: 4, FirstSeen: testTime, LastModified: testTime}, + {Name: "#example:test:3", Value: 3, FirstSeen: testTime, LastModified: testTime}, }, cc) rank, count, err := getCounterRank(dbc,