initial commit from original sev version
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"abuse_registration_poc/internal/models"
|
||||
"abuse_registration_poc/internal/utils"
|
||||
)
|
||||
|
||||
type MemoryStore struct {
|
||||
mu sync.RWMutex
|
||||
rows []models.Registration
|
||||
resetInterval time.Duration
|
||||
lastReset time.Time
|
||||
nextReset time.Time
|
||||
baseSize int
|
||||
}
|
||||
|
||||
func NewMemoryStore(baseSize int, resetInterval time.Duration) *MemoryStore {
|
||||
if baseSize < 1 {
|
||||
baseSize = 546
|
||||
}
|
||||
if resetInterval < time.Second {
|
||||
resetInterval = 10 * time.Minute
|
||||
}
|
||||
|
||||
s := &MemoryStore{baseSize: baseSize, resetInterval: resetInterval}
|
||||
s.Reset()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *MemoryStore) ResetInterval() time.Duration {
|
||||
return s.resetInterval
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Reset() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
now := time.Now().UTC()
|
||||
s.rows = generateBaseDataset(s.baseSize)
|
||||
s.lastReset = now
|
||||
s.nextReset = now.Add(s.resetInterval)
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Snapshot() ([]models.Registration, time.Time, time.Time) {
|
||||
s.resetIfNeeded()
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
rows := make([]models.Registration, len(s.rows))
|
||||
copy(rows, s.rows)
|
||||
return rows, s.lastReset, s.nextReset
|
||||
}
|
||||
|
||||
func (s *MemoryStore) List(filter models.RegistrationFilter) []models.Registration {
|
||||
rows, _, _ := s.Snapshot()
|
||||
return applyFilter(rows, filter)
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Get(id string) (models.Registration, bool) {
|
||||
s.resetIfNeeded()
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
for _, row := range s.rows {
|
||||
if row.ID == id {
|
||||
return row, true
|
||||
}
|
||||
}
|
||||
return models.Registration{}, false
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Create(input models.Registration) (models.Registration, error) {
|
||||
s.resetIfNeeded()
|
||||
if err := models.NormalizeRegistration(&input); err != nil {
|
||||
return models.Registration{}, err
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
input.ID = utils.NewUUID()
|
||||
s.rows = append(s.rows, input)
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Replace(id string, input models.Registration) (models.Registration, bool, error) {
|
||||
s.resetIfNeeded()
|
||||
if err := models.NormalizeRegistration(&input); err != nil {
|
||||
return models.Registration{}, false, err
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for i := range s.rows {
|
||||
if s.rows[i].ID == id {
|
||||
input.ID = id
|
||||
s.rows[i] = input
|
||||
return input, true, nil
|
||||
}
|
||||
}
|
||||
return models.Registration{}, false, nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Delete(id string) bool {
|
||||
s.resetIfNeeded()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for i := range s.rows {
|
||||
if s.rows[i].ID == id {
|
||||
s.rows = append(s.rows[:i], s.rows[i+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *MemoryStore) resetIfNeeded() {
|
||||
s.mu.RLock()
|
||||
due := time.Now().UTC().After(s.nextReset)
|
||||
s.mu.RUnlock()
|
||||
if due {
|
||||
s.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func applyFilter(rows []models.Registration, filter models.RegistrationFilter) []models.Registration {
|
||||
location := strings.ToLower(strings.TrimSpace(filter.Location))
|
||||
abuseType := strings.ToLower(strings.TrimSpace(filter.AbuseType))
|
||||
gender := strings.ToLower(strings.TrimSpace(filter.Gender))
|
||||
status := strings.ToLower(strings.TrimSpace(filter.Status))
|
||||
search := strings.ToLower(strings.TrimSpace(filter.Search))
|
||||
|
||||
out := make([]models.Registration, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
if location != "" && strings.ToLower(row.Location) != location {
|
||||
continue
|
||||
}
|
||||
if abuseType != "" && strings.ToLower(row.AbuseType) != abuseType {
|
||||
continue
|
||||
}
|
||||
if gender != "" && strings.ToLower(row.Gender) != gender {
|
||||
continue
|
||||
}
|
||||
if status != "" && strings.ToLower(row.Status) != status {
|
||||
continue
|
||||
}
|
||||
if filter.HasFrom || filter.HasTo {
|
||||
registeredAt, err := time.Parse(time.RFC3339, row.RegisteredAt)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if filter.HasFrom && registeredAt.Before(filter.From) {
|
||||
continue
|
||||
}
|
||||
if filter.HasTo && registeredAt.After(filter.To) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if search != "" && !strings.Contains(strings.ToLower(row.ID+" "+row.Location+" "+row.AbuseType+" "+row.Gender+" "+row.Status), search) {
|
||||
continue
|
||||
}
|
||||
out = append(out, row)
|
||||
}
|
||||
|
||||
sort.SliceStable(out, func(i, j int) bool { return out[i].RegisteredAt < out[j].RegisteredAt })
|
||||
|
||||
if filter.Offset > 0 {
|
||||
if filter.Offset >= len(out) {
|
||||
return []models.Registration{}
|
||||
}
|
||||
out = out[filter.Offset:]
|
||||
}
|
||||
if filter.Limit > 0 && filter.Limit < len(out) {
|
||||
out = out[:filter.Limit]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func generateBaseDataset(count int) []models.Registration {
|
||||
seed := rand.New(rand.NewSource(19881991))
|
||||
locations := models.FaroeLocations
|
||||
rows := make([]models.Registration, 0, count)
|
||||
for i := 0; i < count; i++ {
|
||||
location := locations[i%len(locations)]
|
||||
if i >= len(locations) {
|
||||
location = locations[seed.Intn(len(locations))]
|
||||
}
|
||||
rows = append(rows, models.Registration{
|
||||
ID: utils.NewUUID(),
|
||||
RegisteredAt: randomRegistrationTime(seed).Format(time.RFC3339),
|
||||
Gender: models.Genders[seed.Intn(len(models.Genders))],
|
||||
Location: location,
|
||||
AbuseType: models.AbuseCategories[seed.Intn(len(models.AbuseCategories))],
|
||||
Status: models.Statuses[seed.Intn(len(models.Statuses))],
|
||||
})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func randomRegistrationTime(seed *rand.Rand) time.Time {
|
||||
start := time.Date(1988, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
end := time.Date(1991, 12, 31, 23, 59, 59, 0, time.UTC)
|
||||
span := end.Unix() - start.Unix()
|
||||
return time.Unix(start.Unix()+seed.Int63n(span), 0).UTC()
|
||||
}
|
||||
Reference in New Issue
Block a user