mirror of
https://github.com/ThisTine/Snip.git
synced 2026-08-18 23:18:47 +07:00
feat: first commit
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user