From 6278b773d0b880510ec6a401b5021cfa4e602ce9 Mon Sep 17 00:00:00 2001 From: bdoerfchen Date: Mon, 21 Jul 2025 00:57:53 +0200 Subject: [PATCH] feat: meals with id and better search --- cmd/verbs.go | 2 + core/handler/meals/handler.go | 71 +++++++++++++++++++++----------- core/handler/params.go | 4 +- model/external/stwbremen/meal.go | 1 + model/resources/meals.go | 38 ++++++++++------- 5 files changed, 74 insertions(+), 42 deletions(-) diff --git a/cmd/verbs.go b/cmd/verbs.go index 6274eb8..4a24b24 100644 --- a/cmd/verbs.go +++ b/cmd/verbs.go @@ -83,6 +83,7 @@ func getVerbs(ctx context.Context, config *AppConfig) (commands []*cobra.Command Use: r.Name, Aliases: r.Aliases, Short: r.Description, + Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { // Configure log var logLevel = slog.LevelWarn @@ -120,6 +121,7 @@ func getVerbs(ctx context.Context, config *AppConfig) (commands []*cobra.Command param.Description, ) } + verbCommand.AddCommand(resourceCommand) } diff --git a/core/handler/meals/handler.go b/core/handler/meals/handler.go index 5e3018e..57b28b3 100644 --- a/core/handler/meals/handler.go +++ b/core/handler/meals/handler.go @@ -3,6 +3,8 @@ package meals import ( "context" "fmt" + "slices" + "strings" "time" "git.bissendorf.co/bissendorf/unifood/m/v2/core/handler" @@ -22,47 +24,68 @@ type MealsHandler struct { func (h *MealsHandler) Get(ctx context.Context, name string, params params.Container) (*interfaces.ResourceList, error) { // Read parameters - p, err := params.GetValue(handler.ParamDate) + date, err := params.GetValue(handler.ParamDate) if err != nil { return nil, fmt.Errorf("unable to parse date parameter: %w", err) } - date := p.(time.Time) - location, err := params.GetValue(handler.ParamLocation) + restaurant, err := params.GetValue(handler.ParamRestaurant) if err != nil { - return nil, fmt.Errorf("unable to parse location parameter: %w", err) + return nil, fmt.Errorf("unable to parse restaurant parameter: %w", err) } // Build query - query := fmt.Sprintf( - `page('meals').children.filterBy('location', '%s').filterBy('date', '%s').filterBy('printonly', 0)`, - location, - date.Format(time.DateOnly), - ) + query := "page('meals').children.filterBy('printonly', 0)" + if date != "" { + parsed, err := time.Parse(time.DateOnly, date.(string)) + if err != nil { + return nil, fmt.Errorf("unable to parse date parameter: %w", err) + } + query += fmt.Sprintf(".filterBy('date', '%s')", parsed.Format(time.DateOnly)) + } + if restaurant != "" { + query += fmt.Sprintf(".filterBy('location', '%s')", restaurant.(string)) + } + if name != "" { + query += fmt.Sprintf(".filterBy('id', 'meals/%s')", name) + } // Run query meals, err := h.QueryClient.Get(ctx, query, - `{"title":true,"ingredients":"page.ingredients.toObject","prices":"page.prices.toObject","location":true,"counter":true,"date":true,"mealadds":true,"mark":true,"frei3":true,"printonly":true,"kombicategory":true,"categories":"page.categories.split"}`, + `{"title":true,"id":"page.id","ingredients":"page.ingredients.toObject","prices":"page.prices.toObject","location":true,"counter":true,"date":true,"mealadds":true,"mark":true,"frei3":true,"printonly":true,"kombicategory":true,"categories":"page.categories.split"}`, false, ) if err != nil { return nil, fmt.Errorf("querying menu failed: %w", err) } + // Transform to meal resource + items := util.Transform(*meals, func(i *stwbremen.Meal) *resources.Meal { + d, err := resources.MealFromDTO(*i) + if err != nil { + return &resources.Meal{} + } + + return d + }) + + // Sort by date, restaurant and title - need to apply stable sorts in reverse order to achieve this + slices.SortFunc(items, func(a, b *resources.Meal) int { + return strings.Compare(a.Title, b.Title) + }) + slices.SortStableFunc(items, func(a, b *resources.Meal) int { + return strings.Compare(a.RestaurantID, b.RestaurantID) + }) + slices.SortStableFunc(items, func(a, b *resources.Meal) int { + return a.Date.Compare(b.Date) + }) + // Return return &interfaces.ResourceList{ ItemKind: resources.ResourceMeal, - Items: util.Transform(*meals, func(i *stwbremen.Meal) interfaces.Resource { - d, err := resources.MealFromDTO(*i) - if err != nil { - return &resources.Meal{} - } - - return d - }), + Items: util.Transform(items, func(i **resources.Meal) interfaces.Resource { return *i }), }, nil - } func (h *MealsHandler) GetParametersForVerb(verb interfaces.Verb) []params.Registration { @@ -71,14 +94,14 @@ func (h *MealsHandler) GetParametersForVerb(verb interfaces.Verb) []params.Regis Name: handler.ParamDate, ShortHand: "d", Description: "Meal date", - DefaultFunc: func() string { return time.Now().Format(time.DateOnly) }, - ParseFunc: func(value string) (any, error) { return time.Parse(time.DateOnly, value) }, + DefaultFunc: func() string { return "" }, + ParseFunc: params.ParseString, }, { - Name: handler.ParamLocation, - ShortHand: "l", + Name: handler.ParamRestaurant, + ShortHand: "r", Description: "Select a restaurant", - DefaultFunc: func() string { return "330" }, + DefaultFunc: func() string { return "" }, ParseFunc: params.ParseString, }, } diff --git a/core/handler/params.go b/core/handler/params.go index 04618ea..04ec9b0 100644 --- a/core/handler/params.go +++ b/core/handler/params.go @@ -1,6 +1,6 @@ package handler const ( - ParamDate string = "date" - ParamLocation string = "location" + ParamDate string = "date" + ParamRestaurant string = "restaurant" ) diff --git a/model/external/stwbremen/meal.go b/model/external/stwbremen/meal.go index 9d0d84a..10ac0e4 100644 --- a/model/external/stwbremen/meal.go +++ b/model/external/stwbremen/meal.go @@ -2,6 +2,7 @@ package stwbremen type Meal struct { Title string `json:"title"` + ID string `json:"id"` Ingredients []Ingredient `json:"ingredients"` Prices []Price `json:"prices"` Location string `json:"location"` diff --git a/model/resources/meals.go b/model/resources/meals.go index a17cc00..68bc469 100644 --- a/model/resources/meals.go +++ b/model/resources/meals.go @@ -16,12 +16,15 @@ func MealFromDTO(meal stwbremen.Meal) (*Meal, error) { return nil, fmt.Errorf("unable to parse meal date: %w", err) } + id, _ := strings.CutPrefix(meal.ID, "meals/") + return &Meal{ - Title: meal.Title, - Location: meal.Location, - Date: date, - Tags: strings.Split(strings.Replace(meal.Tags, " ", "", -1), ","), - Counter: meal.Counter, + Title: meal.Title, + RestaurantID: meal.Location, + ID: id, + Date: date, + Tags: strings.Split(strings.Replace(meal.Tags, " ", "", -1), ","), + Counter: meal.Counter, Prices: util.Map(meal.Prices, func(i *stwbremen.Price) (string, float32) { p, err := strconv.ParseFloat(strings.Trim(i.Price, " "), 32) if err != nil { @@ -41,13 +44,14 @@ func MealFromDTO(meal stwbremen.Meal) (*Meal, error) { const ResourceMeal = "meal" type Meal struct { - Title string - Location string - Ingredients []ingredient - Prices map[string]float32 - Date time.Time - Counter string - Tags []string + Title string + ID string + RestaurantID string + Ingredients []ingredient + Prices map[string]float32 + Date time.Time + Counter string + Tags []string } type ingredient struct { @@ -56,10 +60,12 @@ type ingredient struct { } func (d *Meal) Kind() string { return ResourceMeal } -func (d *Meal) ItemName() string { return d.Title } +func (d *Meal) ItemName() string { return d.ID } // Table output -func (d *Meal) ColumnNames() []string { return []string{"Location", "Date", "Counter", "Price"} } -func (d *Meal) Columns() []any { - return []any{d.Location, d.Date.Format(time.DateOnly), d.Counter, d.Prices["Studierende"]} +func (d *Meal) ColumnNames() []string { + return []string{"Date", "Restaurant", "Title", "Counter", "Price"} +} +func (d *Meal) Columns() []any { + return []any{d.Date.Format(time.DateOnly), d.RestaurantID, d.Title, d.Counter, d.Prices["Studierende"]} } -- 2.49.0