···11package main
2233import (
44- "careme/internal/config"
55- "careme/internal/logsetup"
66- "careme/internal/mail"
77- "careme/internal/static"
88- "careme/internal/templates"
94 "context"
105 _ "embed"
116 "flag"
127 "log"
138 "log/slog"
149 "os"
1010+1111+ "careme/internal/config"
1212+ "careme/internal/logsetup"
1313+ "careme/internal/mail"
1414+ "careme/internal/static"
1515+ "careme/internal/templates"
1516)
16171718func main() {
1819 var serve, mailer bool
1920 var addr string
20212121- //left for back compat does noting
2222+ // left for back compat does noting
2223 flag.BoolVar(&serve, "serve", false, "dead we always serve")
2324 flag.BoolVar(&mailer, "mail", false, "Run one-shot mail sender and exit")
2425 flag.StringVar(&addr, "addr", ":8080", "Address to bind in server mode")
2526 flag.Parse()
26272727- if err := os.MkdirAll("recipes", 0755); err != nil {
2828+ if err := os.MkdirAll("recipes", 0o755); err != nil {
2829 log.Fatalf("failed to create recipes directory: %v", err)
2930 }
3031
+4-3
cmd/careme/middleware.go
···11package main
2233import (
44- "careme/internal/logsetup"
54 "errors"
65 "log/slog"
76 "net/http"
···1110 "strconv"
1211 "strings"
1312 "time"
1313+1414+ "careme/internal/logsetup"
14151516 "github.com/clerk/clerk-sdk-go/v2"
1617 azureappinsights "github.com/microsoft/ApplicationInsights-Go/appinsights"
···32333334func (l *logger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
3435 start := time.Now()
3535- //should we use auth client?
3636+ // should we use auth client?
3637 user := ""
3738 if claims, ok := clerk.SessionClaimsFromContext(r.Context()); ok {
3839 user = claims.Subject
···150151}
151152152153func (r *recoverer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
153153- //app insights could also track this https://github.com/microsoft/ApplicationInsights-Go?tab=readme-ov-file#exceptions
154154+ // app insights could also track this https://github.com/microsoft/ApplicationInsights-Go?tab=readme-ov-file#exceptions
154155 defer func() {
155156 if err := recover(); err != nil {
156157 slog.ErrorContext(req.Context(), "panic recovered", "error", err, "stack", debug.Stack())
+1-1
cmd/careme/ready.go
···2020 return err
2121 }
2222 }
2323- //not thread safe? only ever set to true
2323+ // not thread safe? only ever set to true
2424 r.done = true
2525 return nil
2626}
+13-13
cmd/careme/web.go
···11package main
2233import (
44+ "context"
55+ "errors"
66+ "fmt"
77+ "html/template"
88+ "log/slog"
99+ "net/http"
1010+ "os"
1111+ "os/signal"
1212+ "syscall"
1313+ "time"
1414+415 "careme/internal/actowiz"
516 "careme/internal/admin"
617 "careme/internal/auth"
···1526 "careme/internal/templates"
1627 "careme/internal/users"
1728 utypes "careme/internal/users/types"
1818- "context"
1919- "errors"
2020- "fmt"
2121- "html/template"
2222- "log/slog"
2323- "net/http"
2424- "os"
2525- "os/signal"
2626- "syscall"
2727- "time"
2829)
29303031func runServer(cfg *config.Config, addr string) error {
···102103 http.Error(w, "unable to load account", http.StatusInternalServerError)
103104 return
104105 }
105105- //no user is fine we'll just pass nil currentUser to template
106106+ // no user is fine we'll just pass nil currentUser to template
106107 // just have two different templates?
107107-108108 }
109109110110 var favoriteStoreName string
···112112 loc, locErr := locationStorage.GetLocationByID(ctx, currentUser.FavoriteStore)
113113 if locErr != nil {
114114 slog.ErrorContext(ctx, "failed to get location name for favorite store", "location_id", currentUser.FavoriteStore, "error", locErr)
115115- //mutation intentionally not saved bac.
115115+ // mutation intentionally not saved bac.
116116 currentUser.FavoriteStore = ""
117117 } else {
118118 favoriteStoreName = loc.Name
+3-4
cmd/careme/web_e2e_test.go
···2727 defer srv.Close()
28282929 client := newTestClient(t)
3030- resp := mustGet(t, client, srv.URL+"/ready") //our readiness probe works even with mocks?
3030+ resp := mustGet(t, client, srv.URL+"/ready") // our readiness probe works even with mocks?
3131 if resp.StatusCode != http.StatusOK {
3232 t.Fatalf("expected /ready to return 200 OK, got %d", resp.StatusCode)
3333 }
···118118 t.Fatalf("expected recipe page to persist stars value, got body: %s", recipeBody)
119119 }
120120121121- //TODO step 6 make sure recipes are saved to user page?
122122-121121+ // TODO step 6 make sure recipes are saved to user page?
123122}
124123125124func TestZipFromCoordinatesRedirect(t *testing.T) {
···154153 t.Helper()
155154156155 cfg := &config.Config{Mocks: config.MockConfig{Enable: true}}
157157- err := templates.Init(cfg, "dummyhash") //initialize templates so they don't hit the file system during tests
156156+ err := templates.Init(cfg, "dummyhash") // initialize templates so they don't hit the file system during tests
158157 if err != nil {
159158 t.Fatalf("failed to create templates %v", err)
160159 }
···11package main
2233import (
44- "careme/internal/config"
55- "careme/internal/kroger"
66- "careme/internal/recipes"
74 "context"
85 "flag"
96 "fmt"
···118 "slices"
129 "strings"
1310 "unicode"
1111+1212+ "careme/internal/config"
1313+ "careme/internal/kroger"
1414+ "careme/internal/recipes"
14151516 "github.com/samber/lo"
1617 "golang.org/x/text/unicode/norm"
···2425 var locationID string
2526 var produceCSV string
26272727- //local to bellevue Fred Meyer 70100023, factoria 70500822
2828+ // local to bellevue Fred Meyer 70100023, factoria 70500822
2829 flag.StringVar(&locationID, "location", "70500874", "Kroger location ID to validate")
2930 flag.StringVar(&locationID, "l", "70500874", "Kroger location ID to validate (short)")
3031 flag.StringVar(&produceCSV, "produce", strings.Join(all, ","), "Comma-separated produce list to check")
···9091}
91929293func checkProduceAvailability(ctx context.Context, client staplesProvider, locationID string, produce []string) ([]string, int, error) {
9393-9494- //todo check total number of queries.
9494+ // todo check total number of queries.
95959696 ingredients, err := client.FetchStaples(ctx, locationID)
9797 if err != nil {
···112112113113 // TODO have staples return subset of ingredient that can say the search term it go a match on
114114 // then we can give info on whats coming from what queries. Could use categories for wholefoods.
115115- //annotateUniqueOnlyMatches(stats)
115115+ // annotateUniqueOnlyMatches(stats)
116116 printProduceFilterSummary(stats, len(produce))
117117118118 return evaluateProduceAvailability(produce, ingredients), len(ingredients), nil
···190190}*/
191191192192func printProduceFilterSummary(stat produceFilterStats, totalProduceTerms int) {
193193-194193 fmt.Printf("- %s -> %d ingredients, %d/%d produce terms, %d matches, %d unique-only products\n",
195194 stat.FilterTerm,
196195 stat.IngredientMatches,
···318317}
319318320319func normalizeToken(s string) string {
321321-322320 switch {
323321 case strings.HasSuffix(s, "ies") && len(s) > 3:
324322 s = s[:len(s)-3] + "y"
···11package ai
2233import (
44- "careme/internal/kroger"
55- "careme/internal/locations"
64 "context"
75 "encoding/base64"
86 "encoding/json"
···1210 "log/slog"
1311 "strings"
1412 "time"
1313+1414+ "careme/internal/kroger"
1515+ "careme/internal/locations"
15161617 openai "github.com/openai/openai-go/v3"
1718 "github.com/openai/openai-go/v3/conversations"
···3233// todo collapse closer to
3334type Ingredient struct {
3435 Name string `json:"name"`
3535- Quantity string `json:"quantity"` //should this and price be numbers? need units then
3636- Price string `json:"price"` //TODO exclude empty
3636+ Quantity string `json:"quantity"` // should this and price be numbers? need units then
3737+ Price string `json:"price"` // TODO exclude empty
3738}
38393940type Recipe struct {
···4647 Health string `json:"health"`
4748 DrinkPairing string `json:"drink_pairing"`
4849 WineStyles []string `json:"wine_styles"`
4949- OriginHash string `json:"origin_hash,omitempty" jsonschema:"-"` //not in schema
5050- Saved bool `json:"previously_saved,omitempty" jsonschema:"-"` //not in schema
5050+ OriginHash string `json:"origin_hash,omitempty" jsonschema:"-"` // not in schema
5151+ Saved bool `json:"previously_saved,omitempty" jsonschema:"-"` // not in schema
5152}
52535354// ComputeHash calculates the fnv128 hash of the recipe content
···7475 return base64.URLEncoding.EncodeToString(fnv.Sum(nil))
7576}
76777777-//intionally not including ConversationID to preserve old hashes
7878-7878+// intionally not including ConversationID to preserve old hashes
7979type ShoppingList struct {
8080 ConversationID string `json:"conversation_id,omitempty" jsonschema:"-"`
8181 Recipes []Recipe `json:"recipes" jsonschema:"required"`
···88888989// ignoring model for now.
9090func NewClient(apiKey, _ string) *Client {
9191- //ignor model for now.
9191+ // ignor model for now.
9292 r := jsonschema.Reflector{
9393 DoNotReference: true, // no $defs and no $ref
9494 ExpandedStruct: true, // put the root type inline (not a $ref)
···161161 Format: responses.ResponseFormatTextConfigUnionParam{
162162 OfJSONSchema: &responses.ResponseFormatTextJSONSchemaConfigParam{
163163 Name: "recipes",
164164- Schema: schema, //https://platform.openai.com/docs/guides/structured-outputs?example=structured-data
164164+ Schema: schema, // https://platform.openai.com/docs/guides/structured-outputs?example=structured-data
165165 },
166166 },
167167 }
···176176177177 params := responses.ResponseNewParams{
178178 Model: c.model,
179179- //only new input
179179+ // only new input
180180 Input: responses.ResponseNewParamsInputUnion{
181181 OfInputItemList: messages,
182182 },
···303303 },
304304 Text: scheme(c.schema),
305305 }
306306- //should we stream. Can we pass past generation.
306306+ // should we stream. Can we pass past generation.
307307308308 resp, err := client.Responses.New(ctx, params)
309309 if err != nil {
···319319// buildRecipeMessages creates separate messages for the LLM to process more efficiently
320320func (c *Client) buildRecipeMessages(location *locations.Location, saleIngredients []kroger.Ingredient, instructions []string, date time.Time, lastRecipes []string) (responses.ResponseInputParam, error) {
321321 var messages []responses.ResponseInputItemUnionParam
322322- //constants we might make variable later
322322+ // constants we might make variable later
323323 messages = append(messages, user("Prioritize ingredients that are in season for the current date and user's state location "+date.Format("January 2nd")+" in "+location.State+"."))
324324 messages = append(messages, user("Default: each recipe should serve 2 people."))
325325 messages = append(messages, user("Default: generate 3 recipes"))
+1-1
internal/ai/recipe_test.go
···5252 }
53535454 hash := recipe.ComputeHash()
5555- //fnv 128 url encoded is 24
5555+ // fnv 128 url encoded is 24
5656 if len(hash) != 24 {
5757 t.Fatalf("expected hash length of 24, got %d", len(hash))
5858 }
+5-4
internal/albertsons/cache.go
···11package albertsons
2233import (
44- "careme/internal/cache"
55- locationtypes "careme/internal/locations/types"
66- "careme/internal/sitemapfetch"
74 "context"
85 "encoding/json"
96 "errors"
107 "fmt"
118 "log/slog"
99+1010+ "careme/internal/cache"
1111+ locationtypes "careme/internal/locations/types"
1212+ "careme/internal/sitemapfetch"
12131314 "github.com/samber/lo"
1415 lop "github.com/samber/lo/parallel"
···5051 return nil, fmt.Errorf("list cached store summaries: %w", err)
5152 }
52535353- //expensive. Just save a smaller map of centroids
5454+ // expensive. Just save a smaller map of centroids
5455 summaries := lop.Map(keys, func(key string, _ int) *StoreSummary {
5556 reader, err := c.Get(ctx, StoreCachePrefix+key)
5657 if err != nil {
···1313const AppInsightsConnectionStringEnv = "APPLICATIONINSIGHTS_CONNECTION_STRING"
14141515func Configure(ctx context.Context) (func(), error) {
1616-1716 handlers := []slog.Handler{slog.NewTextHandler(os.Stdout, nil)}
18171919- closeFn := func() {} //can be a list if we have multiple
1818+ closeFn := func() {} // can be a list if we have multiple
20192120 if connectionString := os.Getenv(AppInsightsConnectionStringEnv); connectionString != "" {
2221 handler, err := appinsights.NewHandler(connectionString, nil)
+8-7
internal/mail/mail.go
···5566import (
77 "bytes"
88- "careme/internal/cache"
99- "careme/internal/config"
1010- "careme/internal/locations"
1111- "careme/internal/recipes"
1212- "careme/internal/users"
1313- utypes "careme/internal/users/types"
148 "context"
159 "encoding/json"
1610 "errors"
···1913 "net/http"
2014 "os"
2115 "time"
1616+1717+ "careme/internal/cache"
1818+ "careme/internal/config"
1919+ "careme/internal/locations"
2020+ "careme/internal/recipes"
2121+ "careme/internal/users"
2222+ utypes "careme/internal/users/types"
22232324 "github.com/sendgrid/rest"
2425 "github.com/sendgrid/sendgrid-go"
···168169 }
169170 }
170171171171- //can orphan recipes here with crash or shutdown. Params should have a start time
172172+ // can orphan recipes here with crash or shutdown. Params should have a start time
172173173174 shoppingList, err = m.generator.GenerateRecipes(ctx, p)
174175 if err != nil {
-2
internal/mail/mail_test.go
···102102}
103103104104func TestSendEmail_DoesNotRecordSentClaimOnNonSuccessSendGridStatus(t *testing.T) {
105105-106105 fc := newFakeMailCache(t)
107106 location := &locations.Location{ID: "123", Name: "Test Store", Address: "123 Test St", ZipCode: "98005"}
108107 m := &mailer{
···131130}
132131133132func TestSendEmail_RecordsSentClaimOnSuccessSendGridStatus(t *testing.T) {
134134-135133 fc := newFakeMailCache(t)
136134 location := &locations.Location{ID: "123", Name: "Test Store", Address: "123 Test St", ZipCode: "98005"}
137135 m := &mailer{
···102102 return nil, fmt.Errorf("request %q: %w", endpoint, err)
103103 }
104104 defer func() {
105105- //why do we need to copy to discard here?
105105+ // why do we need to copy to discard here?
106106 // Because some servers (including Cloudflare) will not close the connection
107107 // if the response body is not fully read, which can lead to resource leaks and
108108 // exhaustion of available connections in the HTTP client's connection pool.
···11package recipes
2233import (
44- "careme/internal/ai"
55- "careme/internal/locations"
64 "net/http/httptest"
75 "strings"
86 "testing"
97 "time"
88+99+ "careme/internal/ai"
1010+ "careme/internal/locations"
1011)
11121213// Test that the HTML contains Save and Dismiss buttons for recipes.
···11package recipes
2233import (
44- "careme/internal/ai"
55- "careme/internal/cache"
66- "careme/internal/config"
77- "careme/internal/kroger"
88- "careme/internal/locations"
99- "careme/internal/parallelism"
1010- "careme/internal/wholefoods"
114 "context"
125 "encoding/base64"
136 "errors"
···1811 "strings"
1912 "time"
20131414+ "careme/internal/ai"
1515+ "careme/internal/cache"
1616+ "careme/internal/config"
1717+ "careme/internal/kroger"
1818+ "careme/internal/locations"
1919+ "careme/internal/parallelism"
2020+ "careme/internal/wholefoods"
2121+2122 "github.com/samber/lo"
2223 "github.com/samber/lo/mutable"
2324)
···6465 var styles []string
6566 for _, style := range recipe.WineStyles {
6667 style = strings.TrimSpace(style)
6767- if style != "" { //would this ever happen?
6868+ if style != "" { // would this ever happen?
6869 styles = append(styles, style)
6970 }
7071 }
71727272- //whole foods search not actually implmented hard code categories
7373+ // whole foods search not actually implmented hard code categories
7374 if wholefoods.NewIdentityProvider().IsID(location) {
7474- styles = []string{"red-wine", "white-wine", "sparkling"} //rose
7575+ styles = []string{"red-wine", "white-wine", "sparkling"} // rose
7576 }
76777778 if len(styles) == 0 {
···158159 // should never happen? How do you get save on first generte?
159160 // shoppingList.Recipes = append(shoppingList.Recipes, p.Saved...)
160161161161- //TODO this does not get saved in params and thus must be loaded from html
162162+ // TODO this does not get saved in params and thus must be loaded from html
162163 // could update params after first generation or pregenerate before we save params.
163164 p.ConversationID = shoppingList.ConversationID
164165 slog.InfoContext(ctx, "generated chat", "location", p.String(), "duration", time.Since(start), "hash", hash)
···185186 if err != nil {
186187 return nil, fmt.Errorf("failed to get ingredients for staples: %w", err)
187188 }
188188- //should this be pushed down into staple proivder? go off product id?
189189+ // should this be pushed down into staple proivder? go off product id?
189190 ingredients = uniqueByDescription(ingredients)
190191191192 mutable.Shuffle(ingredients)
···2233import (
44 "bytes"
55- "careme/internal/ai"
66- "careme/internal/locations"
75 "context"
86 "encoding/base64"
97 "errors"
···1412 "net/http"
1513 "strings"
1614 "time"
1515+1616+ "careme/internal/ai"
1717+ "careme/internal/locations"
17181819 "github.com/samber/lo"
1920)
···3031 Location *locations.Location `json:"location,omitempty"`
3132 Date time.Time `json:"date,omitempty"`
3233 // People int
3333- //per round instuctions
3434+ // per round instuctions
3435 Instructions string `json:"instructions,omitempty"`
3536 Directive string `json:"directive,omitempty"` // this is the new one that will be used. Can remove GenerationPrompt after a while.
3637 LastRecipes []string `json:"last_recipes,omitempty"`
3738 // UserID string `json:"user_id,omitempty"`
3839 ConversationID string `json:"conversation_id,omitempty"` // Can remove if we pass it in separately to generate recipes?
3939- //TODO Both should just be title and hash insread of full ai.Recipe
4040+ // TODO Both should just be title and hash insread of full ai.Recipe
4041 Saved []ai.Recipe `json:"saved_recipes,omitempty"`
4142 Dismissed []ai.Recipe `json:"dismissed_recipes,omitempty"`
4243}
···11package recipes
2233import (
44- "careme/internal/ai"
55- "careme/internal/cache"
64 "context"
75 "encoding/json"
86 "errors"
97 "fmt"
108 "strings"
119 "time"
1010+1111+ "careme/internal/ai"
1212+ "careme/internal/cache"
12131314 "github.com/samber/lo"
1415)
···9293 if err != nil {
9394 return fmt.Errorf("failed to marshal recipe selection: %w", err)
9495 }
9595- //good place for etags :)
9696+ // good place for etags :)
9697 if err := s.Cache.Put(ctx, recipeSelectionKey(userID, originHash), string(body), cache.Unconditional()); err != nil {
9798 return fmt.Errorf("failed to save recipe selection: %w", err)
9899 }
+23-23
internal/recipes/server.go
···2233import (
44 "bytes"
55- "careme/internal/ai"
66- "careme/internal/auth"
77- "careme/internal/cache"
88- "careme/internal/config"
99- "careme/internal/locations"
1010- "careme/internal/seasons"
1111- "careme/internal/templates"
1212- "careme/internal/users"
1313- utypes "careme/internal/users/types"
145 "context"
156 "errors"
167 "fmt"
···2213 "strings"
2314 "sync"
2415 "time"
1616+1717+ "careme/internal/ai"
1818+ "careme/internal/auth"
1919+ "careme/internal/cache"
2020+ "careme/internal/config"
2121+ "careme/internal/locations"
2222+ "careme/internal/seasons"
2323+ "careme/internal/templates"
2424+ "careme/internal/users"
2525+ utypes "careme/internal/users/types"
25262627 "github.com/samber/lo"
2728)
···146147 FormatRecipeHTML(p, *recipe, signedIn, thread, feedback, wineRecommendation, w)
147148 return
148149 }
149149- //we didn't go back and update old recipes's with new hash so have to handle that here. Could still backfill
150150+ // we didn't go back and update old recipes's with new hash so have to handle that here. Could still backfill
150151 if normalizedHash, ok := legacyHashToCurrent(recipe.OriginHash, legacyRecipeHashSeed); ok {
151152 slog.InfoContext(ctx, "normalized legacy origin hash to current hash", "origin_hash", recipe.OriginHash, "hash", normalizedHash)
152153 recipe.OriginHash = normalizedHash
153153- //could resave to backfill but don't think we'll ever get them all without looping
154154+ // could resave to backfill but don't think we'll ever get them all without looping
154155 }
155156 p, err := s.ParamsFromCache(ctx, recipe.OriginHash)
156157 if err != nil {
157158 slog.ErrorContext(ctx, "failed to load params for hash", "hash", recipe.OriginHash, "error", err)
158158- //http.Error(w, "recipe not found or expired", http.StatusNotFound)
159159- //return
159159+ // http.Error(w, "recipe not found or expired", http.StatusNotFound)
160160+ // return
160161 p = DefaultParams(&locations.Location{
161162 ID: "",
162163 Name: "Unknown Location",
···217218 return
218219 }
219220220220- //this is going to take a while. Start a go routine? and spin?
221221+ // this is going to take a while. Start a go routine? and spin?
221222 // can't use request context because it will be canceled when request finishes but we want to finish processing question and save it to cache.
222223 ctx, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), 45*time.Second)
223224 defer cancel()
···443444 return
444445 }
445446446446- //could pass this in with htmx instead of loading title
447447+ // could pass this in with htmx instead of loading title
447448 recipe, err := s.SingleFromCache(ctx, recipeHash)
448449 if err != nil {
449450 if errors.Is(err, cache.ErrNotFound) {
···604605 http.Error(w, "failed to prepare regeneration", http.StatusInternalServerError)
605606 return
606607 }
607607- //so we have a choice we could save slection here matching params
608608+ // so we have a choice we could save slection here matching params
608609 // or backfill it on first load after regeneration Backfilling is a little more resilient
609609- //selection := recipeSelectionFromParams(p)
610610- //if err := s.saveRecipeSelection(ctx, currentUser.ID, newHash, selection);
610610+ // selection := recipeSelectionFromParams(p)
611611+ // if err := s.saveRecipeSelection(ctx, currentUser.ID, newHash, selection);
611612 s.kickgeneration(ctx, p, currentUser)
612613613614 redirectToHash(w, r, newHash, true /*useStart*/)
···640641 return
641642 }
642643 if len(p.Saved) == 0 {
643643- //ui does not allow this
644644+ // ui does not allow this
644645 slog.ErrorContext(ctx, "Got zero saved recipes finalize", "hash", hash)
645646 http.Error(w, "no recipes selected to save", http.StatusBadRequest)
646647 return
···679680680681 selection, err := s.loadRecipeSelection(ctx, userID, hash)
681682 if err != nil {
682682- //should we just fall back to params? selection saving
683683+ // should we just fall back to params? selection saving
683684 return nil, fmt.Errorf("failed to load recipe selection")
684685 }
685686···700701func (s *server) notFound(ctx context.Context, w http.ResponseWriter, r *http.Request) {
701702 startArg := r.URL.Query().Get(queryArgStart)
702703 hashParam := r.URL.Query().Get(queryArgHash)
703703- //okay give them a new start time.
704704+ // okay give them a new start time.
704705 if startArg == "" {
705706 redirectToHash(w, r, hashParam, true /*useStart*/)
706707 return
···834835 }
835836836837 p.Directive = currentUser.Directive
837837- //if params are already saved redirect and assume someone kicks off genration
838838+ // if params are already saved redirect and assume someone kicks off genration
838839839840 if err := s.SaveParams(ctx, p); err != nil {
840841 if errors.Is(err, ErrAlreadyExists) {
···997998}
9989999991000func (s *server) removeRecipeFromUserProfile(ctx context.Context, currentUser utypes.User, recipeHash string) error {
10001000-10011001 recipeHash = strings.TrimSpace(recipeHash)
10021002 if recipeHash == "" {
10031003 return fmt.Errorf("invalid recipe hash")
···11package sitemap
2233import (
44- "careme/internal/cache"
55- "careme/internal/recipes"
64 "encoding/xml"
75 "fmt"
86 "log/slog"
97 "net/http"
108 "strings"
99+1010+ "careme/internal/cache"
1111+ "careme/internal/recipes"
1112)
12131314type Server struct {
···2627)
27282829func New(c cache.ListCache) *Server {
2929-3030 return &Server{cache: c}
3131}
3232···4747}
48484949func (s *Server) handleSitemap(w http.ResponseWriter, r *http.Request) {
5050-5150 hashes, err := s.cache.List(r.Context(), recipes.ShoppingListCachePrefix, "")
5251 if err != nil {
5352 http.Error(w, "failed to load sitemap", http.StatusInternalServerError)
···5756 entries := make([]urlEntry, 0, len(hashes)+1)
5857 entries = append(entries, urlEntry{Loc: domain + "/about"})
59586060- //this is going to get too big. at some point we need a real db to find latest
6161- //or we track new entries and expire a lsit.
5959+ // this is going to get too big. at some point we need a real db to find latest
6060+ // or we track new entries and expire a lsit.
6261 for _, key := range hashes {
6362 hash := strings.TrimPrefix(key, recipes.ShoppingListCachePrefix)
6463 entries = append(entries, urlEntry{Loc: domain + "/recipes?h=" + hash})
···11package walmart
2233import (
44- locationtypes "careme/internal/locations/types"
54 "context"
65 "fmt"
76 "strconv"
77+88+ locationtypes "careme/internal/locations/types"
89)
9101011func (c *Client) GetLocationByID(_ context.Context, locationID string) (*locationtypes.Location, error) {
1111- //depending on cache to protect us.
1212+ // depending on cache to protect us.
1213 return nil, fmt.Errorf("walmart GetLocationByID not supported yet for ID %s", locationID)
1314}
1415
+5-4
internal/wholefoods/cache.go
···11package wholefoods
2233import (
44- "careme/internal/cache"
55- locationtypes "careme/internal/locations/types"
66- "careme/internal/sitemapfetch"
74 "context"
85 "encoding/json"
96 "errors"
···118 "log/slog"
129 "strconv"
13101111+ "careme/internal/cache"
1212+ locationtypes "careme/internal/locations/types"
1313+ "careme/internal/sitemapfetch"
1414+1415 "github.com/samber/lo"
1516 lop "github.com/samber/lo/parallel"
1617)
17181819const (
1920 Container = "wholefoods"
2020- //prefixes are a little redundant since we already have a container. Could simpify with reimport.
2121+ // prefixes are a little redundant since we already have a container. Could simpify with reimport.
2122 StoreCachePrefix = "wholefoods/stores/"
2223 StoreURLMapCacheKey = "wholefoods/store_url_map.json"
2324 LocationIDPrefix = "wholefoods_"
···11package wholefoods
2233import (
44+ "context"
55+ "fmt"
66+ "strings"
77+48 "careme/internal/cache"
59 "careme/internal/config"
610 "careme/internal/locations/nearby"
711 locationtypes "careme/internal/locations/types"
88- "context"
99- "fmt"
1010- "strings"
1112)
12131314type centroidByZip interface {
···3738}
38393940func newLocationBackend(ctx context.Context, c cache.ListCache, zipLookup centroidByZip) (*LocationBackend, error) {
4040-4141- //Is this too much? should we just fetch a single blob that is all coordinates -> store ids and lazily fetch stores?
4141+ // Is this too much? should we just fetch a single blob that is all coordinates -> store ids and lazily fetch stores?
4242 summaries, err := loadCachedStoreSummaries(ctx, c)
4343 if err != nil {
4444 return nil, err