mirror of
https://github.com/ThisTine/Snip.git
synced 2026-08-18 23:18:47 +07:00
172 lines
3.6 KiB
Go
172 lines
3.6 KiB
Go
// Package memory provides in-memory adapters used by tests and for running the
|
|
// stack without Postgres/Redis (STORE=memory).
|
|
package memory
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/snip/backend/internal/domain"
|
|
)
|
|
|
|
type LinkRepo struct {
|
|
mu sync.RWMutex
|
|
byID map[string]*domain.Link
|
|
seq int64
|
|
clock func() time.Time
|
|
}
|
|
|
|
func NewLinkRepo() *LinkRepo {
|
|
return &LinkRepo{byID: make(map[string]*domain.Link), clock: time.Now}
|
|
}
|
|
|
|
func clone(l *domain.Link) *domain.Link {
|
|
cp := *l
|
|
cp.Last7Days = append([]domain.DayCount(nil), l.Last7Days...)
|
|
return &cp
|
|
}
|
|
|
|
func (r *LinkRepo) Create(_ context.Context, l *domain.Link) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
for _, e := range r.byID {
|
|
if e.Code == l.Code {
|
|
return domain.ErrCodeTaken
|
|
}
|
|
}
|
|
r.byID[l.ID] = clone(l)
|
|
return nil
|
|
}
|
|
|
|
func (r *LinkRepo) GetByID(_ context.Context, id string) (*domain.Link, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
l, ok := r.byID[id]
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
return clone(l), nil
|
|
}
|
|
|
|
func (r *LinkRepo) GetByCode(_ context.Context, code string) (*domain.Link, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
for _, l := range r.byID {
|
|
if l.Code == code {
|
|
return clone(l), nil
|
|
}
|
|
}
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
|
|
func (r *LinkRepo) ListByOwner(_ context.Context, ownerID, query string, limit, offset int) ([]domain.Link, int, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
q := strings.ToLower(query)
|
|
var matched []domain.Link
|
|
for _, l := range r.byID {
|
|
if l.OwnerID != ownerID {
|
|
continue
|
|
}
|
|
if q != "" && !strings.Contains(strings.ToLower(l.Code), q) && !strings.Contains(strings.ToLower(l.LongURL), q) {
|
|
continue
|
|
}
|
|
matched = append(matched, *clone(l))
|
|
}
|
|
sort.Slice(matched, func(i, j int) bool { return matched[i].CreatedAt.After(matched[j].CreatedAt) })
|
|
|
|
total := len(matched)
|
|
if offset > total {
|
|
offset = total
|
|
}
|
|
end := offset + limit
|
|
if limit <= 0 || end > total {
|
|
end = total
|
|
}
|
|
return matched[offset:end], total, nil
|
|
}
|
|
|
|
func (r *LinkRepo) StatsByOwner(_ context.Context, ownerID string) (domain.OwnerStats, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
var s domain.OwnerStats
|
|
for _, l := range r.byID {
|
|
if l.OwnerID != ownerID {
|
|
continue
|
|
}
|
|
s.TotalLinks++
|
|
s.TotalClicks += l.TotalClicks
|
|
for _, d := range l.Last7Days {
|
|
s.WeekClicks += d.Count
|
|
}
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (r *LinkRepo) Update(_ context.Context, l *domain.Link) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if _, ok := r.byID[l.ID]; !ok {
|
|
return domain.ErrNotFound
|
|
}
|
|
r.byID[l.ID] = clone(l)
|
|
return nil
|
|
}
|
|
|
|
func (r *LinkRepo) Delete(_ context.Context, id, ownerID string) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
l, ok := r.byID[id]
|
|
if !ok || l.OwnerID != ownerID {
|
|
return domain.ErrNotFound
|
|
}
|
|
delete(r.byID, id)
|
|
return nil
|
|
}
|
|
|
|
func (r *LinkRepo) ExistsCode(_ context.Context, code string) (bool, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
for _, l := range r.byID {
|
|
if l.Code == code {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (r *LinkRepo) NextSequence(_ context.Context) (int64, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.seq++
|
|
return r.seq, nil
|
|
}
|
|
|
|
func (r *LinkRepo) RecordClicks(_ context.Context, code string, day time.Time, n int64) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
key := day.Format("2006-01-02")
|
|
for _, l := range r.byID {
|
|
if l.Code != code {
|
|
continue
|
|
}
|
|
l.TotalClicks += n
|
|
found := false
|
|
for i := range l.Last7Days {
|
|
if l.Last7Days[i].Date == key {
|
|
l.Last7Days[i].Count += n
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
l.Last7Days = append(l.Last7Days, domain.DayCount{Date: key, Count: n})
|
|
}
|
|
return nil
|
|
}
|
|
return domain.ErrNotFound
|
|
}
|