1package store23import (4 "context"5 "fmt"6 "slices"7 "time"89 "website-feeds/model"10)1112type Fake struct {13 settings model.Settings14 sites []model.Site15 posts map[int]map[string]storedPost16 requests []model.Request1718 InjectCreateSiteError *error19 InjectNumFetchPostsForSiteError *error20 InjectSettingsError *error21 InjectUpdateSettingsError *error22 InjectSiteByNameError *error23 InjectTopPostsForSiteError *error24 InjectWriteRequestError *error25 InjectWriteSitePostsError *error26}2728type storedPost struct {29 model.Post30 lastFetched time.Time31}3233func NewFake() Fake {34 return Fake{35 settings: model.Settings{36 // These are the current default settings37 DefaultNumPosts: 21,38 MaxNumPosts: 70,39 SiteFetchWait: 3 * time.Hour,40 },41 }42}4344var _ Store = (*Fake)(nil)4546func siteByName(name string) func(model.Site) bool {47 return func(s model.Site) bool {48 return s.Name == name49 }50}5152func siteByID(id int) func(model.Site) bool {53 return func(s model.Site) bool {54 return s.ID == id55 }56}5758// Close implements Store.59func (f *Fake) Close() {}6061// CreateSite implements Store.62func (f *Fake) CreateSite(63 ctx context.Context,64 name string,65 displayName string,66) (*model.Site, error) {67 if err := f.InjectCreateSiteError; err != nil {68 f.InjectCreateSiteError = nil69 return nil, *err70 }7172 if slices.ContainsFunc(f.sites, siteByName(name)) {73 return nil, fmt.Errorf("site already exists")74 }7576 s := model.Site{77 ID: len(f.sites),78 Name: name,79 DisplayName: displayName,80 LastFetched: nil,81 }82 f.sites = append(f.sites, s)8384 return &s, nil85}8687// SiteByName implements Store.88func (f *Fake) SiteByName(89 ctx context.Context,90 name string,91) (*model.Site, error) {92 if err := f.InjectSiteByNameError; err != nil {93 f.InjectSiteByNameError = nil94 return nil, *err95 }9697 if i := slices.IndexFunc(f.sites, siteByName(name)); i < 0 {98 return nil, fmt.Errorf("site not found")99 } else {100 return f.sites[i].Clone(), nil101 }102}103104// WriteSitePosts implements Store.105func (f *Fake) WriteSitePosts(106 ctx context.Context,107 siteID int,108 posts []model.Post,109 lastFetched model.SiteLastFetched,110) error {111 if err := f.InjectWriteSitePostsError; err != nil {112 f.InjectWriteSitePostsError = nil113 return *err114 }115116 if i := slices.IndexFunc(f.sites, siteByID(siteID)); i >= 0 {117 f.sites[i].LastFetched = lastFetched.Clone()118 if _, ok := f.posts[siteID]; !ok {119 f.posts[siteID] = map[string]storedPost{}120 }121122 for _, post := range posts {123 if dbPost, ok := f.posts[siteID][post.SiteUniqueID]; ok {124 if !lastFetched.Time.Before(dbPost.lastFetched) {125 dbPost.Score = post.Score126 dbPost.Title = post.Title127 dbPost.lastFetched = lastFetched.Time128 f.posts[siteID][post.SiteUniqueID] = dbPost129 }130 } else {131 f.posts[siteID][post.SiteUniqueID] = storedPost{132 Post: post,133 lastFetched: lastFetched.Time,134 }135 }136 }137 } else {138 return fmt.Errorf("site not found")139 }140141 return nil142}143144// TopPostsForSite implements Store.145func (f *Fake) TopPostsForSite(146 ctx context.Context,147 siteID int,148 now time.Time,149 numPosts int,150) ([]model.Post, error) {151 if err := f.InjectTopPostsForSiteError; err != nil {152 f.InjectTopPostsForSiteError = nil153 return nil, *err154 }155156 returnPosts := []model.Post{}157 if posts, ok := f.posts[siteID]; ok {158 for _, post := range posts {159 since := now.Sub(post.Created)160 if since > 8*24*time.Hour || since < 24*time.Hour {161 continue162 }163164 returnPosts = append(returnPosts, post.Post)165 }166 } else {167 return nil, fmt.Errorf("site not found")168 }169170 return returnPosts, nil171}172173// Settings implements Store.174func (f *Fake) Settings(ctx context.Context) (*model.Settings, error) {175 if err := f.InjectSettingsError; err != nil {176 f.InjectSettingsError = nil177 return nil, *err178 }179180 return f.settings.Clone(), nil181}182183// UpdateSettings implements Store.184func (f *Fake) UpdateSettings(185 ctx context.Context,186 settings *model.Settings,187) error {188 if err := f.InjectUpdateSettingsError; err != nil {189 f.InjectUpdateSettingsError = nil190 return *err191 }192193 f.settings = *settings.Clone()194195 return nil196}197198// WriteRequest implements Store.199func (f *Fake) WriteRequest(ctx context.Context, r *model.Request) error {200 if err := f.InjectWriteRequestError; err != nil {201 f.InjectWriteRequestError = nil202 return *err203 }204205 f.requests = append(f.requests, *r)206207 return nil208}209210// NumFetchPostsForSite implements Store.211func (f *Fake) NumFetchPostsForSite(212 ctx context.Context,213 siteID int,214 now time.Time,215) (int, error) {216 if err := f.InjectNumFetchPostsForSiteError; err != nil {217 f.InjectNumFetchPostsForSiteError = nil218 return -1, *err219 }220221 numPosts := -1222 for _, request := range f.requests {223 if now.Sub(request.Timestamp) > 24*time.Hour {224 continue225 }226227 if request.SiteId != siteID {228 continue229 }230231 numPosts = max(request.NumPosts, numPosts)232 }233234 if len(f.requests) == 0 {235 return -1, fmt.Errorf("no matching requests in store")236 }237238 return numPosts, nil239}