GetAnalyticsPostsInRange now returns posts oldest-first to match the real store implementation.
214 lines
6.4 KiB
Go
214 lines
6.4 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/nacorid/naco-api/internal/store"
|
|
)
|
|
|
|
// fakeStore is an in-memory implementation of the Store interface used by handler
|
|
// tests so db-backed tool paths (style profiles, analytics, similar users) can be
|
|
// exercised without a live Postgres+pgvector.
|
|
type fakeStore struct {
|
|
analyticsCache map[cacheKey]cacheEntry
|
|
analyticsPosts map[string]store.AnalyticsPost
|
|
coverageRanges []store.AnalyticsCoverageRange
|
|
styleProfiles map[string]store.UserStyleProfile
|
|
stylePosts map[string]store.UserStylePost
|
|
similarRows []store.FindSimilarUsersRow
|
|
findSimilarErr error
|
|
styleProfileErr error
|
|
stylePostsErr error
|
|
cacheErr bool
|
|
postsRangeErr bool
|
|
coverageErr bool
|
|
}
|
|
|
|
type cacheKey struct {
|
|
user, platform, tool, hash string
|
|
}
|
|
|
|
type cacheEntry struct {
|
|
value json.RawMessage
|
|
exp time.Time
|
|
}
|
|
|
|
func newFakeStore() *fakeStore {
|
|
return &fakeStore{
|
|
analyticsCache: map[cacheKey]cacheEntry{},
|
|
analyticsPosts: map[string]store.AnalyticsPost{},
|
|
styleProfiles: map[string]store.UserStyleProfile{},
|
|
stylePosts: map[string]store.UserStylePost{},
|
|
}
|
|
}
|
|
|
|
func postKey(user, platform, id string) string {
|
|
return user + "|" + platform + "|" + id
|
|
}
|
|
|
|
func profileKey(user, platform string) string {
|
|
return user + "|" + platform
|
|
}
|
|
|
|
func (f *fakeStore) GetAnalyticsCache(ctx context.Context, arg store.GetAnalyticsCacheParams) (json.RawMessage, error) {
|
|
if f.cacheErr {
|
|
return nil, sql.ErrNoRows
|
|
}
|
|
key := cacheKey{arg.UserID, arg.Platform, arg.ToolName, arg.TimeRangeHash}
|
|
entry, ok := f.analyticsCache[key]
|
|
if !ok || time.Now().After(entry.exp) {
|
|
return nil, sql.ErrNoRows
|
|
}
|
|
return entry.value, nil
|
|
}
|
|
|
|
func (f *fakeStore) UpsertAnalyticsCache(ctx context.Context, arg store.UpsertAnalyticsCacheParams) error {
|
|
key := cacheKey{arg.UserID, arg.Platform, arg.ToolName, arg.TimeRangeHash}
|
|
f.analyticsCache[key] = cacheEntry{value: arg.Result, exp: arg.ExpiresAt}
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeStore) GetAnalyticsPostsInRange(ctx context.Context, arg store.GetAnalyticsPostsInRangeParams) ([]store.GetAnalyticsPostsInRangeRow, error) {
|
|
if f.postsRangeErr {
|
|
return nil, sql.ErrConnDone
|
|
}
|
|
var rows []store.GetAnalyticsPostsInRangeRow
|
|
for _, p := range f.analyticsPosts {
|
|
if p.UserID != arg.UserID || p.Platform != arg.Platform {
|
|
continue
|
|
}
|
|
if p.PostTimestamp.Before(arg.PostTimestamp) || p.PostTimestamp.After(arg.PostTimestamp_2) {
|
|
continue
|
|
}
|
|
rows = append(rows, store.GetAnalyticsPostsInRangeRow{
|
|
PostID: p.PostID,
|
|
PostText: p.PostText,
|
|
PostTimestamp: p.PostTimestamp,
|
|
Likes: p.Likes,
|
|
Reposts: p.Reposts,
|
|
Replies: p.Replies,
|
|
Quotes: p.Quotes,
|
|
})
|
|
}
|
|
sort.Slice(rows, func(i, j int) bool { return rows[i].PostTimestamp.Before(rows[j].PostTimestamp) })
|
|
if int32(len(rows)) > arg.Limit {
|
|
rows = rows[:arg.Limit]
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func (f *fakeStore) UpsertAnalyticsPosts(ctx context.Context, arg store.UpsertAnalyticsPostsParams) error {
|
|
f.analyticsPosts[postKey(arg.UserID, arg.Platform, arg.PostID)] = store.AnalyticsPost{
|
|
UserID: arg.UserID,
|
|
Platform: arg.Platform,
|
|
PostID: arg.PostID,
|
|
PostText: arg.PostText,
|
|
PostTimestamp: arg.PostTimestamp,
|
|
Likes: arg.Likes,
|
|
Reposts: arg.Reposts,
|
|
Replies: arg.Replies,
|
|
Quotes: arg.Quotes,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeStore) GetAnalyticsCoverageRanges(ctx context.Context, arg store.GetAnalyticsCoverageRangesParams) ([]store.GetAnalyticsCoverageRangesRow, error) {
|
|
if f.coverageErr {
|
|
return nil, sql.ErrConnDone
|
|
}
|
|
var rows []store.GetAnalyticsCoverageRangesRow
|
|
for _, r := range f.coverageRanges {
|
|
if r.UserID != arg.UserID || r.Platform != arg.Platform {
|
|
continue
|
|
}
|
|
rows = append(rows, store.GetAnalyticsCoverageRangesRow{CoveredStart: r.CoveredStart, CoveredEnd: r.CoveredEnd})
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func (f *fakeStore) InsertAnalyticsCoverageRange(ctx context.Context, arg store.InsertAnalyticsCoverageRangeParams) error {
|
|
f.coverageRanges = append(f.coverageRanges, store.AnalyticsCoverageRange{
|
|
UserID: arg.UserID,
|
|
Platform: arg.Platform,
|
|
CoveredStart: arg.CoveredStart,
|
|
CoveredEnd: arg.CoveredEnd,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeStore) GetStyleProfile(ctx context.Context, arg store.GetStyleProfileParams) (store.UserStyleProfile, error) {
|
|
if f.styleProfileErr != nil {
|
|
return store.UserStyleProfile{}, f.styleProfileErr
|
|
}
|
|
p, ok := f.styleProfiles[profileKey(arg.UserID, arg.Platform)]
|
|
if !ok {
|
|
return store.UserStyleProfile{}, sql.ErrNoRows
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (f *fakeStore) UpsertStyleProfile(ctx context.Context, arg store.UpsertStyleProfileParams) error {
|
|
f.styleProfiles[profileKey(arg.UserID, arg.Platform)] = store.UserStyleProfile{
|
|
UserID: arg.UserID,
|
|
Platform: arg.Platform,
|
|
Embedding: arg.Embedding,
|
|
TopKeywords: arg.TopKeywords,
|
|
StyleSummary: arg.StyleSummary,
|
|
PostCount: arg.PostCount,
|
|
ModifiedAt: sql.NullTime{Time: time.Now(), Valid: true},
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeStore) DeleteStylePosts(ctx context.Context, arg store.DeleteStylePostsParams) error {
|
|
for _, k := range f.stylePosts {
|
|
if k.UserID == arg.UserID && k.Platform == arg.Platform {
|
|
delete(f.stylePosts, postKey(k.UserID, k.Platform, k.PostID))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeStore) GetStylePosts(ctx context.Context, arg store.GetStylePostsParams) ([]store.GetStylePostsRow, error) {
|
|
if f.stylePostsErr != nil {
|
|
return nil, f.stylePostsErr
|
|
}
|
|
var rows []store.GetStylePostsRow
|
|
for _, p := range f.stylePosts {
|
|
if p.UserID != arg.UserID || p.Platform != arg.Platform {
|
|
continue
|
|
}
|
|
rows = append(rows, store.GetStylePostsRow{PostID: p.PostID, PostText: p.PostText})
|
|
}
|
|
sort.Slice(rows, func(i, j int) bool { return rows[i].PostID > rows[j].PostID })
|
|
if int32(len(rows)) > arg.Limit {
|
|
rows = rows[:arg.Limit]
|
|
}
|
|
return rows, nil
|
|
}
|
|
|
|
func (f *fakeStore) UpsertStylePosts(ctx context.Context, arg store.UpsertStylePostsParams) error {
|
|
f.stylePosts[postKey(arg.UserID, arg.Platform, arg.PostID)] = store.UserStylePost{
|
|
UserID: arg.UserID,
|
|
Platform: arg.Platform,
|
|
PostID: arg.PostID,
|
|
PostText: arg.PostText,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeStore) FindSimilarUsers(ctx context.Context, arg store.FindSimilarUsersParams) ([]store.FindSimilarUsersRow, error) {
|
|
if f.findSimilarErr != nil {
|
|
return nil, f.findSimilarErr
|
|
}
|
|
return f.similarRows, nil
|
|
}
|
|
|
|
func (f *fakeStore) PruneStylePosts(ctx context.Context) error {
|
|
return nil
|
|
}
|