From f594d5c9746dc9407e4e6bf27a5ef1d3c036b313 Mon Sep 17 00:00:00 2001 From: Wan Muhafidz Faldi Date: Fri, 15 Nov 2024 11:06:52 +0700 Subject: [PATCH] Add function IsLockedTTLWithLimit checks if the key has been incremented more than the specified limit --- candiutils/locker.go | 30 +++++++++++++++++++++++++++ codebase/interfaces/locker.go | 1 + mocks/candiutils/LockerOption.go | 32 +++++++++++++++++++++++++++++ mocks/codebase/interfaces/Locker.go | 20 +++++++++++++++++- 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 mocks/candiutils/LockerOption.go diff --git a/candiutils/locker.go b/candiutils/locker.go index 63a6138..8ca05cd 100644 --- a/candiutils/locker.go +++ b/candiutils/locker.go @@ -104,6 +104,33 @@ func (r *RedisLocker) IsLockedTTL(key string, TTL time.Duration) bool { return incr > 1 } +// IsLockedTTLWithLimit checks if the key has been incremented more than the specified limit +// within the given TTL. If the key is being created for the first time, it sets the TTL. +// Example usage: check if a key has been incremented more than 10 times within 1 minute. +func (r *RedisLocker) IsLockedTTLWithLimit(key string, limit int, TTL time.Duration) bool { + conn := r.pool.Get() + defer conn.Close() + + lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key) + incr, err := redis.Int64(conn.Do("INCR", lockKey)) + if err != nil { + return false + } + + var expireTime time.Duration + if TTL > 0 { + expireTime = TTL + } else { + expireTime = r.lockeroptions.TTL + } + + if expireTime > 0 && incr == 1 { + conn.Do("EXPIRE", lockKey, int(expireTime.Seconds())) + } + + return incr > int64(limit) +} + func (r *RedisLocker) HasBeenLocked(key string) bool { conn := r.pool.Get() defer conn.Close() @@ -234,3 +261,6 @@ func (NoopLocker) GetPrefixLocker() string { return "" } // GetTTLLocker method func (NoopLocker) GetTTLLocker() time.Duration { return 0 } + +// IsLockedTTLWithLimit method +func (NoopLocker) IsLockedTTLWithLimit(string, int, time.Duration) bool { return false } diff --git a/codebase/interfaces/locker.go b/codebase/interfaces/locker.go index afc484a..3ec642f 100644 --- a/codebase/interfaces/locker.go +++ b/codebase/interfaces/locker.go @@ -13,6 +13,7 @@ type ( Lock(key string, timeout time.Duration) (unlockFunc func(), err error) GetPrefixLocker() string GetTTLLocker() time.Duration + IsLockedTTLWithLimit(key string, limit int, TTL time.Duration) bool Closer } ) diff --git a/mocks/candiutils/LockerOption.go b/mocks/candiutils/LockerOption.go new file mode 100644 index 0000000..715c2e1 --- /dev/null +++ b/mocks/candiutils/LockerOption.go @@ -0,0 +1,32 @@ +// Code generated by mockery v2.46.3. DO NOT EDIT. + +package mocks + +import ( + candiutils "github.com/golangid/candi/candiutils" + mock "github.com/stretchr/testify/mock" +) + +// LockerOption is an autogenerated mock type for the LockerOption type +type LockerOption struct { + mock.Mock +} + +// Execute provides a mock function with given fields: _a0 +func (_m *LockerOption) Execute(_a0 *candiutils.LockerOptions) { + _m.Called(_a0) +} + +// NewLockerOption creates a new instance of LockerOption. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewLockerOption(t interface { + mock.TestingT + Cleanup(func()) +}) *LockerOption { + mock := &LockerOption{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/codebase/interfaces/Locker.go b/mocks/codebase/interfaces/Locker.go index 531c21a..80c053b 100644 --- a/mocks/codebase/interfaces/Locker.go +++ b/mocks/codebase/interfaces/Locker.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.1. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks @@ -123,6 +123,24 @@ func (_m *Locker) IsLockedTTL(key string, ttl time.Duration) bool { return r0 } +// IsLockedTTLWithLimit provides a mock function with given fields: key, limit, TTL +func (_m *Locker) IsLockedTTLWithLimit(key string, limit int, TTL time.Duration) bool { + ret := _m.Called(key, limit, TTL) + + if len(ret) == 0 { + panic("no return value specified for IsLockedTTLWithLimit") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(string, int, time.Duration) bool); ok { + r0 = rf(key, limit, TTL) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // Lock provides a mock function with given fields: key, timeout func (_m *Locker) Lock(key string, timeout time.Duration) (func(), error) { ret := _m.Called(key, timeout)