feat: add universities and restaurants (#4)

Features:
- Allow to search for individual resources by their name
- Add new resources and their handler: Universities, Restaurants
- Added new parameter to reverse output order

Reviewed-on: #4
Co-authored-by: bdoerfchen <git@bissendorf.co>
Co-committed-by: bdoerfchen <git@bissendorf.co>
This commit is contained in:
2025-07-20 22:13:03 +00:00
committed by bissendorf
parent e395b0b4ca
commit 4b7866da03
20 changed files with 408 additions and 33 deletions

View File

@ -1,7 +1,8 @@
package cmd
type AppConfig struct {
OutputVerbose bool
OutputFormatter string
PrintConfig bool
OutputVerbose bool
OutputFormatter string
OutputOrderReverse bool
PrintConfig bool
}

View File

@ -2,8 +2,11 @@ package cmd
import (
"context"
"slices"
"strings"
"git.bissendorf.co/bissendorf/unifood/m/v2/core/handler/meals"
"git.bissendorf.co/bissendorf/unifood/m/v2/core/handler/universities"
"git.bissendorf.co/bissendorf/unifood/m/v2/core/interfaces"
"git.bissendorf.co/bissendorf/unifood/m/v2/core/interfaces/params"
"git.bissendorf.co/bissendorf/unifood/m/v2/core/services/stwhbclient"
@ -15,28 +18,56 @@ import (
var availableResources = []interfaces.ResourceCommand[any]{
{
Name: "resources",
Aliases: []string{"resource", "r"},
Description: "A meta resource representing all other object kinds of this CLI",
Aliases: []string{"resource"},
Description: "A meta resource representing all other object kinds of this CLI.",
Verbs: []interfaces.Verb{interfaces.VerbGet},
Handler: &registeredResourcesHandler{},
},
{
Name: "meals",
Aliases: []string{"meal", "m"},
Description: "A meal represents a cooked combination of ingredients that can be bought and consumed",
Description: "A meal represents a cooked combination of ingredients that can be bought and consumed.",
Verbs: []interfaces.Verb{interfaces.VerbGet},
Handler: &meals.MealsHandler{
QueryClient: stwhbclient.New[[]stwbremen.Meal](),
},
},
{
Name: "universities",
Aliases: []string{"university", "unis", "uni", "u"},
Description: "A facility that is hosting one or multiple restaurants.",
Verbs: []interfaces.Verb{interfaces.VerbGet},
Handler: &universities.UniversityHandler{
QueryClient: stwhbclient.New[stwbremen.RestaurantList](),
},
},
{
Name: "restaurants",
Aliases: []string{"restaurant", "r"},
Description: "A place to eat meals",
Verbs: []interfaces.Verb{interfaces.VerbGet},
Handler: &universities.RestaurantHandler{
QueryClient: stwhbclient.New[stwbremen.RestaurantList](),
QueryClientRestaurant: stwhbclient.New[stwbremen.Restaurant](),
},
},
}
type registeredResourcesHandler struct{}
func (h *registeredResourcesHandler) Get(ctx context.Context, params params.Container) (*interfaces.ResourceList, error) {
func (h *registeredResourcesHandler) Get(ctx context.Context, name string, params params.Container) (*interfaces.ResourceList, error) {
lowerName := strings.ToLower(name)
list := util.Select(availableResources, func(i *interfaces.ResourceCommand[any]) bool {
return name == "" || i.Name == lowerName
})
slices.SortFunc(list, func(a, b interfaces.ResourceCommand[any]) int {
return strings.Compare(a.Name, b.Name)
})
return &interfaces.ResourceList{
ItemKind: resources.ResourceResource,
Items: util.Transform(availableResources, func(i *interfaces.ResourceCommand[any]) interfaces.Resource {
Items: util.Transform(list, func(i *interfaces.ResourceCommand[any]) interfaces.Resource {
return &resources.Resource{
ResourceName: i.Name,
Aliases: i.Aliases,

View File

@ -32,6 +32,7 @@ func initRootCmd() {
rootCmd.PersistentFlags().BoolVarP(&appConfig.OutputVerbose, "verbose", "v", false, "Enable verbose output")
rootCmd.PersistentFlags().StringVarP(&appConfig.OutputFormatter, "output", "o", "table", "Set output format")
rootCmd.PersistentFlags().BoolVar(&appConfig.OutputOrderReverse, "reverse", false, "Reverses output item order")
rootCmd.PersistentFlags().BoolVar(&appConfig.PrintConfig, "print-config", false, "Enable printing the application config")
logger := jlog.New(slog.LevelDebug)

View File

@ -20,24 +20,27 @@ type VerbItem struct {
Name interfaces.Verb
Aliases []string
Description string
RunFn func(ctx context.Context, config *AppConfig, handler interfaces.ResourceHandler, params params.Container) error
RunFn func(ctx context.Context, config *AppConfig, handler interfaces.ResourceHandler, params params.Container, name string) error
}
var verbs = []VerbItem{
{
Name: interfaces.VerbGet,
Description: "Retrieve a list of resources",
RunFn: func(ctx context.Context, config *AppConfig, handler interfaces.ResourceHandler, params params.Container) error {
RunFn: func(ctx context.Context, config *AppConfig, handler interfaces.ResourceHandler, params params.Container, name string) error {
h, ok := handler.(interfaces.GetHandler)
if !ok {
return fmt.Errorf("resource does not support GET")
}
// Get items
items, err := h.Get(ctx, params)
items, err := h.Get(ctx, name, params)
if err != nil {
return fmt.Errorf("retrieving item failed: %w", err)
}
if config.OutputOrderReverse {
slices.Reverse(items.Items)
}
formatterName := strings.ToLower(config.OutputFormatter)
formatter, exists := output.Formatters[formatterName]
@ -94,7 +97,12 @@ func getVerbs(ctx context.Context, config *AppConfig) (commands []*cobra.Command
logger.WarnContext(ctx, "Printing app config", slog.Any("config", config), slog.Any("parameters", params.ToMap()))
}
err := v.RunFn(ctx, config, r.Handler, params)
var name string
if len(args) > 0 {
name = args[0]
}
err := v.RunFn(ctx, config, r.Handler, params, name)
if err != nil {
logger.ErrorContext(ctx, fmt.Sprintf("%s %s failed", strings.ToUpper(string(v.Name)), r.Name), "error", err.Error())
os.Exit(1)