675 lines
15 KiB
Go
675 lines
15 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"apps/backend/internal/module/growth/domain"
|
|
)
|
|
|
|
type Memory struct {
|
|
mu sync.Mutex
|
|
outcomes map[string]*domain.OutcomeEvent
|
|
bySource map[string]string
|
|
checkups map[string]*domain.WeeklyCheckup
|
|
health map[string]*domain.AccountHealth
|
|
workspaces map[string]*domain.Workspace
|
|
wsState map[int64]*domain.MemberWorkspaceState
|
|
reviews map[string]*domain.DraftReview
|
|
rewards map[string]*domain.InviteReward
|
|
playbooks map[string]*domain.Playbook
|
|
utm map[string]*domain.UtmLink
|
|
wsMembers map[string]*domain.WorkspaceMember
|
|
bench map[string]*domain.BenchmarkSample
|
|
}
|
|
|
|
func NewMemory() *Memory {
|
|
return &Memory{
|
|
outcomes: map[string]*domain.OutcomeEvent{},
|
|
bySource: map[string]string{},
|
|
checkups: map[string]*domain.WeeklyCheckup{},
|
|
health: map[string]*domain.AccountHealth{},
|
|
workspaces: map[string]*domain.Workspace{},
|
|
wsState: map[int64]*domain.MemberWorkspaceState{},
|
|
reviews: map[string]*domain.DraftReview{},
|
|
rewards: map[string]*domain.InviteReward{},
|
|
playbooks: map[string]*domain.Playbook{},
|
|
utm: map[string]*domain.UtmLink{},
|
|
wsMembers: map[string]*domain.WorkspaceMember{},
|
|
bench: map[string]*domain.BenchmarkSample{},
|
|
}
|
|
}
|
|
|
|
func sourceKey(t, id string) string { return t + ":" + id }
|
|
|
|
func (m *Memory) UpsertOutcomeBySource(_ context.Context, e *domain.OutcomeEvent) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
sk := sourceKey(e.SourceType, e.SourceID)
|
|
if id, ok := m.bySource[sk]; ok {
|
|
// 發佈 hook 可能重送;已存在就保持原 sent_at 與所有觀測/成交訊號。
|
|
*e = *m.outcomes[id]
|
|
return nil
|
|
} else if e.ID == "" {
|
|
e.ID = domain.NewID()
|
|
}
|
|
cp := *e
|
|
m.outcomes[e.ID] = &cp
|
|
m.bySource[sk] = e.ID
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) GetOutcome(_ context.Context, id string) (*domain.OutcomeEvent, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
e, ok := m.outcomes[id]
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
cp := *e
|
|
return &cp, nil
|
|
}
|
|
|
|
func (m *Memory) ListOutcomes(_ context.Context, ownerUID int64, f domain.OutcomeListFilter) ([]*domain.OutcomeEvent, int64, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var all []*domain.OutcomeEvent
|
|
for _, e := range m.outcomes {
|
|
if e.OwnerUID != ownerUID {
|
|
continue
|
|
}
|
|
if f.Kind != "" && e.Kind != f.Kind {
|
|
continue
|
|
}
|
|
if f.Confidence != "" && e.Confidence != f.Confidence {
|
|
continue
|
|
}
|
|
if f.SourceType != "" && e.SourceType != f.SourceType {
|
|
continue
|
|
}
|
|
if f.From > 0 && e.SentAt < f.From {
|
|
continue
|
|
}
|
|
if f.To > 0 && e.SentAt > f.To {
|
|
continue
|
|
}
|
|
cp := *e
|
|
all = append(all, &cp)
|
|
}
|
|
sort.Slice(all, func(i, j int) bool { return all[i].SentAt > all[j].SentAt })
|
|
total := int64(len(all))
|
|
page, ps := f.Page, f.PageSize
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if ps < 1 {
|
|
ps = 20
|
|
}
|
|
start := (page - 1) * ps
|
|
if start >= len(all) {
|
|
return nil, total, nil
|
|
}
|
|
end := start + ps
|
|
if end > len(all) {
|
|
end = len(all)
|
|
}
|
|
return all[start:end], total, nil
|
|
}
|
|
|
|
func (m *Memory) SaveOutcome(_ context.Context, e *domain.OutcomeEvent) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
cp := *e
|
|
m.outcomes[e.ID] = &cp
|
|
m.bySource[sourceKey(e.SourceType, e.SourceID)] = e.ID
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) ListObservingDue(_ context.Context, now int64, limit int) ([]*domain.OutcomeEvent, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var out []*domain.OutcomeEvent
|
|
for _, e := range m.outcomes {
|
|
if e.Status != domain.StatusObserving {
|
|
continue
|
|
}
|
|
cp := *e
|
|
out = append(out, &cp)
|
|
if limit > 0 && len(out) >= limit {
|
|
break
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) SaveCheckup(_ context.Context, c *domain.WeeklyCheckup) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
cp := *c
|
|
m.checkups[c.ID] = &cp
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) GetCheckup(_ context.Context, id string) (*domain.WeeklyCheckup, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
c, ok := m.checkups[id]
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
cp := *c
|
|
return &cp, nil
|
|
}
|
|
|
|
func (m *Memory) LatestCheckup(_ context.Context, ownerUID int64) (*domain.WeeklyCheckup, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var best *domain.WeeklyCheckup
|
|
for _, c := range m.checkups {
|
|
if c.OwnerUID != ownerUID {
|
|
continue
|
|
}
|
|
if best == nil || c.CreatedAt > best.CreatedAt {
|
|
cp := *c
|
|
best = &cp
|
|
}
|
|
}
|
|
if best == nil {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
return best, nil
|
|
}
|
|
|
|
func (m *Memory) ListCheckups(_ context.Context, ownerUID int64, page, pageSize int) ([]*domain.WeeklyCheckup, int64, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var all []*domain.WeeklyCheckup
|
|
for _, c := range m.checkups {
|
|
if c.OwnerUID == ownerUID {
|
|
cp := *c
|
|
all = append(all, &cp)
|
|
}
|
|
}
|
|
sort.Slice(all, func(i, j int) bool { return all[i].CreatedAt > all[j].CreatedAt })
|
|
total := int64(len(all))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 {
|
|
pageSize = 20
|
|
}
|
|
start := (page - 1) * pageSize
|
|
if start >= len(all) {
|
|
return nil, total, nil
|
|
}
|
|
end := start + pageSize
|
|
if end > len(all) {
|
|
end = len(all)
|
|
}
|
|
return all[start:end], total, nil
|
|
}
|
|
|
|
func (m *Memory) CountCheckupGenerations(_ context.Context, ownerUID int64, sinceNano int64) (int64, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var n int64
|
|
for _, c := range m.checkups {
|
|
if c.OwnerUID == ownerUID && c.CreatedAt >= sinceNano {
|
|
n++
|
|
}
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func (m *Memory) SaveHealth(_ context.Context, h *domain.AccountHealth) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
cp := *h
|
|
m.health[h.ThreadsAccountID] = &cp
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) GetHealth(_ context.Context, accountID string) (*domain.AccountHealth, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
h, ok := m.health[accountID]
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
cp := *h
|
|
return &cp, nil
|
|
}
|
|
|
|
func (m *Memory) ListHealth(_ context.Context, ownerUID int64) ([]*domain.AccountHealth, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var out []*domain.AccountHealth
|
|
for _, h := range m.health {
|
|
if h.OwnerUID == ownerUID {
|
|
cp := *h
|
|
out = append(out, &cp)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) SaveWorkspace(_ context.Context, w *domain.Workspace) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
cp := *w
|
|
m.workspaces[w.ID] = &cp
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) GetWorkspace(_ context.Context, id string) (*domain.Workspace, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
w, ok := m.workspaces[id]
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
cp := *w
|
|
return &cp, nil
|
|
}
|
|
|
|
func (m *Memory) ListWorkspaces(_ context.Context, ownerUID int64, includeArchived bool) ([]*domain.Workspace, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var out []*domain.Workspace
|
|
for _, w := range m.workspaces {
|
|
if w.OwnerUID != ownerUID {
|
|
continue
|
|
}
|
|
if w.Archived && !includeArchived {
|
|
continue
|
|
}
|
|
cp := *w
|
|
out = append(out, &cp)
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].CreatedAt < out[j].CreatedAt })
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) CountActiveWorkspaces(_ context.Context, ownerUID int64) (int64, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var n int64
|
|
for _, w := range m.workspaces {
|
|
if w.OwnerUID == ownerUID && !w.Archived {
|
|
n++
|
|
}
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func (m *Memory) GetMemberWSState(_ context.Context, ownerUID int64) (*domain.MemberWorkspaceState, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
s, ok := m.wsState[ownerUID]
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
cp := *s
|
|
return &cp, nil
|
|
}
|
|
|
|
func (m *Memory) SaveMemberWSState(_ context.Context, s *domain.MemberWorkspaceState) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
cp := *s
|
|
m.wsState[s.OwnerUID] = &cp
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) SaveReview(_ context.Context, r *domain.DraftReview) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
cp := *r
|
|
m.reviews[r.ID] = &cp
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) GetReview(_ context.Context, id string) (*domain.DraftReview, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
r, ok := m.reviews[id]
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
cp := *r
|
|
return &cp, nil
|
|
}
|
|
|
|
func (m *Memory) FindReviewByRef(_ context.Context, ownerUID int64, refType, refID string) (*domain.DraftReview, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var best *domain.DraftReview
|
|
for _, r := range m.reviews {
|
|
if r.OwnerUID == ownerUID && r.RefType == refType && r.RefID == refID {
|
|
if best == nil || r.UpdatedAt > best.UpdatedAt {
|
|
cp := *r
|
|
best = &cp
|
|
}
|
|
}
|
|
}
|
|
if best == nil {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
return best, nil
|
|
}
|
|
|
|
func (m *Memory) ListReviews(_ context.Context, ownerUID int64, status string, page, pageSize int) ([]*domain.DraftReview, int64, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var all []*domain.DraftReview
|
|
for _, r := range m.reviews {
|
|
if r.OwnerUID != ownerUID {
|
|
continue
|
|
}
|
|
if status != "" && r.Status != status {
|
|
continue
|
|
}
|
|
cp := *r
|
|
all = append(all, &cp)
|
|
}
|
|
total := int64(len(all))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 {
|
|
pageSize = 20
|
|
}
|
|
start := (page - 1) * pageSize
|
|
if start >= len(all) {
|
|
return nil, total, nil
|
|
}
|
|
end := start + pageSize
|
|
if end > len(all) {
|
|
end = len(all)
|
|
}
|
|
return all[start:end], total, nil
|
|
}
|
|
|
|
func (m *Memory) SaveInviteReward(_ context.Context, r *domain.InviteReward) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
cp := *r
|
|
m.rewards[r.ID] = &cp
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) ListInviteRewards(_ context.Context, inviterUID int64, limit int) ([]*domain.InviteReward, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var all []*domain.InviteReward
|
|
for _, r := range m.rewards {
|
|
if r.InviterUID == inviterUID {
|
|
cp := *r
|
|
all = append(all, &cp)
|
|
}
|
|
}
|
|
sort.Slice(all, func(i, j int) bool { return all[i].CreatedAt > all[j].CreatedAt })
|
|
if limit > 0 && len(all) > limit {
|
|
all = all[:limit]
|
|
}
|
|
return all, nil
|
|
}
|
|
|
|
func (m *Memory) SumInviteRewardPointsMonth(_ context.Context, inviterUID int64, monthStartNano int64) (int, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
sum := 0
|
|
for _, r := range m.rewards {
|
|
if r.InviterUID == inviterUID && r.Status == domain.RewardCredited && r.CreatedAt >= monthStartNano {
|
|
sum += r.Points
|
|
}
|
|
}
|
|
return sum, nil
|
|
}
|
|
|
|
func (m *Memory) SumInviteRewardPointsTotal(_ context.Context, inviterUID int64) (int, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
sum := 0
|
|
for _, r := range m.rewards {
|
|
if r.InviterUID == inviterUID && r.Status == domain.RewardCredited {
|
|
sum += r.Points
|
|
}
|
|
}
|
|
return sum, nil
|
|
}
|
|
|
|
func (m *Memory) HasInviteRewardForInvitee(_ context.Context, inviteeUID int64) (bool, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
for _, r := range m.rewards {
|
|
if r.InviteeUID == inviteeUID && (r.Status == domain.RewardCredited || r.Status == domain.RewardCapped) {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// Ensure compile-time interface
|
|
var _ domain.Repository = (*Memory)(nil)
|
|
|
|
// MonthStartNano helper for tests
|
|
func MonthStartNano(t time.Time) int64 {
|
|
u := t.UTC()
|
|
return time.Date(u.Year(), u.Month(), 1, 0, 0, 0, 0, time.UTC).UnixNano()
|
|
}
|
|
|
|
// --- P2 ---
|
|
|
|
func (m *Memory) SavePlaybook(_ context.Context, p *domain.Playbook) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.playbooks == nil {
|
|
m.playbooks = map[string]*domain.Playbook{}
|
|
}
|
|
cp := *p
|
|
m.playbooks[p.ID] = &cp
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) GetPlaybook(_ context.Context, id string) (*domain.Playbook, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
p, ok := m.playbooks[id]
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
cp := *p
|
|
return &cp, nil
|
|
}
|
|
|
|
func (m *Memory) ListPlaybooks(_ context.Context, kind, niche string, ownerOnlyUID int64, page, pageSize int) ([]*domain.Playbook, int64, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var all []*domain.Playbook
|
|
for _, p := range m.playbooks {
|
|
if kind != "" && p.Kind != kind {
|
|
continue
|
|
}
|
|
if niche != "" && !stringsContainsFold(p.Niche, niche) {
|
|
continue
|
|
}
|
|
if ownerOnlyUID > 0 && p.OwnerUID != ownerOnlyUID {
|
|
continue
|
|
}
|
|
cp := *p
|
|
all = append(all, &cp)
|
|
}
|
|
sort.Slice(all, func(i, j int) bool { return all[i].CreatedAt > all[j].CreatedAt })
|
|
total := int64(len(all))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 {
|
|
pageSize = 20
|
|
}
|
|
start := (page - 1) * pageSize
|
|
if start >= len(all) {
|
|
return nil, total, nil
|
|
}
|
|
end := start + pageSize
|
|
if end > len(all) {
|
|
end = len(all)
|
|
}
|
|
return all[start:end], total, nil
|
|
}
|
|
|
|
func stringsContainsFold(s, sub string) bool {
|
|
return strings.Contains(strings.ToLower(s), strings.ToLower(sub))
|
|
}
|
|
|
|
func (m *Memory) DeletePlaybook(_ context.Context, id string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.playbooks[id]; !ok {
|
|
return domain.ErrNotFound
|
|
}
|
|
delete(m.playbooks, id)
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) IncPlaybookImport(_ context.Context, id string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
p, ok := m.playbooks[id]
|
|
if !ok {
|
|
return domain.ErrNotFound
|
|
}
|
|
p.ImportCount++
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) SaveUtmLink(_ context.Context, u *domain.UtmLink) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.utm == nil {
|
|
m.utm = map[string]*domain.UtmLink{}
|
|
}
|
|
cp := *u
|
|
m.utm[u.Code] = &cp
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) GetUtmByCode(_ context.Context, code string) (*domain.UtmLink, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
u, ok := m.utm[code]
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
cp := *u
|
|
return &cp, nil
|
|
}
|
|
|
|
func (m *Memory) ListUtmLinks(_ context.Context, ownerUID int64) ([]*domain.UtmLink, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var out []*domain.UtmLink
|
|
for _, u := range m.utm {
|
|
if u.OwnerUID == ownerUID {
|
|
cp := *u
|
|
out = append(out, &cp)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) IncUtmClick(_ context.Context, code string) (*domain.UtmLink, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
u, ok := m.utm[code]
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
u.Clicks++
|
|
cp := *u
|
|
return &cp, nil
|
|
}
|
|
|
|
func (m *Memory) SaveWorkspaceMember(_ context.Context, mem *domain.WorkspaceMember) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.wsMembers == nil {
|
|
m.wsMembers = map[string]*domain.WorkspaceMember{}
|
|
}
|
|
cp := *mem
|
|
m.wsMembers[mem.ID] = &cp
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) ListWorkspaceMembers(_ context.Context, workspaceID string) ([]*domain.WorkspaceMember, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var out []*domain.WorkspaceMember
|
|
for _, mem := range m.wsMembers {
|
|
if mem.WorkspaceID == workspaceID {
|
|
cp := *mem
|
|
out = append(out, &cp)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (m *Memory) RemoveWorkspaceMember(_ context.Context, workspaceID string, uid int64) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
for id, mem := range m.wsMembers {
|
|
if mem.WorkspaceID == workspaceID && mem.UID == uid {
|
|
delete(m.wsMembers, id)
|
|
return nil
|
|
}
|
|
}
|
|
return domain.ErrNotFound
|
|
}
|
|
|
|
func (m *Memory) IsWorkspaceMember(_ context.Context, workspaceID string, uid int64) (bool, string, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if w, ok := m.workspaces[workspaceID]; ok && w.OwnerUID == uid {
|
|
return true, domain.WSRoleOwner, nil
|
|
}
|
|
for _, mem := range m.wsMembers {
|
|
if mem.WorkspaceID == workspaceID && mem.UID == uid {
|
|
return true, mem.Role, nil
|
|
}
|
|
}
|
|
return false, "", nil
|
|
}
|
|
|
|
func (m *Memory) UpsertBenchmarkSample(_ context.Context, s *domain.BenchmarkSample) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.bench == nil {
|
|
m.bench = map[string]*domain.BenchmarkSample{}
|
|
}
|
|
key := s.Niche + ":" + s.OwnerHash
|
|
s.ID = key
|
|
cp := *s
|
|
m.bench[key] = &cp
|
|
return nil
|
|
}
|
|
|
|
func (m *Memory) ListBenchmarkSamples(_ context.Context, niche string, limit int) ([]*domain.BenchmarkSample, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
var out []*domain.BenchmarkSample
|
|
for _, s := range m.bench {
|
|
if niche == "" || s.Niche == niche {
|
|
cp := *s
|
|
out = append(out, &cp)
|
|
}
|
|
}
|
|
if limit > 0 && len(out) > limit {
|
|
out = out[:limit]
|
|
}
|
|
return out, nil
|
|
}
|