diff --git a/cmd/yggdrasil/main.go b/cmd/yggdrasil/main.go index 008708e..150fd86 100644 --- a/cmd/yggdrasil/main.go +++ b/cmd/yggdrasil/main.go @@ -22,7 +22,8 @@ func main() { defer logger.Sync() trainingRepository := training.NewInMemoryTrainingRepository() - apiHandlers := server.NewAPIHandlers(trainingRepository) + trainingDateRepository := training.NewInMemoryTrainingDateRepository() + apiHandlers := server.NewAPIHandlers(trainingRepository, trainingDateRepository) server := server.NewServer(apiHandlers, port, logger) server.Run(shutdownCtx) } diff --git a/internal/server/api.gen.go b/internal/server/api.gen.go index 08962e2..50304fa 100644 --- a/internal/server/api.gen.go +++ b/internal/server/api.gen.go @@ -12,6 +12,7 @@ import ( "net/url" "path" "strings" + "time" "github.com/getkin/kin-openapi/openapi3" "github.com/gofiber/fiber/v2" @@ -27,15 +28,27 @@ const ( OPEN TrainingPriceType = "OPEN" ) +// CreateTrainingDateRequest defines model for CreateTrainingDateRequest. +type CreateTrainingDateRequest = NewTrainingDate + +// CreateTrainingDateResponse defines model for CreateTrainingDateResponse. +type CreateTrainingDateResponse = TrainingDate + // CreateTrainingRequest defines model for CreateTrainingRequest. type CreateTrainingRequest = NewTraining // CreateTrainingResponse defines model for CreateTrainingResponse. type CreateTrainingResponse = Training +// GetTrainingDateResponse defines model for GetTrainingDateResponse. +type GetTrainingDateResponse = TrainingDate + // GetTrainingResponse defines model for GetTrainingResponse. type GetTrainingResponse = Training +// ListTrainingDatesResponse defines model for ListTrainingDatesResponse. +type ListTrainingDatesResponse = []TrainingDate + // ListTrainingsResponse defines model for ListTrainingsResponse. type ListTrainingsResponse = []Training @@ -47,6 +60,18 @@ type NewTraining struct { Pricing []TrainingPrice `json:"pricing"` } +// NewTrainingDate defines model for NewTrainingDate. +type NewTrainingDate struct { + Address string `json:"address"` + Capacity int8 `json:"capacity"` + Date time.Time `json:"date"` + Days int8 `json:"days"` + IsOnline bool `json:"isOnline"` + Location string `json:"location"` + Price TrainingDatePrice `json:"price"` + StartTime string `json:"startTime"` +} + // ProblemDetails Schema that carries the details of an error in an HTTP response. See https://datatracker.ietf.org/doc/html/rfc7807 for more information. type ProblemDetails struct { // Detail A human-readable explanation specific to this occurrence of the problem. @@ -74,6 +99,28 @@ type Training struct { Pricing []TrainingPrice `json:"pricing"` } +// TrainingDate defines model for TrainingDate. +type TrainingDate struct { + Address string `json:"address"` + Capacity int8 `json:"capacity"` + Date time.Time `json:"date"` + Days int8 `json:"days"` + Id TrainingDateID `json:"id"` + IsOnline bool `json:"isOnline"` + Location string `json:"location"` + Price TrainingDatePrice `json:"price"` + StartTime string `json:"startTime"` +} + +// TrainingDateID defines model for TrainingDateID. +type TrainingDateID = training.TrainingDateID + +// TrainingDatePrice defines model for TrainingDatePrice. +type TrainingDatePrice struct { + Amount decimal.Decimal `json:"amount"` + Currency currency.Currency `json:"currency"` +} + // TrainingID defines model for TrainingID. type TrainingID = training.TrainingID @@ -87,6 +134,12 @@ type TrainingPrice struct { // TrainingPriceType defines model for TrainingPrice.Type. type TrainingPriceType string +// UpdateTrainingDateRequest defines model for UpdateTrainingDateRequest. +type UpdateTrainingDateRequest = NewTrainingDate + +// UpdateTrainingDateResponse defines model for UpdateTrainingDateResponse. +type UpdateTrainingDateResponse = TrainingDate + // UpdateTrainingRequest defines model for UpdateTrainingRequest. type UpdateTrainingRequest = NewTraining @@ -108,6 +161,12 @@ type CreateTrainingJSONRequestBody = CreateTrainingRequest // UpdateTrainingJSONRequestBody defines body for UpdateTraining for application/json ContentType. type UpdateTrainingJSONRequestBody = UpdateTrainingRequest +// CreateTrainingDateJSONRequestBody defines body for CreateTrainingDate for application/json ContentType. +type CreateTrainingDateJSONRequestBody = CreateTrainingDateRequest + +// UpdateTrainingDateJSONRequestBody defines body for UpdateTrainingDate for application/json ContentType. +type UpdateTrainingDateJSONRequestBody = UpdateTrainingDateRequest + // ServerInterface represents all server handlers. type ServerInterface interface { // List all trainings @@ -125,6 +184,21 @@ type ServerInterface interface { // Update a training by ID // (PUT /v1/trainings/{trainingID}) UpdateTraining(c *fiber.Ctx, trainingID TrainingID) error + // List all dates of a training + // (GET /v1/trainings/{trainingID}/dates) + ListTrainingDates(c *fiber.Ctx, trainingID TrainingID) error + // Create a new training date + // (POST /v1/trainings/{trainingID}/dates) + CreateTrainingDate(c *fiber.Ctx, trainingID TrainingID) error + // Delete a training date by ID + // (DELETE /v1/trainings/{trainingID}/dates/{trainingDateID}) + DeleteTrainingDate(c *fiber.Ctx, trainingID TrainingID, trainingDateID TrainingDateID) error + // Get a training date by ID + // (GET /v1/trainings/{trainingID}/dates/{trainingDateID}) + GetTrainingDate(c *fiber.Ctx, trainingID TrainingID, trainingDateID TrainingDateID) error + // Update a training date by ID + // (PUT /v1/trainings/{trainingID}/dates/{trainingDateID}) + UpdateTrainingDate(c *fiber.Ctx, trainingID TrainingID, trainingDateID TrainingDateID) error } // ServerInterfaceWrapper converts contexts to parameters. @@ -194,6 +268,110 @@ func (siw *ServerInterfaceWrapper) UpdateTraining(c *fiber.Ctx) error { return siw.Handler.UpdateTraining(c, trainingID) } +// ListTrainingDates operation middleware +func (siw *ServerInterfaceWrapper) ListTrainingDates(c *fiber.Ctx) error { + + var err error + + // ------------- Path parameter "trainingID" ------------- + var trainingID TrainingID + + err = runtime.BindStyledParameterWithOptions("simple", "trainingID", c.Params("trainingID"), &trainingID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter trainingID: %w", err).Error()) + } + + return siw.Handler.ListTrainingDates(c, trainingID) +} + +// CreateTrainingDate operation middleware +func (siw *ServerInterfaceWrapper) CreateTrainingDate(c *fiber.Ctx) error { + + var err error + + // ------------- Path parameter "trainingID" ------------- + var trainingID TrainingID + + err = runtime.BindStyledParameterWithOptions("simple", "trainingID", c.Params("trainingID"), &trainingID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter trainingID: %w", err).Error()) + } + + return siw.Handler.CreateTrainingDate(c, trainingID) +} + +// DeleteTrainingDate operation middleware +func (siw *ServerInterfaceWrapper) DeleteTrainingDate(c *fiber.Ctx) error { + + var err error + + // ------------- Path parameter "trainingID" ------------- + var trainingID TrainingID + + err = runtime.BindStyledParameterWithOptions("simple", "trainingID", c.Params("trainingID"), &trainingID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter trainingID: %w", err).Error()) + } + + // ------------- Path parameter "trainingDateID" ------------- + var trainingDateID TrainingDateID + + err = runtime.BindStyledParameterWithOptions("simple", "trainingDateID", c.Params("trainingDateID"), &trainingDateID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter trainingDateID: %w", err).Error()) + } + + return siw.Handler.DeleteTrainingDate(c, trainingID, trainingDateID) +} + +// GetTrainingDate operation middleware +func (siw *ServerInterfaceWrapper) GetTrainingDate(c *fiber.Ctx) error { + + var err error + + // ------------- Path parameter "trainingID" ------------- + var trainingID TrainingID + + err = runtime.BindStyledParameterWithOptions("simple", "trainingID", c.Params("trainingID"), &trainingID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter trainingID: %w", err).Error()) + } + + // ------------- Path parameter "trainingDateID" ------------- + var trainingDateID TrainingDateID + + err = runtime.BindStyledParameterWithOptions("simple", "trainingDateID", c.Params("trainingDateID"), &trainingDateID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter trainingDateID: %w", err).Error()) + } + + return siw.Handler.GetTrainingDate(c, trainingID, trainingDateID) +} + +// UpdateTrainingDate operation middleware +func (siw *ServerInterfaceWrapper) UpdateTrainingDate(c *fiber.Ctx) error { + + var err error + + // ------------- Path parameter "trainingID" ------------- + var trainingID TrainingID + + err = runtime.BindStyledParameterWithOptions("simple", "trainingID", c.Params("trainingID"), &trainingID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter trainingID: %w", err).Error()) + } + + // ------------- Path parameter "trainingDateID" ------------- + var trainingDateID TrainingDateID + + err = runtime.BindStyledParameterWithOptions("simple", "trainingDateID", c.Params("trainingDateID"), &trainingDateID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter trainingDateID: %w", err).Error()) + } + + return siw.Handler.UpdateTrainingDate(c, trainingID, trainingDateID) +} + // FiberServerOptions provides options for the Fiber server. type FiberServerOptions struct { BaseURL string @@ -225,6 +403,16 @@ func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, option router.Put(options.BaseURL+"/v1/trainings/:trainingID", wrapper.UpdateTraining) + router.Get(options.BaseURL+"/v1/trainings/:trainingID/dates", wrapper.ListTrainingDates) + + router.Post(options.BaseURL+"/v1/trainings/:trainingID/dates", wrapper.CreateTrainingDate) + + router.Delete(options.BaseURL+"/v1/trainings/:trainingID/dates/:trainingDateID", wrapper.DeleteTrainingDate) + + router.Get(options.BaseURL+"/v1/trainings/:trainingID/dates/:trainingDateID", wrapper.GetTrainingDate) + + router.Put(options.BaseURL+"/v1/trainings/:trainingID/dates/:trainingDateID", wrapper.UpdateTrainingDate) + } type InternalErrorApplicationProblemPlusJSONResponse ProblemDetails @@ -427,6 +615,216 @@ func (response UpdateTraining500ApplicationProblemPlusJSONResponse) VisitUpdateT return ctx.JSON(&response) } +type ListTrainingDatesRequestObject struct { + TrainingID TrainingID `json:"trainingID"` +} + +type ListTrainingDatesResponseObject interface { + VisitListTrainingDatesResponse(ctx *fiber.Ctx) error +} + +type ListTrainingDates200JSONResponse ListTrainingDatesResponse + +func (response ListTrainingDates200JSONResponse) VisitListTrainingDatesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type ListTrainingDates404ApplicationProblemPlusJSONResponse struct { + NotFoundErrorApplicationProblemPlusJSONResponse +} + +func (response ListTrainingDates404ApplicationProblemPlusJSONResponse) VisitListTrainingDatesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(404) + + return ctx.JSON(&response) +} + +type ListTrainingDates500ApplicationProblemPlusJSONResponse struct { + InternalErrorApplicationProblemPlusJSONResponse +} + +func (response ListTrainingDates500ApplicationProblemPlusJSONResponse) VisitListTrainingDatesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type CreateTrainingDateRequestObject struct { + TrainingID TrainingID `json:"trainingID"` + Body *CreateTrainingDateJSONRequestBody +} + +type CreateTrainingDateResponseObject interface { + VisitCreateTrainingDateResponse(ctx *fiber.Ctx) error +} + +type CreateTrainingDate201JSONResponse CreateTrainingDateResponse + +func (response CreateTrainingDate201JSONResponse) VisitCreateTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(201) + + return ctx.JSON(&response) +} + +type CreateTrainingDate400ApplicationProblemPlusJSONResponse struct { + InvalidInputErrorApplicationProblemPlusJSONResponse +} + +func (response CreateTrainingDate400ApplicationProblemPlusJSONResponse) VisitCreateTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(400) + + return ctx.JSON(&response) +} + +type CreateTrainingDate500ApplicationProblemPlusJSONResponse struct { + InternalErrorApplicationProblemPlusJSONResponse +} + +func (response CreateTrainingDate500ApplicationProblemPlusJSONResponse) VisitCreateTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type DeleteTrainingDateRequestObject struct { + TrainingID TrainingID `json:"trainingID"` + TrainingDateID TrainingDateID `json:"trainingDateID"` +} + +type DeleteTrainingDateResponseObject interface { + VisitDeleteTrainingDateResponse(ctx *fiber.Ctx) error +} + +type DeleteTrainingDate204Response struct { +} + +func (response DeleteTrainingDate204Response) VisitDeleteTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Status(204) + return nil +} + +type DeleteTrainingDate404ApplicationProblemPlusJSONResponse struct { + NotFoundErrorApplicationProblemPlusJSONResponse +} + +func (response DeleteTrainingDate404ApplicationProblemPlusJSONResponse) VisitDeleteTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(404) + + return ctx.JSON(&response) +} + +type DeleteTrainingDate500ApplicationProblemPlusJSONResponse struct { + InternalErrorApplicationProblemPlusJSONResponse +} + +func (response DeleteTrainingDate500ApplicationProblemPlusJSONResponse) VisitDeleteTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type GetTrainingDateRequestObject struct { + TrainingID TrainingID `json:"trainingID"` + TrainingDateID TrainingDateID `json:"trainingDateID"` +} + +type GetTrainingDateResponseObject interface { + VisitGetTrainingDateResponse(ctx *fiber.Ctx) error +} + +type GetTrainingDate200JSONResponse GetTrainingDateResponse + +func (response GetTrainingDate200JSONResponse) VisitGetTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type GetTrainingDate404ApplicationProblemPlusJSONResponse struct { + NotFoundErrorApplicationProblemPlusJSONResponse +} + +func (response GetTrainingDate404ApplicationProblemPlusJSONResponse) VisitGetTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(404) + + return ctx.JSON(&response) +} + +type GetTrainingDate500ApplicationProblemPlusJSONResponse struct { + InternalErrorApplicationProblemPlusJSONResponse +} + +func (response GetTrainingDate500ApplicationProblemPlusJSONResponse) VisitGetTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type UpdateTrainingDateRequestObject struct { + TrainingID TrainingID `json:"trainingID"` + TrainingDateID TrainingDateID `json:"trainingDateID"` + Body *UpdateTrainingDateJSONRequestBody +} + +type UpdateTrainingDateResponseObject interface { + VisitUpdateTrainingDateResponse(ctx *fiber.Ctx) error +} + +type UpdateTrainingDate200JSONResponse UpdateTrainingDateResponse + +func (response UpdateTrainingDate200JSONResponse) VisitUpdateTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type UpdateTrainingDate400ApplicationProblemPlusJSONResponse struct { + InvalidInputErrorApplicationProblemPlusJSONResponse +} + +func (response UpdateTrainingDate400ApplicationProblemPlusJSONResponse) VisitUpdateTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(400) + + return ctx.JSON(&response) +} + +type UpdateTrainingDate404ApplicationProblemPlusJSONResponse struct { + NotFoundErrorApplicationProblemPlusJSONResponse +} + +func (response UpdateTrainingDate404ApplicationProblemPlusJSONResponse) VisitUpdateTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(404) + + return ctx.JSON(&response) +} + +type UpdateTrainingDate500ApplicationProblemPlusJSONResponse struct { + InternalErrorApplicationProblemPlusJSONResponse +} + +func (response UpdateTrainingDate500ApplicationProblemPlusJSONResponse) VisitUpdateTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + // StrictServerInterface represents all server handlers. type StrictServerInterface interface { // List all trainings @@ -444,6 +842,21 @@ type StrictServerInterface interface { // Update a training by ID // (PUT /v1/trainings/{trainingID}) UpdateTraining(ctx context.Context, request UpdateTrainingRequestObject) (UpdateTrainingResponseObject, error) + // List all dates of a training + // (GET /v1/trainings/{trainingID}/dates) + ListTrainingDates(ctx context.Context, request ListTrainingDatesRequestObject) (ListTrainingDatesResponseObject, error) + // Create a new training date + // (POST /v1/trainings/{trainingID}/dates) + CreateTrainingDate(ctx context.Context, request CreateTrainingDateRequestObject) (CreateTrainingDateResponseObject, error) + // Delete a training date by ID + // (DELETE /v1/trainings/{trainingID}/dates/{trainingDateID}) + DeleteTrainingDate(ctx context.Context, request DeleteTrainingDateRequestObject) (DeleteTrainingDateResponseObject, error) + // Get a training date by ID + // (GET /v1/trainings/{trainingID}/dates/{trainingDateID}) + GetTrainingDate(ctx context.Context, request GetTrainingDateRequestObject) (GetTrainingDateResponseObject, error) + // Update a training date by ID + // (PUT /v1/trainings/{trainingID}/dates/{trainingDateID}) + UpdateTrainingDate(ctx context.Context, request UpdateTrainingDateRequestObject) (UpdateTrainingDateResponseObject, error) } type StrictHandlerFunc func(ctx *fiber.Ctx, args interface{}) (interface{}, error) @@ -602,32 +1015,188 @@ func (sh *strictHandler) UpdateTraining(ctx *fiber.Ctx, trainingID TrainingID) e return nil } +// ListTrainingDates operation middleware +func (sh *strictHandler) ListTrainingDates(ctx *fiber.Ctx, trainingID TrainingID) error { + var request ListTrainingDatesRequestObject + + request.TrainingID = trainingID + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ListTrainingDates(ctx.UserContext(), request.(ListTrainingDatesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ListTrainingDates") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(ListTrainingDatesResponseObject); ok { + if err := validResponse.VisitListTrainingDatesResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// CreateTrainingDate operation middleware +func (sh *strictHandler) CreateTrainingDate(ctx *fiber.Ctx, trainingID TrainingID) error { + var request CreateTrainingDateRequestObject + + request.TrainingID = trainingID + + var body CreateTrainingDateJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.CreateTrainingDate(ctx.UserContext(), request.(CreateTrainingDateRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CreateTrainingDate") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(CreateTrainingDateResponseObject); ok { + if err := validResponse.VisitCreateTrainingDateResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// DeleteTrainingDate operation middleware +func (sh *strictHandler) DeleteTrainingDate(ctx *fiber.Ctx, trainingID TrainingID, trainingDateID TrainingDateID) error { + var request DeleteTrainingDateRequestObject + + request.TrainingID = trainingID + request.TrainingDateID = trainingDateID + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.DeleteTrainingDate(ctx.UserContext(), request.(DeleteTrainingDateRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteTrainingDate") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(DeleteTrainingDateResponseObject); ok { + if err := validResponse.VisitDeleteTrainingDateResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// GetTrainingDate operation middleware +func (sh *strictHandler) GetTrainingDate(ctx *fiber.Ctx, trainingID TrainingID, trainingDateID TrainingDateID) error { + var request GetTrainingDateRequestObject + + request.TrainingID = trainingID + request.TrainingDateID = trainingDateID + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.GetTrainingDate(ctx.UserContext(), request.(GetTrainingDateRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetTrainingDate") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(GetTrainingDateResponseObject); ok { + if err := validResponse.VisitGetTrainingDateResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// UpdateTrainingDate operation middleware +func (sh *strictHandler) UpdateTrainingDate(ctx *fiber.Ctx, trainingID TrainingID, trainingDateID TrainingDateID) error { + var request UpdateTrainingDateRequestObject + + request.TrainingID = trainingID + request.TrainingDateID = trainingDateID + + var body UpdateTrainingDateJSONRequestBody + if err := ctx.BodyParser(&body); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + request.Body = &body + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.UpdateTrainingDate(ctx.UserContext(), request.(UpdateTrainingDateRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UpdateTrainingDate") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(UpdateTrainingDateResponseObject); ok { + if err := validResponse.VisitUpdateTrainingDateResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/server/api.go b/internal/server/api.go index 5853047..7754823 100644 --- a/internal/server/api.go +++ b/internal/server/api.go @@ -2,18 +2,21 @@ package server import ( "context" + "time" "github.com/gofiber/fiber/v2" "gitlab.mareshq.com/hq/yggdrasil/pkg/training" ) type APIHandlers struct { - trainingRepository training.TrainingRepository + trainingRepository training.TrainingRepository + trainingDateRepository training.TrainingDateRepository } -func NewAPIHandlers(trainingRepository training.TrainingRepository) *APIHandlers { +func NewAPIHandlers(trainingRepository training.TrainingRepository, trainingDateRepository training.TrainingDateRepository) *APIHandlers { return &APIHandlers{ - trainingRepository: trainingRepository, + trainingRepository: trainingRepository, + trainingDateRepository: trainingDateRepository, } } @@ -183,3 +186,197 @@ func (h *APIHandlers) UpdateTraining(ctx context.Context, req UpdateTrainingRequ Description: t.Description, }, nil } + +const trainingDateStartTimeFormat = "15:04" + +func (h *APIHandlers) ListTrainingDates(ctx context.Context, req ListTrainingDatesRequestObject) (ListTrainingDatesResponseObject, error) { + trainingDates, err := h.trainingDateRepository.FindAllByTrainingID(req.TrainingID) + if err != nil { + return ListTrainingDates500ApplicationProblemPlusJSONResponse{ + InternalErrorApplicationProblemPlusJSONResponse: InternalErrorApplicationProblemPlusJSONResponse{ + Status: fiber.StatusInternalServerError, + Title: "Internal Server Error: Failed to list training dates", + Detail: err.Error(), + }}, nil + } + + data := make([]TrainingDate, len(trainingDates)) + for idx, td := range trainingDates { + data[idx] = TrainingDate{ + Id: td.ID, + Date: td.Date, + StartTime: td.StartTime.Format(trainingDateStartTimeFormat), + Days: td.Days, + IsOnline: td.IsOnline, + Location: td.Location, + Address: td.Address, + Capacity: td.Capacity, + Price: TrainingDatePrice{ + Amount: td.Price.Amount, + Currency: td.Price.Currency, + }, + } + } + + return ListTrainingDates200JSONResponse(data), nil +} + +func (h *APIHandlers) CreateTrainingDate(ctx context.Context, req CreateTrainingDateRequestObject) (CreateTrainingDateResponseObject, error) { + price := training.TrainingDatePrice{ + Amount: req.Body.Price.Amount, + Currency: req.Body.Price.Currency, + } + + startTime, err := time.Parse(time.RFC3339, "2000-01-01T"+req.Body.StartTime) + if err != nil { + return CreateTrainingDate400ApplicationProblemPlusJSONResponse{ + InvalidInputErrorApplicationProblemPlusJSONResponse: InvalidInputErrorApplicationProblemPlusJSONResponse{ + Status: fiber.StatusBadRequest, + Title: "Invalid Input: Invalid startTime", + Detail: err.Error(), + }}, nil + } + + td := training.TrainingDate{ + Date: req.Body.Date, + StartTime: startTime, + Days: req.Body.Days, + IsOnline: req.Body.IsOnline, + Location: req.Body.Location, + Address: req.Body.Address, + Capacity: req.Body.Capacity, + Price: price, + } + + err = h.trainingDateRepository.Create(req.TrainingID, &td) + if err != nil { + return CreateTrainingDate500ApplicationProblemPlusJSONResponse{ + InternalErrorApplicationProblemPlusJSONResponse: InternalErrorApplicationProblemPlusJSONResponse{ + Status: fiber.StatusInternalServerError, + Title: "Internal Server Error: Failed to create training date", + Detail: err.Error(), + }}, nil + } + + return CreateTrainingDate201JSONResponse{ + Id: td.ID, + Date: td.Date, + StartTime: td.StartTime.Format(trainingDateStartTimeFormat), + Days: td.Days, + IsOnline: td.IsOnline, + Location: td.Location, + Address: td.Address, + Capacity: td.Capacity, + Price: TrainingDatePrice{ + Amount: td.Price.Amount, + Currency: td.Price.Currency, + }, + }, nil +} + +func (h *APIHandlers) DeleteTrainingDate(ctx context.Context, req DeleteTrainingDateRequestObject) (DeleteTrainingDateResponseObject, error) { + err := h.trainingDateRepository.Delete(req.TrainingDateID) + if err == training.ErrTrainingDateNotFound { + return DeleteTrainingDate404ApplicationProblemPlusJSONResponse{ + NotFoundErrorApplicationProblemPlusJSONResponse: NotFoundErrorApplicationProblemPlusJSONResponse{ + Status: fiber.StatusNotFound, + Title: "Not Found: Training date not found", + }}, nil + } else if err != nil { + return DeleteTrainingDate500ApplicationProblemPlusJSONResponse{ + InternalErrorApplicationProblemPlusJSONResponse: InternalErrorApplicationProblemPlusJSONResponse{ + Status: fiber.StatusInternalServerError, + Title: "Internal Server Error: Failed to delete training date", + Detail: err.Error(), + }}, nil + } + + return DeleteTrainingDate204Response{}, nil +} + +func (h *APIHandlers) GetTrainingDate(ctx context.Context, req GetTrainingDateRequestObject) (GetTrainingDateResponseObject, error) { + td, err := h.trainingDateRepository.FindByID(req.TrainingDateID) + if err == training.ErrTrainingDateNotFound { + return GetTrainingDate404ApplicationProblemPlusJSONResponse{ + NotFoundErrorApplicationProblemPlusJSONResponse: NotFoundErrorApplicationProblemPlusJSONResponse{ + Status: fiber.StatusNotFound, + Title: "Not Found: Training date not found", + }}, nil + } else if err != nil { + return GetTrainingDate500ApplicationProblemPlusJSONResponse{ + InternalErrorApplicationProblemPlusJSONResponse: InternalErrorApplicationProblemPlusJSONResponse{ + Status: fiber.StatusInternalServerError, + Title: "Internal Server Error: Failed to get training date", + Detail: err.Error(), + }}, nil + } + + return GetTrainingDate200JSONResponse{ + Id: td.ID, + Date: td.Date, + StartTime: td.StartTime.Format(trainingDateStartTimeFormat), + Days: td.Days, + IsOnline: td.IsOnline, + Location: td.Location, + Address: td.Address, + Capacity: td.Capacity, + Price: TrainingDatePrice{ + Amount: td.Price.Amount, + Currency: td.Price.Currency, + }, + }, nil +} + +func (h *APIHandlers) UpdateTrainingDate(ctx context.Context, req UpdateTrainingDateRequestObject) (UpdateTrainingDateResponseObject, error) { + price := training.TrainingDatePrice{ + Amount: req.Body.Price.Amount, + Currency: req.Body.Price.Currency, + } + + startTime, err := time.Parse(time.RFC3339, "2000-01-01T"+req.Body.StartTime) + if err != nil { + return UpdateTrainingDate400ApplicationProblemPlusJSONResponse{ + InvalidInputErrorApplicationProblemPlusJSONResponse: InvalidInputErrorApplicationProblemPlusJSONResponse{ + Status: fiber.StatusBadRequest, + Title: "Invalid Input: Invalid startTime", + Detail: err.Error(), + }}, nil + } + + td := training.TrainingDate{ + ID: req.TrainingDateID, + Date: req.Body.Date, + StartTime: startTime, + Days: req.Body.Days, + IsOnline: req.Body.IsOnline, + Location: req.Body.Location, + Address: req.Body.Address, + Capacity: req.Body.Capacity, + Price: price, + } + + err = h.trainingDateRepository.Update(&td) + if err != nil { + return UpdateTrainingDate500ApplicationProblemPlusJSONResponse{ + InternalErrorApplicationProblemPlusJSONResponse: InternalErrorApplicationProblemPlusJSONResponse{ + Status: fiber.StatusInternalServerError, + Title: "Internal Server Error: Failed to update training date", + Detail: err.Error(), + }}, nil + } + + return UpdateTrainingDate200JSONResponse{ + Id: td.ID, + Date: td.Date, + StartTime: td.StartTime.Format(trainingDateStartTimeFormat), + Days: td.Days, + IsOnline: td.IsOnline, + Location: td.Location, + Address: td.Address, + Capacity: td.Capacity, + Price: TrainingDatePrice{ + Amount: td.Price.Amount, + Currency: td.Price.Currency, + }, + }, nil +}