diff --git a/api/v1/openapi.yaml b/api/v1/openapi.yaml index 579675f..f4d9537 100644 --- a/api/v1/openapi.yaml +++ b/api/v1/openapi.yaml @@ -163,10 +163,15 @@ components: maximum: 5 description: type: string + pricing: + type: array + items: + $ref: "#/components/schemas/TrainingPrice" required: - name - days - description + - pricing Training: allOf: @@ -185,6 +190,34 @@ components: x-go-type-import: path: gitlab.mareshq.com/hq/yggdrasil/pkg/training + TrainingPrice: + type: object + properties: + currency: + type: string + enum: + - CZK + - EUR + - USD + x-go-type: currency.Currency + x-go-type-package: + path: gitlab.mareshq.com/hq/yggdrasil/internal/currency + amount: + type: number + minimum: 0 + x-go-type: decimal.Decimal + x-go-type-package: + path: github.com/shopspring/decimal + type: + type: string + enum: + - OPEN + - CORPORATE + required: + - currency + - amount + - type + ProblemDetails: type: object description: > diff --git a/go.mod b/go.mod index b27e3a1..2f9183a 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( github.com/oklog/ulid v1.3.1 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.52.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect diff --git a/go.sum b/go.sum index bfae769..fb64f68 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= diff --git a/internal/currency/currency.go b/internal/currency/currency.go new file mode 100644 index 0000000..208fbfc --- /dev/null +++ b/internal/currency/currency.go @@ -0,0 +1,13 @@ +package currency + +type Currency string + +const ( + USD Currency = "USD" + EUR Currency = "EUR" + CZK Currency = "CZK" +) + +var ( + AllowedCurrencies = []Currency{USD, EUR, CZK} +) diff --git a/internal/server/api.gen.go b/internal/server/api.gen.go index 1404ccf..08962e2 100644 --- a/internal/server/api.gen.go +++ b/internal/server/api.gen.go @@ -16,9 +16,17 @@ import ( "github.com/getkin/kin-openapi/openapi3" "github.com/gofiber/fiber/v2" "github.com/oapi-codegen/runtime" + "github.com/shopspring/decimal" + "gitlab.mareshq.com/hq/yggdrasil/internal/currency" "gitlab.mareshq.com/hq/yggdrasil/pkg/training" ) +// Defines values for TrainingPriceType. +const ( + CORPORATE TrainingPriceType = "CORPORATE" + OPEN TrainingPriceType = "OPEN" +) + // CreateTrainingRequest defines model for CreateTrainingRequest. type CreateTrainingRequest = NewTraining @@ -33,9 +41,10 @@ type ListTrainingsResponse = []Training // NewTraining defines model for NewTraining. type NewTraining struct { - Days int8 `json:"days"` - Description string `json:"description"` - Name string `json:"name"` + Days int8 `json:"days"` + Description string `json:"description"` + Name string `json:"name"` + Pricing []TrainingPrice `json:"pricing"` } // ProblemDetails Schema that carries the details of an error in an HTTP response. See https://datatracker.ietf.org/doc/html/rfc7807 for more information. @@ -58,15 +67,26 @@ type ProblemDetails struct { // Training defines model for Training. type Training struct { - Days int8 `json:"days"` - Description string `json:"description"` - Id TrainingID `json:"id"` - Name string `json:"name"` + Days int8 `json:"days"` + Description string `json:"description"` + Id TrainingID `json:"id"` + Name string `json:"name"` + Pricing []TrainingPrice `json:"pricing"` } // TrainingID defines model for TrainingID. type TrainingID = training.TrainingID +// TrainingPrice defines model for TrainingPrice. +type TrainingPrice struct { + Amount decimal.Decimal `json:"amount"` + Currency currency.Currency `json:"currency"` + Type TrainingPriceType `json:"type"` +} + +// TrainingPriceType defines model for TrainingPrice.Type. +type TrainingPriceType string + // UpdateTrainingRequest defines model for UpdateTrainingRequest. type UpdateTrainingRequest = NewTraining @@ -585,27 +605,29 @@ func (sh *strictHandler) UpdateTraining(ctx *fiber.Ctx, trainingID TrainingID) e // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/9xYwXLbNhD9FQzaW2lRbpJJhqemdZtoJk3cxOnF9QEClyQcEoCBpWyNhl/SU/+l/a8O", - "AJEiJdqRG2Xc5GSaBHbfvl087GpFuaq0kiDR0mRFNTOsAgTj/zszTEgh89mJ+y8Fy43QKJSkSfeNzE5o", - "RIV7pRkWNKKSVUATipvNETVwVQsDKU3Q1BBRywuomLP6rYGMJvSbeAMkDl9t3PPfNI2zYrWSFjy4mUQw", - "kpU/G6OMe8GVRJDoHpnWpeDMQY21UfMSqu8urcO92tP1adh1AshEaYP7IQGtfwIeQBPRmVywUqQzqWt8", - "OFQeAxEOxAbaa4W/qFqmDwXrtUKSOQAtpKatAZ/LnwwwhDbdb+GqBuuxaaM0GBQh5Slb+r+ZMhVDmlAh", - "8RmNaMVuRFVXNHkS0UrI8HwcUVxqCKsgB8/DANWqXWDRCJm776F2dz40/Qo+D6uiAGdo86JzquaXwNHZ", - "3A4u1LBnvizfZDQ5v5vd13DdbqZNtNpxsM2SSO95rlaD4ERKL5qLJqIvAL880K+E7VDbPmyBUNl9fbi0", - "rREzY9jSH6FeRF9JZW6d2x2Nf+dpIVgwJJwZI8ASLICkYQNRGWEynGgipHt+eXZ2SlqZnpB3AKRA1DaJ", - "45QhQ8P4BzATAZhNlMnjVPG4wKqMTcafPps+JZkypFIGiJCBS6Hk5A9Jo23CPYRdyM9JUVdMHhlgKZuX", - "QOBGl0x6O8Rq4CITnKAiWAhLFOe1MSA5uFhcaGsBnNBoNwVCWmSSw5jX929nxEAGwZhnTKQgUWQtaZ3z", - "+zm1yLAeyc1ZAYHtsIBwlQLJQYJhCCmZL71lZUQuJLFgFmA8uXvH3atNFFjCHlzbuqqYWW7ZJM7gaGzh", - "xX8h8yOmt06F/9qG0VEatUXUy+zYKemf+v+3+A37tU6L6lqkOxxF9OYoV0frl22zNjnrd23diiNRaWXC", - "jeyavITmAks2n1TMgC2uJlxVcXEVL/M8NcyKMtYf8hg7DpqIvtfp13vFbwf3ZdyWjde0TLX9IOM+IVB5", - "ZaWCVT8s1CUCL3yaXZI37f3v6hL//pMX5Fdm4J+/aERr43a1cn99fT3Z2d1EtBQc1tysLZ0apY0AZGbZ", - "Exvq7NqXv5HnpzMa0QUYG+RhOjmeTN1CpUEyLWhCH/lXka9Nz0+8OO6qz7/IwYfmOPRXwSylybBXoFvj", - "xffT6R198v364/GmZKRNfk5KYdHrZ4erieiTgGXMRYc5Hs5DvsEOcryOlLCy7JmNKDLHzTndUOA0RCs7", - "QtWwh10PdGDxR5UuD0bT+BTQDI+mmx+bnVwdfzYQtyerm4C535K6TD3eL1Pbc+IhchyAE0YkXHd5viXN", - "TTQ8IvFqM6034UYuAWG3Dk78+14d9H8wuEXkNksGqnSxk8THd/zKEACtOX78caaGA+8h+A2hE9Zx63os", - "f02OH6RRyenNVAcm73BqNTb3jWoV9qalB0rKC8D9M6LrkYwMr+5PT8rhZXG8c9pLFqefDcQeslj7LZ8o", - "iw9UVyHcfUvL7/UjVqiZISGvFGclSWEBpdIVSFyPY4OWKYnj0q0rlMXk0XQ69eW0drZt8U1bwZawuapx", - "cK2vu6oNvuai+TcAAP//9nxvI+cVAAA=", + "H4sIAAAAAAAC/+xYzXLbthN/FQz+/1tpUW6SSYanurabaJraqmP3UNcHCFyJcEgABpZ2NB4+SU99l/a9", + "OgBIipQYW06ccTqTkyh8LH7728V+4JZyVWglQaKlyS3VzLACEIz/d2qYkEIuJgfuXwqWG6FRKEmTdo5M", + "DmhEhRvSDDMaUckKoAnF1eaIGrgqhYGUJmhKiKjlGRTMSf2/gTlN6P/iFZA4zNq4c35VVU6K1Upa8OAm", + "EsFIlh8ao4wb4EoiSHSfTOtccOagxtqoWQ7Fd5fW4b7d8uhp2HUAyERuw/F9AprzCXgAVUQn8prlIp1I", + "XeLTofIYiHAgVtCOFP6kSpk+FawjhWTuADSQqsYHvC33DTCExtwncFWC9di0URoMimDylC3971yZgiFN", + "qJD4ika0YB9EURY0eRHRQsjwvRtRXGoIq2ABnoceqttmgUUj5MLNB98dmNBGcPeZ3FKBUNhtXXdqBAcn", + "oJbIjGFLGpy5uRLn4dgo6NcHuTr5opWhZpfA0Qld5y1cD2/UPD+e0+T8bpRHcNNsplW0TrdIH3RB+yqJ", + "dADxRRXR14D/LcBvhW0R2y7kBznCpg9EtKvNN2e/z9nXosxGRnrn0RDMGBLOjBFgCWZA0rCBqDlhMsQf", + "IqT7fnN6OiVNUhmRdwAkQ9Q2ieOUIUPD+HswIwE4HymziFPF4wyLPDZz/vLV+CWZK0MKZYAIGcwklBz9", + "ERTp2dJD2IS8R7KyYHLHAEvZLAcCH3TOpJdDrAYu5oITVAQzYYnivDQGJAeni1OtDtcjGm0aUUiLTHIY", + "OvXsZEIMzCEI84yJFCSKeUNae/jDDrXIsBywzWkGge2wgHCVAlmABMMQUjJbesnKiIWQxIK5BuPJ3Vrv", + "jtujwBy24NqWRcHMck0mcQIHdQsDn0LmPaLX7oefbdRoKY0aJ+pYduiWdAPK1xtT+5VlG+LKUqQb/ET0", + "w85C7dSDTVk5Ou3Wl+2KHVFoZULt4MrRhC4E5mw2KpgBm12NuCri7CpeLhapYVbksX6/iLHVv4MtRLON", + "uMwKVYbCqY2+4xayLIsZmDXIKXBRsHx0EH57cDXj79kC+nizcuZx2kxpqx0JcS3DA6xvw9JtAukAnNP9", + "33+mET08O6ERPXt30KF9kMVGxGi/kXUvqDtJFHUxHLfYqs6NaUAeTw+PaET3j0+mxyd7p4ebKNcdiK/g", + "1bzXO4Zc/0yn36rIT0is67x9/UVZ5fPbXDWdDOPezlD4LEsFK364VpcIPPMe6/x11Zj+pi7x7z95Rn5h", + "Bv75i0a0NG5Xk/pvbm5GG7uriOaCQ81LLWlqlDYCkJllJ/FQJ9e++ZXsTSc0otdgbEgV49HuaOwWKg2S", + "aUET+swPRf6aebbi6902GvmBBXjVHKO+LJikNOmXpHStMf5+PL6jw3tYZzdc+w40eHskFxZ9Lm1xVRF9", + "EbAMHdFijvudvG8NQ2quNSUszztiI4rMcXNOVxS4nKKVHaCq3yLVTxFg8UeVLh+NpuH+tep7M5oSqg1b", + "7X4xEB83Vvt2w/2W1Fnq+XaWWn/heAwbB+CEEQk3rZ0/YuYq6l+R+Hb1zlSF6iwHhE0/OPDjHT/oPnV9", + "JMCtlvRi1MWGEZ/f8T4WANUcP7+fqf5TzWPwG1QnrOXW1du+bBq+SIMhp9O2PzJ5jxethp4WBmMVdpry", + "JzLKa8DtLaLLAYv00/bnG+Xxw+JwQbZVWBx/MRBbhMXSb/nMsPhEfhXU3da1/F7fbgef6RPyVnGWkxSu", + "IVe6AIl1a94rmZI4zt26TFlMno3HY+9O9WHrEo8bD7aEzVSJvbReV1UrfNVF9W8AAAD//yH0TvuhGAAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/server/api.go b/internal/server/api.go index ebd953e..5853047 100644 --- a/internal/server/api.go +++ b/internal/server/api.go @@ -29,12 +29,21 @@ func (h *APIHandlers) ListTrainings(ctx context.Context, req ListTrainingsReques } data := make([]Training, len(trainings)) - for idx, training := range trainings { + for idx, t := range trainings { + pricing := make([]TrainingPrice, len(t.Pricing)) + for idx, p := range t.Pricing { + pricing[idx] = TrainingPrice{ + Amount: p.Amount, + Currency: p.Currency, + Type: TrainingPriceType(p.Type), + } + } data[idx] = Training{ - Id: training.ID, - Name: training.Name, - Days: training.Days, - Description: training.Description, + Id: t.ID, + Name: t.Name, + Days: t.Days, + Description: t.Description, + Pricing: pricing, } } @@ -42,10 +51,20 @@ func (h *APIHandlers) ListTrainings(ctx context.Context, req ListTrainingsReques } func (h *APIHandlers) CreateTraining(ctx context.Context, req CreateTrainingRequestObject) (CreateTrainingResponseObject, error) { + pricing := make([]training.TrainingPrice, len(req.Body.Pricing)) + for idx, p := range req.Body.Pricing { + pricing[idx] = training.TrainingPrice{ + Amount: p.Amount, + Currency: p.Currency, + Type: training.TrainingPriceType(p.Type), + } + } + t := training.Training{ Name: req.Body.Name, Days: req.Body.Days, Description: req.Body.Description, + Pricing: pricing, } err := h.trainingRepository.Create(&t) @@ -58,11 +77,21 @@ func (h *APIHandlers) CreateTraining(ctx context.Context, req CreateTrainingRequ }}, nil } + responsePricing := make([]TrainingPrice, len(t.Pricing)) + for idx, p := range t.Pricing { + responsePricing[idx] = TrainingPrice{ + Amount: p.Amount, + Currency: p.Currency, + Type: TrainingPriceType(p.Type), + } + } + return CreateTraining201JSONResponse{ Id: t.ID, Name: t.Name, Days: t.Days, Description: t.Description, + Pricing: responsePricing, }, nil } @@ -103,20 +132,38 @@ func (h *APIHandlers) GetTraining(ctx context.Context, req GetTrainingRequestObj }}, nil } + pricing := make([]TrainingPrice, len(t.Pricing)) + for idx, p := range t.Pricing { + pricing[idx] = TrainingPrice{ + Amount: p.Amount, + Currency: p.Currency, + Type: TrainingPriceType(p.Type), + } + } return GetTraining200JSONResponse{ Id: t.ID, Name: t.Name, Days: t.Days, Description: t.Description, + Pricing: pricing, }, nil } func (h *APIHandlers) UpdateTraining(ctx context.Context, req UpdateTrainingRequestObject) (UpdateTrainingResponseObject, error) { + pricing := make([]training.TrainingPrice, len(req.Body.Pricing)) + for idx, p := range req.Body.Pricing { + pricing[idx] = training.TrainingPrice{ + Amount: p.Amount, + Currency: p.Currency, + Type: training.TrainingPriceType(p.Type), + } + } t := training.Training{ ID: req.TrainingID, Name: req.Body.Name, Days: req.Body.Days, Description: req.Body.Description, + Pricing: pricing, } err := h.trainingRepository.Update(&t) diff --git a/pkg/training/model.go b/pkg/training/model.go index 17f82fc..eb3c1b9 100644 --- a/pkg/training/model.go +++ b/pkg/training/model.go @@ -2,6 +2,8 @@ package training import ( "github.com/google/uuid" + "github.com/shopspring/decimal" + "gitlab.mareshq.com/hq/yggdrasil/internal/currency" ) type TrainingID = uuid.UUID @@ -15,4 +17,18 @@ type Training struct { Name string Days int8 Description string + Pricing []TrainingPrice } + +type TrainingPrice struct { + Amount decimal.Decimal + Currency currency.Currency + Type TrainingPriceType +} + +type TrainingPriceType string + +const ( + OpenTrainingPrice TrainingPriceType = "OPEN" + CorporateTrainingPrice TrainingPriceType = "CORPORATE" +) diff --git a/pkg/training/repository.go b/pkg/training/repository.go index af3136c..642b8a3 100644 --- a/pkg/training/repository.go +++ b/pkg/training/repository.go @@ -1,5 +1,10 @@ package training +import ( + "github.com/shopspring/decimal" + "gitlab.mareshq.com/hq/yggdrasil/internal/currency" +) + type TrainingRepository interface { Create(training *Training) error FindByID(id TrainingID) (*Training, error) @@ -17,35 +22,167 @@ func NewInMemoryTrainingRepository() *InMemoryTrainingRepository { trainings: make(map[TrainingID]Training), } - repo.Create(&Training{ + _ = repo.Create(&Training{ Name: "Kubernetes", Days: 2, - Description: "", + Description: "Kubernetes", + Pricing: []TrainingPrice{ + { + Amount: decimal.NewFromInt(450), + Currency: currency.USD, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(2000), + Currency: currency.USD, + Type: CorporateTrainingPrice, + }, + { + Amount: decimal.NewFromInt(9900), + Currency: currency.CZK, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(49000), + Currency: currency.CZK, + Type: CorporateTrainingPrice, + }, + }, }) - repo.Create(&Training{ + _ = repo.Create(&Training{ Name: "Terraform", Days: 1, - Description: "", + Description: "Terraform", + Pricing: []TrainingPrice{ + { + Amount: decimal.NewFromInt(200), + Currency: currency.USD, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(1000), + Currency: currency.USD, + Type: CorporateTrainingPrice, + }, + { + Amount: decimal.NewFromInt(4900), + Currency: currency.CZK, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(24000), + Currency: currency.CZK, + Type: CorporateTrainingPrice, + }, + }, }) - repo.Create(&Training{ + _ = repo.Create(&Training{ Name: "RKE2", Days: 1, - Description: "", + Description: "RKE2", + Pricing: []TrainingPrice{ + { + Amount: decimal.NewFromInt(200), + Currency: currency.USD, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(1000), + Currency: currency.USD, + Type: CorporateTrainingPrice, + }, + { + Amount: decimal.NewFromInt(4900), + Currency: currency.CZK, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(24000), + Currency: currency.CZK, + Type: CorporateTrainingPrice, + }, + }, }) - repo.Create(&Training{ + _ = repo.Create(&Training{ Name: "GitHub Actions", Days: 1, - Description: "", + Description: "GitHub Actions", + Pricing: []TrainingPrice{ + { + Amount: decimal.NewFromInt(200), + Currency: currency.USD, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(1000), + Currency: currency.USD, + Type: CorporateTrainingPrice, + }, + { + Amount: decimal.NewFromInt(4900), + Currency: currency.CZK, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(24000), + Currency: currency.CZK, + Type: CorporateTrainingPrice, + }, + }, }) - repo.Create(&Training{ + _ = repo.Create(&Training{ Name: "GitLab CI", Days: 1, - Description: "", + Description: "GitLab CI", + Pricing: []TrainingPrice{ + { + Amount: decimal.NewFromInt(200), + Currency: currency.USD, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(1000), + Currency: currency.USD, + Type: CorporateTrainingPrice, + }, + { + Amount: decimal.NewFromInt(4900), + Currency: currency.CZK, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(24000), + Currency: currency.CZK, + Type: CorporateTrainingPrice, + }, + }, }) - repo.Create(&Training{ + _ = repo.Create(&Training{ Name: "Prometheus", Days: 2, - Description: "", + Description: "Prometheus", + Pricing: []TrainingPrice{ + { + Amount: decimal.NewFromInt(450), + Currency: currency.USD, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(2000), + Currency: currency.USD, + Type: CorporateTrainingPrice, + }, + { + Amount: decimal.NewFromInt(9900), + Currency: currency.CZK, + Type: OpenTrainingPrice, + }, + { + Amount: decimal.NewFromInt(49000), + Currency: currency.CZK, + Type: CorporateTrainingPrice, + }, + }, }) return repo