feat: base implementation
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
unifood
|
||||||
|
__bin*
|
||||||
18
.vscode/launch.json
vendored
Normal file
18
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug app",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "debug",
|
||||||
|
"program": "${workspaceFolder}/main.go",
|
||||||
|
"args": [
|
||||||
|
"get", "menu"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
14
cmd/config.go
Normal file
14
cmd/config.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
type AppConfig struct {
|
||||||
|
OutputVerbose bool
|
||||||
|
OutputMode string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutputMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Go OutputMode = "go"
|
||||||
|
Json OutputMode = "json"
|
||||||
|
Yaml OutputMode = "yaml"
|
||||||
|
)
|
||||||
5
cmd/output.go
Normal file
5
cmd/output.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
type OutputProvider interface {
|
||||||
|
Convert(item any) (string, error)
|
||||||
|
}
|
||||||
19
cmd/resources.go
Normal file
19
cmd/resources.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/core/handler/menu"
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/core/interfaces"
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/core/services/stwhbclient"
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/model/external/stwbremen"
|
||||||
|
)
|
||||||
|
|
||||||
|
var availableResources = []interfaces.ResourceCommand[any]{
|
||||||
|
{
|
||||||
|
Name: "menu",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Verbs: []interfaces.Verb{interfaces.VerbGet},
|
||||||
|
Handler: &menu.MenuHandler{
|
||||||
|
QueryClient: stwhbclient.New[[]stwbremen.Dish](),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
42
cmd/root.go
Normal file
42
cmd/root.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/core/services/jlog"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "unifood",
|
||||||
|
Short: "Unifood is a CLI for retrieving restaurant information",
|
||||||
|
Long: ``,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
initRootCmd()
|
||||||
|
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initRootCmd() {
|
||||||
|
var appConfig AppConfig
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&appConfig.OutputVerbose, "verbose", "v", false, "Enable verbose output")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&appConfig.OutputMode, "output", "o", string(Json), "Set output format")
|
||||||
|
|
||||||
|
logger := jlog.New(slog.LevelDebug)
|
||||||
|
ctx := jlog.ContextWith(context.Background(), logger)
|
||||||
|
|
||||||
|
logger.Debug("Register verb commands")
|
||||||
|
rootCmd.AddCommand(getVerbs(ctx)...)
|
||||||
|
logger.Debug("Verb commands registered successfully")
|
||||||
|
}
|
||||||
99
cmd/verbs.go
Normal file
99
cmd/verbs.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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/jlog"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerbItem struct {
|
||||||
|
Name interfaces.Verb
|
||||||
|
Aliases []string
|
||||||
|
Description string
|
||||||
|
RunFn func(ctx context.Context, handler interfaces.ResourceHandler, params params.Container) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var verbs = []VerbItem{
|
||||||
|
{
|
||||||
|
Name: interfaces.VerbGet,
|
||||||
|
Description: "Retrieve resource information",
|
||||||
|
RunFn: func(ctx context.Context, handler interfaces.ResourceHandler, params params.Container) error {
|
||||||
|
h, ok := handler.(interfaces.GetHandler)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource does not support GET")
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := h.Get(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("retrieving item failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewEncoder(os.Stdout).Encode(item)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVerbs(ctx context.Context) (commands []*cobra.Command) {
|
||||||
|
logger := jlog.FromContext(ctx)
|
||||||
|
|
||||||
|
for _, v := range verbs {
|
||||||
|
verbCommand := &cobra.Command{
|
||||||
|
Use: strings.ToLower(string(v.Name)),
|
||||||
|
Aliases: v.Aliases,
|
||||||
|
Short: v.Description,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all resources that can handle this verb
|
||||||
|
for _, r := range availableResources {
|
||||||
|
if !slices.Contains(r.Verbs, v.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var params params.Container
|
||||||
|
|
||||||
|
resourceCommand := &cobra.Command{
|
||||||
|
Use: r.Name,
|
||||||
|
Aliases: r.Aliases,
|
||||||
|
Short: r.Description,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
logger := jlog.New(slog.LevelInfo)
|
||||||
|
ctx := jlog.ContextWith(context.Background(), logger)
|
||||||
|
err := v.RunFn(ctx, r.Handler, params)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorContext(ctx, fmt.Sprintf("%s %s failed", strings.ToUpper(string(v.Name)), r.Name), "error", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register parameters
|
||||||
|
for _, param := range r.Handler.GetParametersForVerb(v.Name) {
|
||||||
|
resourceCommand.Flags().StringVarP(
|
||||||
|
params.Register(param.Name, param.ParseFunc),
|
||||||
|
param.Name,
|
||||||
|
param.ShortHand,
|
||||||
|
param.DefaultFunc(),
|
||||||
|
param.Description,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
verbCommand.AddCommand(resourceCommand)
|
||||||
|
|
||||||
|
logger.Debug(fmt.Sprintf("Registered %s %s", strings.ToUpper(string(v.Name)), r.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
commands = append(commands, verbCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
88
core/handler/menu/menu.go
Normal file
88
core/handler/menu/menu.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package menu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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/model/external/stwbremen"
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/model/resources"
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MenuHandler struct {
|
||||||
|
interfaces.ResourceHandler
|
||||||
|
interfaces.GetHandler
|
||||||
|
|
||||||
|
QueryClient interfaces.QueryClient[[]stwbremen.Dish]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
paramDate = "date"
|
||||||
|
paramLocation = "location"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *MenuHandler) Get(ctx context.Context, params params.Container) (any, error) {
|
||||||
|
// Read parameters
|
||||||
|
p, err := params.GetValue(paramDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse date parameter: %w", err)
|
||||||
|
}
|
||||||
|
date := p.(time.Time)
|
||||||
|
|
||||||
|
location, err := params.GetValue(paramLocation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse location 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),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run query
|
||||||
|
dishes, 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"}`,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("querying menu failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return &resources.Menu{
|
||||||
|
Location: location.(string),
|
||||||
|
Dishes: util.Transform(*dishes, func(i *stwbremen.Dish) resources.Dish {
|
||||||
|
d, err := resources.DishFromDTO(*i)
|
||||||
|
if err != nil {
|
||||||
|
return resources.Dish{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *d
|
||||||
|
}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MenuHandler) GetParametersForVerb(verb interfaces.Verb) []params.Registration {
|
||||||
|
return []params.Registration{
|
||||||
|
{
|
||||||
|
Name: paramDate,
|
||||||
|
ShortHand: "d",
|
||||||
|
Description: "Menu date",
|
||||||
|
DefaultFunc: func() string { return time.Now().Format(time.DateOnly) },
|
||||||
|
ParseFunc: func(value string) (any, error) { return time.Parse(time.DateOnly, value) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: paramLocation,
|
||||||
|
ShortHand: "l",
|
||||||
|
Description: "Define the restaurant",
|
||||||
|
DefaultFunc: func() string { return "330" },
|
||||||
|
ParseFunc: params.ParseString,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
29
core/interfaces/handler.go
Normal file
29
core/interfaces/handler.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package interfaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/core/interfaces/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResourceCommand[T any] struct {
|
||||||
|
Name string
|
||||||
|
Aliases []string
|
||||||
|
Description string
|
||||||
|
Verbs []Verb
|
||||||
|
Handler ResourceHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourceHandler interface {
|
||||||
|
GetParametersForVerb(verb Verb) []params.Registration
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetHandler interface {
|
||||||
|
Get(ctx context.Context, params params.Container) (any, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Verb string
|
||||||
|
|
||||||
|
const (
|
||||||
|
VerbGet Verb = "get"
|
||||||
|
)
|
||||||
7
core/interfaces/http.go
Normal file
7
core/interfaces/http.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package interfaces
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type QueryClient[T any] interface {
|
||||||
|
Get(ctx context.Context, queryStatement string, selectStatement string, allowCache bool) (*T, error)
|
||||||
|
}
|
||||||
56
core/interfaces/params/params.go
Normal file
56
core/interfaces/params/params.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package params
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrParamNotFound = errors.New("parameter not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registration struct {
|
||||||
|
Name string
|
||||||
|
ShortHand string
|
||||||
|
Description string
|
||||||
|
DefaultFunc func() string
|
||||||
|
ParseFunc ParseFn
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParseFn func(value string) (any, error)
|
||||||
|
|
||||||
|
var ParseString ParseFn = func(value string) (any, error) { return value, nil }
|
||||||
|
|
||||||
|
type Container struct {
|
||||||
|
params map[string]Value
|
||||||
|
}
|
||||||
|
|
||||||
|
type Value struct {
|
||||||
|
value *string
|
||||||
|
parseFn ParseFn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) Register(key string, parseFn ParseFn) *string {
|
||||||
|
if c.params == nil {
|
||||||
|
c.params = make(map[string]Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
item, exists := c.params[key]
|
||||||
|
if exists {
|
||||||
|
return item.value
|
||||||
|
}
|
||||||
|
|
||||||
|
var str string
|
||||||
|
value := Value{
|
||||||
|
parseFn: parseFn,
|
||||||
|
value: &str,
|
||||||
|
}
|
||||||
|
c.params[key] = value
|
||||||
|
return value.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) GetValue(key string) (any, error) {
|
||||||
|
item, exists := c.params[key]
|
||||||
|
if !exists {
|
||||||
|
return nil, ErrParamNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.parseFn(*item.value)
|
||||||
|
}
|
||||||
23
core/services/jlog/context.go
Normal file
23
core/services/jlog/context.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package jlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ctxKey string
|
||||||
|
|
||||||
|
const ctxkeyLogger ctxKey = "jlog:logger"
|
||||||
|
|
||||||
|
func FromContext(ctx context.Context) *slog.Logger {
|
||||||
|
logger, exists := ctx.Value(ctxkeyLogger).(*slog.Logger)
|
||||||
|
if !exists {
|
||||||
|
return New(slog.LevelInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContextWith(ctx context.Context, logger *slog.Logger) context.Context {
|
||||||
|
return context.WithValue(ctx, ctxkeyLogger, logger)
|
||||||
|
}
|
||||||
24
core/services/jlog/logger.go
Normal file
24
core/services/jlog/logger.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package jlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(level slog.Level) *slog.Logger {
|
||||||
|
return NewFor(level, os.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFor(level slog.Level, output io.Writer) *slog.Logger {
|
||||||
|
return slog.New(slog.NewJSONHandler(output, &slog.HandlerOptions{
|
||||||
|
// AddSource: true,
|
||||||
|
Level: level,
|
||||||
|
})).With(slog.String("traceid", newTraceID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTraceID() string {
|
||||||
|
return strconv.Itoa(rand.Intn(1000000))
|
||||||
|
}
|
||||||
66
core/services/stwhbclient/client.go
Normal file
66
core/services/stwhbclient/client.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package stwhbclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/core/interfaces"
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/model/external/stwbremen"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stwHBClientFactory struct{}
|
||||||
|
|
||||||
|
func NewFactory() *stwHBClientFactory { return &stwHBClientFactory{} }
|
||||||
|
|
||||||
|
func Create[T any]() *stwHBClient[T] {
|
||||||
|
return New[T]()
|
||||||
|
}
|
||||||
|
|
||||||
|
type stwHBClient[T any] struct {
|
||||||
|
interfaces.QueryClient[T]
|
||||||
|
|
||||||
|
client *http.Client
|
||||||
|
baseUrl string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New[T any]() *stwHBClient[T] {
|
||||||
|
return &stwHBClient[T]{
|
||||||
|
client: http.DefaultClient,
|
||||||
|
baseUrl: "https://content.stw-bremen.de",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *stwHBClient[T]) Get(ctx context.Context, queryStatement string, selectStatement string, allowCache bool) (*T, error) {
|
||||||
|
// Setup url
|
||||||
|
url := c.baseUrl + "/api/kql"
|
||||||
|
if !allowCache {
|
||||||
|
url = url + "nocache"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create request from query
|
||||||
|
fullQuery := fmt.Sprintf(`{"query": "%s", "select": %s}`, queryStatement, selectStatement)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(fullQuery))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform request
|
||||||
|
response, err := c.client.Do(req)
|
||||||
|
if err != nil || response.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("performing request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
// Read response
|
||||||
|
var result stwbremen.Result[T]
|
||||||
|
err = json.NewDecoder(response.Body).Decode(&result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse result: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result.Result, nil
|
||||||
|
|
||||||
|
}
|
||||||
9
go.mod
Normal file
9
go.mod
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module git.bissendorf.co/bissendorf/unifood/m/v2
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/spf13/cobra v1.9.1 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.7 // indirect
|
||||||
|
)
|
||||||
11
go.sum
Normal file
11
go.sum
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||||
|
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||||
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||||
|
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
7
main.go
Normal file
7
main.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "git.bissendorf.co/bissendorf/unifood/m/v2/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
||||||
21
model/external/stwbremen/dish.go
vendored
Normal file
21
model/external/stwbremen/dish.go
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package stwbremen
|
||||||
|
|
||||||
|
type Dish struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Ingredients []Ingredient `json:"ingredients"`
|
||||||
|
Prices []Price `json:"prices"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
Date string `json:"date"` // YYYY-MM-DD
|
||||||
|
Counter string `json:"counter"`
|
||||||
|
Tags string `json:"mealadds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ingredient struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Additionals []string `json:"additionals"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Price struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Price string `json:"price"`
|
||||||
|
}
|
||||||
7
model/external/stwbremen/result.go
vendored
Normal file
7
model/external/stwbremen/result.go
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package stwbremen
|
||||||
|
|
||||||
|
type Result[T any] struct {
|
||||||
|
Code uint16 `json:"code"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Result T `json:"result"`
|
||||||
|
}
|
||||||
65
model/resources/menu.go
Normal file
65
model/resources/menu.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package resources
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/model/external/stwbremen"
|
||||||
|
"git.bissendorf.co/bissendorf/unifood/m/v2/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Menu struct {
|
||||||
|
Location string
|
||||||
|
Dishes []Dish
|
||||||
|
}
|
||||||
|
|
||||||
|
func DishFromDTO(dish stwbremen.Dish) (*Dish, error) {
|
||||||
|
date, err := time.Parse(time.DateOnly, dish.Date)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse dish date: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Dish{
|
||||||
|
Title: dish.Title,
|
||||||
|
Date: date,
|
||||||
|
Tags: strings.Split(strings.Replace(dish.Tags, " ", "", -1), ","),
|
||||||
|
Counter: dish.Counter,
|
||||||
|
Prices: util.Transform(dish.Prices, func(i *stwbremen.Price) price {
|
||||||
|
p, err := strconv.ParseFloat(strings.Trim(i.Price, " "), 32)
|
||||||
|
if err != nil {
|
||||||
|
p = 0
|
||||||
|
}
|
||||||
|
return price{
|
||||||
|
Label: i.Label,
|
||||||
|
Price: float32(p),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Ingredients: util.Select(util.Transform(dish.Ingredients, func(i *stwbremen.Ingredient) ingredient {
|
||||||
|
return ingredient{
|
||||||
|
Name: i.Label,
|
||||||
|
Additionals: i.Additionals,
|
||||||
|
}
|
||||||
|
}), func(i *ingredient) bool { return i.Name != "" }),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dish struct {
|
||||||
|
Title string
|
||||||
|
Ingredients []ingredient
|
||||||
|
Prices []price
|
||||||
|
Date time.Time
|
||||||
|
Counter string
|
||||||
|
Tags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ingredient struct {
|
||||||
|
Name string
|
||||||
|
Additionals []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type price struct {
|
||||||
|
Label string
|
||||||
|
Price float32
|
||||||
|
}
|
||||||
23
util/slices.go
Normal file
23
util/slices.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
func Transform[Tin any, Tout any](s []Tin, transformFn func(i *Tin) Tout) (out []Tout) {
|
||||||
|
out = make([]Tout, 0)
|
||||||
|
|
||||||
|
for _, item := range s {
|
||||||
|
out = append(out, transformFn(&item))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Select[T any](s []T, selectFn func(i *T) bool) (out []T) {
|
||||||
|
out = make([]T, 0)
|
||||||
|
|
||||||
|
for _, item := range s {
|
||||||
|
if selectFn(&item) {
|
||||||
|
out = append(out, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user