diff --git a/.redocly.lint-ignore.yaml b/.redocly.lint-ignore.yaml new file mode 100644 index 0000000..d7d8b44 --- /dev/null +++ b/.redocly.lint-ignore.yaml @@ -0,0 +1,34 @@ +# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API. +# See https://redoc.ly/docs/cli/ for more information. +api/v1/openapi.yaml: + info-license-url: + - '#/info/license/url' + no-server-example.com: + - '#/servers/0/url' + operation-4xx-response: + - '#/paths/~1trainings/get/responses' + - '#/paths/~1trainings~1{trainingID}~1dates/get/responses' + - >- + #/paths/~1trainings~1{trainingID}~1dates~1{dateID}~1attendees/get/responses + - '#/paths/~1trainings~1{trainingID}~1dates~1{dateID}~1feedback/get/responses' + - '#/paths/~1trainings~1{trainingID}~1feedback/get/responses' + - '#/paths/~1trainings~1upcoming-dates/get/responses' + security-defined: + - '#/paths/~1trainings/get' + - '#/paths/~1trainings/post' + - '#/paths/~1trainings~1{trainingID}/get' + - '#/paths/~1trainings~1{trainingID}/put' + - '#/paths/~1trainings~1{trainingID}/delete' + - '#/paths/~1trainings~1{trainingID}~1dates/get' + - '#/paths/~1trainings~1{trainingID}~1dates/post' + - '#/paths/~1trainings~1{trainingID}~1dates~1{dateID}/put' + - '#/paths/~1trainings~1{trainingID}~1dates~1{dateID}/delete' + - '#/paths/~1trainings~1{trainingID}~1dates~1{dateID}~1attendees/get' + - '#/paths/~1trainings~1{trainingID}~1dates~1{dateID}~1attendees/post' + - >- + #/paths/~1trainings~1{trainingID}~1dates~1{dateID}~1attendees~1{attendeeID}/delete + - >- + #/paths/~1trainings~1{trainingID}~1dates~1{dateID}~1attendees~1{attendeeID}~1feedback/post + - '#/paths/~1trainings~1{trainingID}~1dates~1{dateID}~1feedback/get' + - '#/paths/~1trainings~1{trainingID}~1feedback/get' + - '#/paths/~1trainings~1upcoming-dates/get' diff --git a/Makefile b/Makefile index 3aa606f..3e3ba98 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,21 @@ .PHONY: generate generate: - oapi-codegen -config ./oapi-codegen.yaml ./api/v1/openapi.yaml + go generate . + +POSTGRES_URL="postgres://official:backoffice@localhost:5432/backoffice_dev?sslmode=disable" + +.PHONY: local-migrate-up +local-migrate-up: + migrate -database ${POSTGRES_URL} -path ./db/migrations up + +.PHONY: local-migrate-force +local-migrate-force: + migrate -database ${POSTGRES_URL} -path ./db/migrations force + +.PHONY: local-migrate-down +local-migrate-down: + migrate -database ${POSTGRES_URL} -path ./db/migrations down + +.PHONY: local-migrate-drop +local-migrate-drop: + migrate -database ${POSTGRES_URL} -path ./db/migrations drop -f diff --git a/api/v1/openapi.yaml b/api/v1/openapi.yaml index 226614e..cf30217 100644 --- a/api/v1/openapi.yaml +++ b/api/v1/openapi.yaml @@ -1,6 +1,7 @@ openapi: "3.1.0" info: version: 1.3.0 + x-go-package: "github.com/oapi-codegen/runtime" title: Backoffice API license: name: Proprietary @@ -8,6 +9,8 @@ info: name: Vojtěch Mareš email: iam@vojtechmares.com url: https://www.vojtechmares.com + x-oapi-codegen-middlewares: + - Middleware servers: - url: http://localhost:8080/v1 description: Local development server @@ -402,12 +405,18 @@ paths: schema: type: string format: uuid + x-go-type: training.ID + x-go-type-import: + path: gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training - name: dateID in: path required: true schema: type: string format: uuid + x-go-type: training.DateID + x-go-type-import: + path: gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training requestBody: required: true content: @@ -484,139 +493,6 @@ paths: schema: $ref: "#/components/schemas/ProblemDetails" - /trainings/{trainingID}/dates/{dateID}/attendees/{attendeeID}/feedback: - post: - summary: Submit feedback for an attendee of a date of a training - operationId: createTrainingDateAttendeeFeedback - tags: - - training attendee feedback - parameters: - - name: trainingID - in: path - required: true - schema: - type: string - format: uuid - x-go-type: training.ID - x-go-type-import: - path: gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training - - name: dateID - in: path - required: true - schema: - type: string - format: uuid - x-go-type: training.DateID - x-go-type-import: - path: gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training - - name: attendeeID - in: path - required: true - schema: - type: string - format: uuid - x-go-type: training.AttendeeID - x-go-type-import: - path: gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/NewTrainingFeedback" - responses: - "201": - description: Feedback created - content: - application/json: - schema: - $ref: "#/components/schemas/TrainingFeedback" - "409": - description: Feedback already submitted - content: - application/problem+json: - schema: - $ref: "#/components/schemas/ProblemDetails" - "500": - description: Internal error - content: - application/problem+json: - schema: - $ref: "#/components/schemas/ProblemDetails" - - /trainings/{trainingID}/dates/{dateID}/feedback: - get: - summary: List all feedback of a date of a training - operationId: listTrainingDateFeedback - tags: - - training attendee feedback - parameters: - - name: trainingID - in: path - required: true - schema: - type: string - format: uuid - x-go-type: training.ID - x-go-type-import: - path: gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training - - name: dateID - in: path - required: true - schema: - type: string - format: uuid - x-go-type: training.DateID - x-go-type-import: - path: gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training - responses: - "200": - description: A list of feedback - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/TrainingFeedback" - "500": - description: Internal error - content: - application/problem+json: - schema: - $ref: "#/components/schemas/ProblemDetails" - - /trainings/{trainingID}/feedback: - get: - summary: List all feedback of a training - operationId: listTrainingFeedback - tags: - - training attendee feedback - parameters: - - name: trainingID - in: path - required: true - schema: - type: string - format: uuid - x-go-type: training.ID - x-go-type-import: - path: gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training - responses: - "200": - description: A list of feedback - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/TrainingFeedback" - "500": - description: Internal error - content: - application/problem+json: - schema: - $ref: "#/components/schemas/ProblemDetails" - /trainings/upcoming-dates: get: summary: List all upcoming dates of all trainings @@ -670,22 +546,14 @@ components: properties: name: type: string - duration: + days: type: integer format: int32 price: - type: object - properties: - open: - $ref: "#/components/schemas/Price" - corporate: - $ref: "#/components/schemas/Price" - length: - type: integer - format: int32 + $ref: "#/components/schemas/TrainingPrice" required: - name - - duration + - days - price Training: allOf: @@ -701,6 +569,31 @@ components: required: - id + TrainingPrice: + types: array + items: + type: object + required: + - type + - price + properties: + type: + type: string + enum: [open, corporate] + price: + type: object + required: + - currency + - amount + properties: + currency: + type: string + enum: [CZK, EUR, USD] + amount: + type: number + format: float64 + minimum: 0 + NewTrainingDate: type: object properties: @@ -767,40 +660,6 @@ components: required: - id - NewTrainingFeedback: - type: object - properties: - rating: - type: integer - format: int32 - minimum: 0 - maximum: 10 - comment: - type: string - anonymous: - type: boolean - default: false - isSharingAllowed: - type: boolean - default: false - required: - - rating - - comment - - TrainingFeedback: - allOf: - - $ref: "#/components/schemas/NewTrainingFeedback" - - type: object - properties: - id: - type: string - format: uuid - x-go-type: training.FeedbackID - x-go-type-import: - path: gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training - required: - - id - Price: type: object properties: diff --git a/cmd/migrate.go b/cmd/migrate.go new file mode 100644 index 0000000..ffd2a73 --- /dev/null +++ b/cmd/migrate.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +var migrateCmd = &cobra.Command{ + Use: "migrate", + Short: "Migrate database schema up/down", + Long: `All software has versions. This is backoffice backend's`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Migrate database schema up/down") + }, +} diff --git a/cmd/migrate_up.go b/cmd/migrate_up.go new file mode 100644 index 0000000..c042707 --- /dev/null +++ b/cmd/migrate_up.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func init() { + migrateCmd.AddCommand(migrateUpCmd) +} + +var migrateUpCmd = &cobra.Command{ + Use: "up", + Short: "Migrate database schema up", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + // TODO: Implement this + + // TODO: get database URL + + // TODO: run migrations + + // TODO: handle errors + + // TODO: gracefully shutdown + }, +} diff --git a/cmd/server.go b/cmd/server.go new file mode 100644 index 0000000..58610ba --- /dev/null +++ b/cmd/server.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "gitlab.mareshq.com/hq/backoffice/backoffice-api/internal/server" +) + +func init() { + rootCmd.AddCommand(serverCmd) +} + +var serverCmd = &cobra.Command{ + Use: "server", + Short: "Starts backoffice backend API server", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + srv := server.NewServer() + srv.Run() + }, +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..ac6a402 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "gitlab.mareshq.com/hq/backoffice/backoffice-api/internal/version" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of backoffice backend", + Long: `All software has versions. This is backoffice backend's`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Version: %s (commit: %s)\n", version.Version, version.Commit) + }, +} diff --git a/db/migrations/20240329172935_init.down.sql b/db/migrations/20240329172935_init.down.sql new file mode 100644 index 0000000..a91b8c3 --- /dev/null +++ b/db/migrations/20240329172935_init.down.sql @@ -0,0 +1,8 @@ +BEGIN; + +DROP TABLE IF EXISTS training.attendees; +DROP TABLE IF EXISTS training.dates; +DROP TABLE IF EXISTS training.trainings; +DROP SCHEMA IF EXISTS training; + +COMMIT; diff --git a/db/migrations/20240329172935_init.up.sql b/db/migrations/20240329172935_init.up.sql new file mode 100644 index 0000000..9ad7be3 --- /dev/null +++ b/db/migrations/20240329172935_init.up.sql @@ -0,0 +1,62 @@ +BEGIN; + +CREATE SCHEMA IF NOT EXISTS training; + +CREATE TABLE IF NOT EXISTS training.trainings ( + id UUID PRIMARY KEY, + name varchar(255) NOT NULL, + description text NOT NULL, + days smallint NOT NULL, + price decimal NOT NULL +); + +CREATE TABLE IF NOT EXISTS training.prices ( + training_id UUID REFERENCES training.trainings(id), + amount NUMERIC(6, 4) NOT NULL, + currency VARCHAR(3) NOT NULL, + CHECK (currency IN ('USD', 'EUR', 'CZK')) + type VARCHAR(10) NOT NULL, + CHECK (type IN ('OPEN', 'CORPORATE', 'STUDENT', 'GOVERNMENT')) + PRIMARY KEY (training_id, currency, type) -- composite primary key +) + +CREATE TABLE IF NOT EXISTS training.dates ( + id UUID PRIMARY KEY, + training_id UUID NOT NULL, + date DATE NOT NULL, + start_time TIME NOT NULL, + days SMALLINT NOT NULL, + is_online BOOLEAN NOT NULL, + location VARCHAR(255) NOT NULL, + address VARCHAR(255) NOT NULL, + capacity SMALLINT NOT NULL, + amount NUMERIC(6, 4) NOT NULL, + currency VARCHAR(3) NOT NULL, + CHECK (currency IN ('USD', 'EUR', 'CZK')) + FOREIGN KEY (training_id) REFERENCES training.trainings(id) +); + +CREATE TABLE IF NOT EXISTS training.attendees ( + id UUID PRIMARY KEY, + date_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + company VARCHAR(255) NOT NULL, + role VARCHAR(255) NOT NULL, + is_student BOOLEAN NOT NULL, + has_attended BOOLEAN NOT NULL, + has_paid BOOLEAN NOT NULL, + FOREIGN KEY (date_id) REFERENCES training.dates(id) +); + +CREATE TABLE IF NOT EXISTS training.feedback ( + id UUID PRIMARY KEY, + attendee_id UUID NOT NULL, + rating SMALLINT NOT NULL, + comment TEXT NOT NULL, + is_anonymous BOOLEAN NOT NULL, + is_sharing_allowed BOOLEAN NOT NULL, + FOREIGN KEY (attendee_id) REFERENCES training.attendees(id) +); + +COMMIT; diff --git a/go.mod b/go.mod index 020b6cd..d31a9cc 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,58 @@ module gitlab.mareshq.com/hq/backoffice/backoffice-api go 1.22.0 + +require ( + github.com/deepmap/oapi-codegen/v2 v2.1.0 + github.com/getkin/kin-openapi v0.122.0 + github.com/gofiber/contrib/fiberzap/v2 v2.1.3 + github.com/gofiber/contrib/swagger v1.1.2 + github.com/gofiber/fiber/v2 v2.52.4 + github.com/google/uuid v1.5.0 + github.com/oapi-codegen/fiber-middleware v1.0.1 + github.com/oapi-codegen/runtime v1.1.1 + github.com/spf13/cobra v1.8.0 + go.uber.org/zap v1.27.0 +) + +require ( + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/go-openapi/analysis v0.21.4 // indirect + github.com/go-openapi/errors v0.20.4 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/loads v0.21.2 // indirect + github.com/go-openapi/runtime v0.26.2 // indirect + github.com/go-openapi/spec v0.20.11 // indirect + github.com/go-openapi/strfmt v0.21.8 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/validate v0.22.3 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/yaml v0.2.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.17.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/spf13/pflag v1.0.5 // 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 + go.mongodb.org/mongo-driver v1.13.1 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.12.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index e69de29..2c975a4 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,216 @@ +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deepmap/oapi-codegen/v2 v2.1.0 h1:I/NMVhJCtuvL9x+S2QzZKpSjGi33oDZwPRdemvOZWyQ= +github.com/deepmap/oapi-codegen/v2 v2.1.0/go.mod h1:R1wL226vc5VmCNJUvMyYr3hJMm5reyv25j952zAVXZ8= +github.com/getkin/kin-openapi v0.122.0 h1:WB9Jbl0Hp/T79/JF9xlSW5Kl9uYdk/AWD0yAd9HOM10= +github.com/getkin/kin-openapi v0.122.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= +github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= +github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M= +github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro= +github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw= +github.com/go-openapi/runtime v0.26.2 h1:elWyB9MacRzvIVgAZCBJmqTi7hBzU0hlKD4IvfX0Zl0= +github.com/go-openapi/runtime v0.26.2/go.mod h1:O034jyRZ557uJKzngbMDJXkcKJVzXJiymdSfgejrcRw= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/spec v0.20.11 h1:J/TzFDLTt4Rcl/l1PmyErvkqlJDncGvPTMnCI39I4gY= +github.com/go-openapi/spec v0.20.11/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= +github.com/go-openapi/strfmt v0.21.8 h1:VYBUoKYRLAlgKDrIxR/I0lKrztDQ0tuTDrbhLVP8Erg= +github.com/go-openapi/strfmt v0.21.8/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/validate v0.22.3 h1:KxG9mu5HBRYbecRb37KRCihvGGtND2aXziBAv0NNfyI= +github.com/go-openapi/validate v0.22.3/go.mod h1:kVxh31KbfsxU8ZyoHaDbLBWU5CnMdqBUEtadQ2G4d5M= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/gofiber/contrib/fiberzap/v2 v2.1.3 h1:znIDjHJUyhp11h5w8AaABCtEJejxjiCe47O0OC5We/g= +github.com/gofiber/contrib/fiberzap/v2 v2.1.3/go.mod h1:9KrEpG4A8g1Y4J7OS5WYq6rDyy+sXAEDSgGdCGPnH1Y= +github.com/gofiber/contrib/swagger v1.1.2 h1:qwyFR/yXEttI35JxAycC6KFuJu7rbhSsT1m4UkqGqis= +github.com/gofiber/contrib/swagger v1.1.2/go.mod h1:o6hA4kifvR3xGNtAdWKNdSUGaWYtJ0Q5AWjsIHXLEWg= +github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= +github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= +github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= +github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oapi-codegen/fiber-middleware v1.0.1 h1:DZQA+tEsqEMox4pPmc6SJXvDriiWSxc3QiA4UuMwGq4= +github.com/oapi-codegen/fiber-middleware v1.0.1/go.mod h1:UUVrHLPT4Zv7S2U2TCGLXpTx6mfMb613UI+Z6F8S5Vk= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +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= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= +github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= +go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= +go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 2c74a7e..f4caade 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -6,6 +6,7 @@ package api import ( "bytes" "compress/gzip" + "context" "encoding/base64" "fmt" "net/url" @@ -28,10 +29,9 @@ const ( // NewTraining defines model for NewTraining. type NewTraining struct { - Duration int32 `json:"duration"` - Length *int32 `json:"length,omitempty"` - Name string `json:"name"` - Price struct { + Days int32 `json:"days"` + Name string `json:"name"` + Price struct { Corporate *Price `json:"corporate,omitempty"` Open *Price `json:"open,omitempty"` } `json:"price"` @@ -89,11 +89,10 @@ type ProblemDetails struct { // Training defines model for Training. type Training struct { - Duration int32 `json:"duration"` - Id training.ID `json:"id"` - Length *int32 `json:"length,omitempty"` - Name string `json:"name"` - Price struct { + Days int32 `json:"days"` + Id training.ID `json:"id"` + Name string `json:"name"` + Price struct { Corporate *Price `json:"corporate,omitempty"` Open *Price `json:"open,omitempty"` } `json:"price"` @@ -189,7 +188,7 @@ type ServerInterface interface { ListTrainingDateAttendees(c *fiber.Ctx, trainingID training.ID, dateID training.DateID) error // Add an attendee to a date of a training // (POST /trainings/{trainingID}/dates/{dateID}/attendees) - CreateTrainingDateAttendee(c *fiber.Ctx, trainingID openapi_types.UUID, dateID openapi_types.UUID) error + CreateTrainingDateAttendee(c *fiber.Ctx, trainingID training.ID, dateID training.DateID) error // Delete an attendee of a date of a training // (DELETE /trainings/{trainingID}/dates/{dateID}/attendees/{attendeeID}) DeleteTrainingDateAttendee(c *fiber.Ctx, trainingID training.ID, dateID training.DateID, attendeeID training.AttendeeID) error @@ -419,7 +418,7 @@ func (siw *ServerInterfaceWrapper) CreateTrainingDateAttendee(c *fiber.Ctx) erro var err error // ------------- Path parameter "trainingID" ------------- - var trainingID openapi_types.UUID + var trainingID training.ID err = runtime.BindStyledParameterWithOptions("simple", "trainingID", c.Params("trainingID"), &trainingID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) if err != nil { @@ -427,7 +426,7 @@ func (siw *ServerInterfaceWrapper) CreateTrainingDateAttendee(c *fiber.Ctx) erro } // ------------- Path parameter "dateID" ------------- - var dateID openapi_types.UUID + var dateID training.DateID err = runtime.BindStyledParameterWithOptions("simple", "dateID", c.Params("dateID"), &dateID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) if err != nil { @@ -596,42 +595,1117 @@ func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, option } +type ListTrainingsRequestObject struct { +} + +type ListTrainingsResponseObject interface { + VisitListTrainingsResponse(ctx *fiber.Ctx) error +} + +type ListTrainings200JSONResponse []Training + +func (response ListTrainings200JSONResponse) VisitListTrainingsResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type ListTrainings500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response ListTrainings500ApplicationProblemPlusJSONResponse) VisitListTrainingsResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type CreateTrainingRequestObject struct { + Body *CreateTrainingJSONRequestBody +} + +type CreateTrainingResponseObject interface { + VisitCreateTrainingResponse(ctx *fiber.Ctx) error +} + +type CreateTraining201JSONResponse Training + +func (response CreateTraining201JSONResponse) VisitCreateTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(201) + + return ctx.JSON(&response) +} + +type CreateTraining400ApplicationProblemPlusJSONResponse ProblemDetails + +func (response CreateTraining400ApplicationProblemPlusJSONResponse) VisitCreateTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(400) + + return ctx.JSON(&response) +} + +type CreateTraining500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response CreateTraining500ApplicationProblemPlusJSONResponse) VisitCreateTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type ListUpcomingTrainingDatesRequestObject struct { + Params ListUpcomingTrainingDatesParams +} + +type ListUpcomingTrainingDatesResponseObject interface { + VisitListUpcomingTrainingDatesResponse(ctx *fiber.Ctx) error +} + +type ListUpcomingTrainingDates200JSONResponse []struct { + Date *TrainingDate `json:"date,omitempty"` + Training *Training `json:"training,omitempty"` +} + +func (response ListUpcomingTrainingDates200JSONResponse) VisitListUpcomingTrainingDatesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type ListUpcomingTrainingDates500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response ListUpcomingTrainingDates500ApplicationProblemPlusJSONResponse) VisitListUpcomingTrainingDatesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type DeleteTrainingRequestObject struct { + TrainingID training.ID `json:"trainingID"` +} + +type DeleteTrainingResponseObject interface { + VisitDeleteTrainingResponse(ctx *fiber.Ctx) error +} + +type DeleteTraining204Response struct { +} + +func (response DeleteTraining204Response) VisitDeleteTrainingResponse(ctx *fiber.Ctx) error { + ctx.Status(204) + return nil +} + +type DeleteTraining404ApplicationProblemPlusJSONResponse ProblemDetails + +func (response DeleteTraining404ApplicationProblemPlusJSONResponse) VisitDeleteTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(404) + + return ctx.JSON(&response) +} + +type DeleteTraining500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response DeleteTraining500ApplicationProblemPlusJSONResponse) VisitDeleteTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type GetTrainingRequestObject struct { + TrainingID training.ID `json:"trainingID"` +} + +type GetTrainingResponseObject interface { + VisitGetTrainingResponse(ctx *fiber.Ctx) error +} + +type GetTraining200JSONResponse Training + +func (response GetTraining200JSONResponse) VisitGetTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type GetTraining404ApplicationProblemPlusJSONResponse ProblemDetails + +func (response GetTraining404ApplicationProblemPlusJSONResponse) VisitGetTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(404) + + return ctx.JSON(&response) +} + +type GetTraining500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response GetTraining500ApplicationProblemPlusJSONResponse) VisitGetTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type UpdateTrainingRequestObject struct { + TrainingID training.ID `json:"trainingID"` + Body *UpdateTrainingJSONRequestBody +} + +type UpdateTrainingResponseObject interface { + VisitUpdateTrainingResponse(ctx *fiber.Ctx) error +} + +type UpdateTraining200JSONResponse Training + +func (response UpdateTraining200JSONResponse) VisitUpdateTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type UpdateTraining400ApplicationProblemPlusJSONResponse ProblemDetails + +func (response UpdateTraining400ApplicationProblemPlusJSONResponse) VisitUpdateTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(400) + + return ctx.JSON(&response) +} + +type UpdateTraining404ApplicationProblemPlusJSONResponse ProblemDetails + +func (response UpdateTraining404ApplicationProblemPlusJSONResponse) VisitUpdateTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(404) + + return ctx.JSON(&response) +} + +type UpdateTraining500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response UpdateTraining500ApplicationProblemPlusJSONResponse) VisitUpdateTrainingResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type ListTrainingDatesRequestObject struct { + TrainingID training.ID `json:"trainingID"` +} + +type ListTrainingDatesResponseObject interface { + VisitListTrainingDatesResponse(ctx *fiber.Ctx) error +} + +type ListTrainingDates200JSONResponse []TrainingDate + +func (response ListTrainingDates200JSONResponse) VisitListTrainingDatesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type ListTrainingDates500ApplicationProblemPlusJSONResponse ProblemDetails + +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 training.ID `json:"trainingID"` + Body *CreateTrainingDateJSONRequestBody +} + +type CreateTrainingDateResponseObject interface { + VisitCreateTrainingDateResponse(ctx *fiber.Ctx) error +} + +type CreateTrainingDate201JSONResponse TrainingDate + +func (response CreateTrainingDate201JSONResponse) VisitCreateTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(201) + + return ctx.JSON(&response) +} + +type CreateTrainingDate409ApplicationProblemPlusJSONResponse ProblemDetails + +func (response CreateTrainingDate409ApplicationProblemPlusJSONResponse) VisitCreateTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(409) + + return ctx.JSON(&response) +} + +type CreateTrainingDate500ApplicationProblemPlusJSONResponse ProblemDetails + +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 training.ID `json:"trainingID"` + DateID training.DateID `json:"dateID"` +} + +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 ProblemDetails + +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 ProblemDetails + +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 UpdateTrainingDateRequestObject struct { + TrainingID training.ID `json:"trainingID"` + DateID training.DateID `json:"dateID"` + Body *UpdateTrainingDateJSONRequestBody +} + +type UpdateTrainingDateResponseObject interface { + VisitUpdateTrainingDateResponse(ctx *fiber.Ctx) error +} + +type UpdateTrainingDate200ApplicationProblemPlusJSONResponse TrainingDate + +func (response UpdateTrainingDate200ApplicationProblemPlusJSONResponse) VisitUpdateTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type UpdateTrainingDate400ApplicationProblemPlusJSONResponse ProblemDetails + +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 ProblemDetails + +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 ProblemDetails + +func (response UpdateTrainingDate500ApplicationProblemPlusJSONResponse) VisitUpdateTrainingDateResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type ListTrainingDateAttendeesRequestObject struct { + TrainingID training.ID `json:"trainingID"` + DateID training.DateID `json:"dateID"` +} + +type ListTrainingDateAttendeesResponseObject interface { + VisitListTrainingDateAttendeesResponse(ctx *fiber.Ctx) error +} + +type ListTrainingDateAttendees200JSONResponse []TrainingDateAttendee + +func (response ListTrainingDateAttendees200JSONResponse) VisitListTrainingDateAttendeesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type ListTrainingDateAttendees500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response ListTrainingDateAttendees500ApplicationProblemPlusJSONResponse) VisitListTrainingDateAttendeesResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type CreateTrainingDateAttendeeRequestObject struct { + TrainingID training.ID `json:"trainingID"` + DateID training.DateID `json:"dateID"` + Body *CreateTrainingDateAttendeeJSONRequestBody +} + +type CreateTrainingDateAttendeeResponseObject interface { + VisitCreateTrainingDateAttendeeResponse(ctx *fiber.Ctx) error +} + +type CreateTrainingDateAttendee201JSONResponse TrainingDateAttendee + +func (response CreateTrainingDateAttendee201JSONResponse) VisitCreateTrainingDateAttendeeResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(201) + + return ctx.JSON(&response) +} + +type CreateTrainingDateAttendee409ApplicationProblemPlusJSONResponse ProblemDetails + +func (response CreateTrainingDateAttendee409ApplicationProblemPlusJSONResponse) VisitCreateTrainingDateAttendeeResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(409) + + return ctx.JSON(&response) +} + +type CreateTrainingDateAttendee500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response CreateTrainingDateAttendee500ApplicationProblemPlusJSONResponse) VisitCreateTrainingDateAttendeeResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type DeleteTrainingDateAttendeeRequestObject struct { + TrainingID training.ID `json:"trainingID"` + DateID training.DateID `json:"dateID"` + AttendeeID training.AttendeeID `json:"attendeeID"` +} + +type DeleteTrainingDateAttendeeResponseObject interface { + VisitDeleteTrainingDateAttendeeResponse(ctx *fiber.Ctx) error +} + +type DeleteTrainingDateAttendee204Response struct { +} + +func (response DeleteTrainingDateAttendee204Response) VisitDeleteTrainingDateAttendeeResponse(ctx *fiber.Ctx) error { + ctx.Status(204) + return nil +} + +type DeleteTrainingDateAttendee404ApplicationProblemPlusJSONResponse ProblemDetails + +func (response DeleteTrainingDateAttendee404ApplicationProblemPlusJSONResponse) VisitDeleteTrainingDateAttendeeResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(404) + + return ctx.JSON(&response) +} + +type DeleteTrainingDateAttendee500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response DeleteTrainingDateAttendee500ApplicationProblemPlusJSONResponse) VisitDeleteTrainingDateAttendeeResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type CreateTrainingDateAttendeeFeedbackRequestObject struct { + TrainingID training.ID `json:"trainingID"` + DateID training.DateID `json:"dateID"` + AttendeeID training.AttendeeID `json:"attendeeID"` + Body *CreateTrainingDateAttendeeFeedbackJSONRequestBody +} + +type CreateTrainingDateAttendeeFeedbackResponseObject interface { + VisitCreateTrainingDateAttendeeFeedbackResponse(ctx *fiber.Ctx) error +} + +type CreateTrainingDateAttendeeFeedback201JSONResponse TrainingFeedback + +func (response CreateTrainingDateAttendeeFeedback201JSONResponse) VisitCreateTrainingDateAttendeeFeedbackResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(201) + + return ctx.JSON(&response) +} + +type CreateTrainingDateAttendeeFeedback409ApplicationProblemPlusJSONResponse ProblemDetails + +func (response CreateTrainingDateAttendeeFeedback409ApplicationProblemPlusJSONResponse) VisitCreateTrainingDateAttendeeFeedbackResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(409) + + return ctx.JSON(&response) +} + +type CreateTrainingDateAttendeeFeedback500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response CreateTrainingDateAttendeeFeedback500ApplicationProblemPlusJSONResponse) VisitCreateTrainingDateAttendeeFeedbackResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type ListTrainingDateFeedbackRequestObject struct { + TrainingID training.ID `json:"trainingID"` + DateID training.DateID `json:"dateID"` +} + +type ListTrainingDateFeedbackResponseObject interface { + VisitListTrainingDateFeedbackResponse(ctx *fiber.Ctx) error +} + +type ListTrainingDateFeedback200JSONResponse []TrainingFeedback + +func (response ListTrainingDateFeedback200JSONResponse) VisitListTrainingDateFeedbackResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type ListTrainingDateFeedback500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response ListTrainingDateFeedback500ApplicationProblemPlusJSONResponse) VisitListTrainingDateFeedbackResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/problem+json") + ctx.Status(500) + + return ctx.JSON(&response) +} + +type ListTrainingFeedbackRequestObject struct { + TrainingID training.ID `json:"trainingID"` +} + +type ListTrainingFeedbackResponseObject interface { + VisitListTrainingFeedbackResponse(ctx *fiber.Ctx) error +} + +type ListTrainingFeedback200JSONResponse []TrainingFeedback + +func (response ListTrainingFeedback200JSONResponse) VisitListTrainingFeedbackResponse(ctx *fiber.Ctx) error { + ctx.Response().Header.Set("Content-Type", "application/json") + ctx.Status(200) + + return ctx.JSON(&response) +} + +type ListTrainingFeedback500ApplicationProblemPlusJSONResponse ProblemDetails + +func (response ListTrainingFeedback500ApplicationProblemPlusJSONResponse) VisitListTrainingFeedbackResponse(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 + // (GET /trainings) + ListTrainings(ctx context.Context, request ListTrainingsRequestObject) (ListTrainingsResponseObject, error) + // Create a new training + // (POST /trainings) + CreateTraining(ctx context.Context, request CreateTrainingRequestObject) (CreateTrainingResponseObject, error) + // List all upcoming dates of all trainings + // (GET /trainings/upcoming-dates) + ListUpcomingTrainingDates(ctx context.Context, request ListUpcomingTrainingDatesRequestObject) (ListUpcomingTrainingDatesResponseObject, error) + // Delete a training by ID + // (DELETE /trainings/{trainingID}) + DeleteTraining(ctx context.Context, request DeleteTrainingRequestObject) (DeleteTrainingResponseObject, error) + // Get a training by ID + // (GET /trainings/{trainingID}) + GetTraining(ctx context.Context, request GetTrainingRequestObject) (GetTrainingResponseObject, error) + // Update a training by ID + // (PUT /trainings/{trainingID}) + UpdateTraining(ctx context.Context, request UpdateTrainingRequestObject) (UpdateTrainingResponseObject, error) + // List all dates of a training + // (GET /trainings/{trainingID}/dates) + ListTrainingDates(ctx context.Context, request ListTrainingDatesRequestObject) (ListTrainingDatesResponseObject, error) + // Create a new date for a training + // (POST /trainings/{trainingID}/dates) + CreateTrainingDate(ctx context.Context, request CreateTrainingDateRequestObject) (CreateTrainingDateResponseObject, error) + // Delete a date of a training + // (DELETE /trainings/{trainingID}/dates/{dateID}) + DeleteTrainingDate(ctx context.Context, request DeleteTrainingDateRequestObject) (DeleteTrainingDateResponseObject, error) + // Update a date of a training + // (PUT /trainings/{trainingID}/dates/{dateID}) + UpdateTrainingDate(ctx context.Context, request UpdateTrainingDateRequestObject) (UpdateTrainingDateResponseObject, error) + // List all attendees of a date of a training + // (GET /trainings/{trainingID}/dates/{dateID}/attendees) + ListTrainingDateAttendees(ctx context.Context, request ListTrainingDateAttendeesRequestObject) (ListTrainingDateAttendeesResponseObject, error) + // Add an attendee to a date of a training + // (POST /trainings/{trainingID}/dates/{dateID}/attendees) + CreateTrainingDateAttendee(ctx context.Context, request CreateTrainingDateAttendeeRequestObject) (CreateTrainingDateAttendeeResponseObject, error) + // Delete an attendee of a date of a training + // (DELETE /trainings/{trainingID}/dates/{dateID}/attendees/{attendeeID}) + DeleteTrainingDateAttendee(ctx context.Context, request DeleteTrainingDateAttendeeRequestObject) (DeleteTrainingDateAttendeeResponseObject, error) + // Submit feedback for an attendee of a date of a training + // (POST /trainings/{trainingID}/dates/{dateID}/attendees/{attendeeID}/feedback) + CreateTrainingDateAttendeeFeedback(ctx context.Context, request CreateTrainingDateAttendeeFeedbackRequestObject) (CreateTrainingDateAttendeeFeedbackResponseObject, error) + // List all feedback of a date of a training + // (GET /trainings/{trainingID}/dates/{dateID}/feedback) + ListTrainingDateFeedback(ctx context.Context, request ListTrainingDateFeedbackRequestObject) (ListTrainingDateFeedbackResponseObject, error) + // List all feedback of a training + // (GET /trainings/{trainingID}/feedback) + ListTrainingFeedback(ctx context.Context, request ListTrainingFeedbackRequestObject) (ListTrainingFeedbackResponseObject, error) +} + +type StrictHandlerFunc func(ctx *fiber.Ctx, args interface{}) (interface{}, error) + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc +} + +// ListTrainings operation middleware +func (sh *strictHandler) ListTrainings(ctx *fiber.Ctx) error { + var request ListTrainingsRequestObject + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ListTrainings(ctx.UserContext(), request.(ListTrainingsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ListTrainings") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(ListTrainingsResponseObject); ok { + if err := validResponse.VisitListTrainingsResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// CreateTraining operation middleware +func (sh *strictHandler) CreateTraining(ctx *fiber.Ctx) error { + var request CreateTrainingRequestObject + + var body CreateTrainingJSONRequestBody + 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.CreateTraining(ctx.UserContext(), request.(CreateTrainingRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CreateTraining") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(CreateTrainingResponseObject); ok { + if err := validResponse.VisitCreateTrainingResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// ListUpcomingTrainingDates operation middleware +func (sh *strictHandler) ListUpcomingTrainingDates(ctx *fiber.Ctx, params ListUpcomingTrainingDatesParams) error { + var request ListUpcomingTrainingDatesRequestObject + + request.Params = params + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ListUpcomingTrainingDates(ctx.UserContext(), request.(ListUpcomingTrainingDatesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ListUpcomingTrainingDates") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(ListUpcomingTrainingDatesResponseObject); ok { + if err := validResponse.VisitListUpcomingTrainingDatesResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// DeleteTraining operation middleware +func (sh *strictHandler) DeleteTraining(ctx *fiber.Ctx, trainingID training.ID) error { + var request DeleteTrainingRequestObject + + request.TrainingID = trainingID + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.DeleteTraining(ctx.UserContext(), request.(DeleteTrainingRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteTraining") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(DeleteTrainingResponseObject); ok { + if err := validResponse.VisitDeleteTrainingResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// GetTraining operation middleware +func (sh *strictHandler) GetTraining(ctx *fiber.Ctx, trainingID training.ID) error { + var request GetTrainingRequestObject + + request.TrainingID = trainingID + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.GetTraining(ctx.UserContext(), request.(GetTrainingRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetTraining") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(GetTrainingResponseObject); ok { + if err := validResponse.VisitGetTrainingResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// UpdateTraining operation middleware +func (sh *strictHandler) UpdateTraining(ctx *fiber.Ctx, trainingID training.ID) error { + var request UpdateTrainingRequestObject + + request.TrainingID = trainingID + + var body UpdateTrainingJSONRequestBody + 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.UpdateTraining(ctx.UserContext(), request.(UpdateTrainingRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UpdateTraining") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(UpdateTrainingResponseObject); ok { + if err := validResponse.VisitUpdateTrainingResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// ListTrainingDates operation middleware +func (sh *strictHandler) ListTrainingDates(ctx *fiber.Ctx, trainingID training.ID) 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 training.ID) 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 training.ID, dateID training.DateID) error { + var request DeleteTrainingDateRequestObject + + request.TrainingID = trainingID + request.DateID = dateID + + 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 +} + +// UpdateTrainingDate operation middleware +func (sh *strictHandler) UpdateTrainingDate(ctx *fiber.Ctx, trainingID training.ID, dateID training.DateID) error { + var request UpdateTrainingDateRequestObject + + request.TrainingID = trainingID + request.DateID = dateID + + 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 +} + +// ListTrainingDateAttendees operation middleware +func (sh *strictHandler) ListTrainingDateAttendees(ctx *fiber.Ctx, trainingID training.ID, dateID training.DateID) error { + var request ListTrainingDateAttendeesRequestObject + + request.TrainingID = trainingID + request.DateID = dateID + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ListTrainingDateAttendees(ctx.UserContext(), request.(ListTrainingDateAttendeesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ListTrainingDateAttendees") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(ListTrainingDateAttendeesResponseObject); ok { + if err := validResponse.VisitListTrainingDateAttendeesResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// CreateTrainingDateAttendee operation middleware +func (sh *strictHandler) CreateTrainingDateAttendee(ctx *fiber.Ctx, trainingID training.ID, dateID training.DateID) error { + var request CreateTrainingDateAttendeeRequestObject + + request.TrainingID = trainingID + request.DateID = dateID + + var body CreateTrainingDateAttendeeJSONRequestBody + 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.CreateTrainingDateAttendee(ctx.UserContext(), request.(CreateTrainingDateAttendeeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CreateTrainingDateAttendee") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(CreateTrainingDateAttendeeResponseObject); ok { + if err := validResponse.VisitCreateTrainingDateAttendeeResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// DeleteTrainingDateAttendee operation middleware +func (sh *strictHandler) DeleteTrainingDateAttendee(ctx *fiber.Ctx, trainingID training.ID, dateID training.DateID, attendeeID training.AttendeeID) error { + var request DeleteTrainingDateAttendeeRequestObject + + request.TrainingID = trainingID + request.DateID = dateID + request.AttendeeID = attendeeID + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.DeleteTrainingDateAttendee(ctx.UserContext(), request.(DeleteTrainingDateAttendeeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteTrainingDateAttendee") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(DeleteTrainingDateAttendeeResponseObject); ok { + if err := validResponse.VisitDeleteTrainingDateAttendeeResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// CreateTrainingDateAttendeeFeedback operation middleware +func (sh *strictHandler) CreateTrainingDateAttendeeFeedback(ctx *fiber.Ctx, trainingID training.ID, dateID training.DateID, attendeeID training.AttendeeID) error { + var request CreateTrainingDateAttendeeFeedbackRequestObject + + request.TrainingID = trainingID + request.DateID = dateID + request.AttendeeID = attendeeID + + var body CreateTrainingDateAttendeeFeedbackJSONRequestBody + 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.CreateTrainingDateAttendeeFeedback(ctx.UserContext(), request.(CreateTrainingDateAttendeeFeedbackRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CreateTrainingDateAttendeeFeedback") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(CreateTrainingDateAttendeeFeedbackResponseObject); ok { + if err := validResponse.VisitCreateTrainingDateAttendeeFeedbackResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// ListTrainingDateFeedback operation middleware +func (sh *strictHandler) ListTrainingDateFeedback(ctx *fiber.Ctx, trainingID training.ID, dateID training.DateID) error { + var request ListTrainingDateFeedbackRequestObject + + request.TrainingID = trainingID + request.DateID = dateID + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ListTrainingDateFeedback(ctx.UserContext(), request.(ListTrainingDateFeedbackRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ListTrainingDateFeedback") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(ListTrainingDateFeedbackResponseObject); ok { + if err := validResponse.VisitListTrainingDateFeedbackResponse(ctx); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } else if response != nil { + return fmt.Errorf("unexpected response type: %T", response) + } + return nil +} + +// ListTrainingFeedback operation middleware +func (sh *strictHandler) ListTrainingFeedback(ctx *fiber.Ctx, trainingID training.ID) error { + var request ListTrainingFeedbackRequestObject + + request.TrainingID = trainingID + + handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) { + return sh.ssi.ListTrainingFeedback(ctx.UserContext(), request.(ListTrainingFeedbackRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ListTrainingFeedback") + } + + response, err := handler(ctx, request) + + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else if validResponse, ok := response.(ListTrainingFeedbackResponseObject); ok { + if err := validResponse.VisitListTrainingFeedbackResponse(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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", - "JLclvl/+LwAA//9BeUHGnkUAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/api/server.go b/internal/api/server.go new file mode 100644 index 0000000..c61817b --- /dev/null +++ b/internal/api/server.go @@ -0,0 +1,126 @@ +package api + +import ( + "database/sql" + "fmt" + + "github.com/gofiber/fiber/v2" + "gitlab.mareshq.com/hq/backoffice/backoffice-api/internal/postgres" + "gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training" + "go.uber.org/zap" +) + +type APIServer struct { + logger *zap.SugaredLogger + + trainingRepo *postgres.TrainingRepository + dateRepo *postgres.DateRepository + attendeeRepo *postgres.AttendeeRepository +} + +func NewAPIServer(db *sql.DB, logger *zap.SugaredLogger) *APIServer { + return &APIServer{ + logger: logger, + + trainingRepo: postgres.NewTrainingRepository(db), + dateRepo: postgres.NewDateRepository(db), + attendeeRepo: postgres.NewAttendeeRepository(db), + } +} + +func (s *APIServer) ListTrainings(c *fiber.Ctx) error { + all, err := s.trainingRepo.FindAll() + if err != nil { + s.logger.Error("ListTrainings", zap.Error(err)) + c.JSON(ListTrainings500ApplicationProblemPlusJSONResponse{ + Status: fiber.StatusInternalServerError, + Title: fmt.Sprintf("Internal server error: %s", "failed to list trainings"), + }) + } + + c.JSON(ListTrainings200JSONResponse{}) +} + +func (s *APIServer) CreateTraining(c *fiber.Ctx) error { + tr := &training.Training{} + if err := c.BodyParser(tr); err != nil { + s.logger.Error("CreateTraining", zap.Error(err)) + c.JSON(CreateTraining400ApplicationProblemPlusJSONResponse{ + Status: fiber.StatusBadRequest, + Title: fmt.Sprintf("Internal server error: %s", "failed to parse request body"), + }) + } + + if err := s.trainingRepo.Create(tr); err != nil { + s.logger.Error("CreateTraining", zap.Error(err)) + c.JSON(CreateTraining500ApplicationProblemPlusJSONResponse{ + Status: fiber.StatusInternalServerError, + Title: fmt.Sprintf("Internal server error: %s", "failed to save training"), + }) + } + + c.JSON(CreateTraining201JSONResponse{ + Id: tr.ID, + Name: tr.Name, + Days: int32(tr.Days), + Price: tr.Price, + }) + +} + +func (s *APIServer) UpdateTraining(c *fiber.Ctx) error { + s.logger.Info("UpdateTraining") +} + +func (s *APIServer) DeleteTraining(c *fiber.Ctx, trainingID training.ID) error { + s.logger.Info("DeleteTraining") +} + +func (s *APIServer) GetTraining(c *fiber.Ctx, trainingID training.ID) error { + s.logger.Info("GetTraining") +} + +func (s *APIServer) ListTrainingDates(c *fiber.Ctx, trainingID training.ID) error { + s.logger.Info("ListTrainingDates") +} + +func (s *APIServer) CreateTrainingDate(c *fiber.Ctx, trainingID training.ID) error { + s.logger.Info("CreateTrainingDate") +} + +func (s *APIServer) UpdateTrainingDate(c *fiber.Ctx, trainingID training.ID, dateID training.DateID) error { + s.logger.Info("UpdateTrainingDate") +} + +func (s *APIServer) DeleteTrainingDate(c *fiber.Ctx, trainingID training.ID, dateID training.DateID) error { + s.logger.Info("DeleteTrainingDate") +} + +func (s *APIServer) ListUpcomingTrainingDates(c *fiber.Ctx, params ListUpcomingTrainingDatesParams) error { + s.logger.Info("ListUpcomingTrainingDates") + return nil +} + +func (s *APIServer) ListTrainingDateAttendees(c *fiber.Ctx, trainingID training.ID, dateID training.DateID) error { + s.logger.Info("ListTrainingDateAttendees") +} + +func (s *APIServer) CreateTrainingDateAttendee(c *fiber.Ctx, trainingID training.ID, dateID training.DateID) error { + s.logger.Info("CreateTrainingDateAttendee") +} + +func (s *APIServer) DeleteTrainingDateAttendee(c *fiber.Ctx, trainingID training.ID, dateID training.DateID, attendeeID training.AttendeeID) error { + s.logger.Info("DeleteTrainingDateAttendee") +} + +func (s *APIServer) CreateTrainingDateAttendeeFeedback(c *fiber.Ctx, trainingID training.ID, dateID training.DateID, attendeeID training.AttendeeID) error { + s.logger.Info("CreateTrainingDateAttendeeFeedback") +} + +func (s *APIServer) ListTrainingDateFeedback(c *fiber.Ctx, trainingID training.ID, dateID training.DateID) error { + s.logger.Info("ListTrainingDateFeedback") +} + +func (s *APIServer) ListTrainingFeedback(c *fiber.Ctx, trainingID training.ID) error { + s.logger.Info("ListTrainingFeedback") +} diff --git a/internal/currency/currency.go b/internal/currency/currency.go new file mode 100644 index 0000000..67af763 --- /dev/null +++ b/internal/currency/currency.go @@ -0,0 +1,13 @@ +package currency + +type Currency string + +var ( + Currencies = []Currency{CZK, EUR, USD} +) + +const ( + CZK Currency = "CZK" + EUR Currency = "EUR" + USD Currency = "USD" +) diff --git a/internal/postgres/attendee_repository.go b/internal/postgres/attendee_repository.go new file mode 100644 index 0000000..b0f40c8 --- /dev/null +++ b/internal/postgres/attendee_repository.go @@ -0,0 +1,103 @@ +package postgres + +import ( + "database/sql" + + "gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training" +) + +type AttendeeRepository struct { + db *sql.DB +} + +func NewAttendeeRepository(db *sql.DB) *AttendeeRepository { + return &AttendeeRepository{db: db} +} + +func (r *AttendeeRepository) Find(id training.AttendeeID) (*training.Attendee, error) { + var a training.Attendee + err := r.db.QueryRow("SELECT * FROM attendee WHERE id = $1", id).Scan(&a.ID, &a.Name, &a.DateID, &a.Email, &a.Company, &a.Role, &a.IsStudent, &a.HasAttended, &a.HasPaid) + if err != nil { + return nil, err + } + + return &a, nil +} + +func (r *AttendeeRepository) FindAll() ([]training.Attendee, error) { + rows, err := r.db.Query("SELECT * FROM attendee") + if err != nil { + return nil, err + } + defer rows.Close() + + var attendees []training.Attendee + for rows.Next() { + var a training.Attendee + err := rows.Scan(&a.ID, &a.Name, &a.DateID, &a.Email, &a.Company, &a.Role, &a.IsStudent, &a.HasAttended, &a.HasPaid) + if err != nil { + return nil, err + } + + attendees = append(attendees, a) + } + + return attendees, nil +} + +func (r *AttendeeRepository) FindAllForDate(dateID training.DateID) ([]training.Attendee, error) { + rows, err := r.db.Query("SELECT * FROM attendee WHERE date_id = $1", dateID) + if err != nil { + return nil, err + } + defer rows.Close() + + var attendees []training.Attendee + for rows.Next() { + var a training.Attendee + err := rows.Scan(&a.ID, &a.Name, &a.DateID, &a.Email, &a.Company, &a.Role, &a.IsStudent, &a.HasAttended, &a.HasPaid) + if err != nil { + return nil, err + } + + attendees = append(attendees, a) + } + + return attendees, nil +} + +func (r *AttendeeRepository) CountForDate(dateID training.DateID) (int, error) { + var count int + err := r.db.QueryRow("SELECT COUNT(*) FROM attendee WHERE date_id = $1", dateID).Scan(&count) + if err != nil { + return 0, err + } + + return count, nil +} + +func (r *AttendeeRepository) Create(a *training.Attendee) error { + a.ID = training.NewAttendeeID() + _, err := r.db.Exec("INSERT INTO attendee (id, date_id, name, email, company, role, is_student, has_attended, has_paid) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", a.ID, a.DateID, a.Name, a.Email, a.Company, a.Role, a.IsStudent, a.HasAttended, a.HasPaid) + return err +} + +func (r *AttendeeRepository) Update(a *training.Attendee) error { + _, err := r.db.Exec("UPDATE attendee SET name = $1, email = $2, company = $3, role = $4, is_student = $5, has_attended = $6, has_paid = $7 WHERE id = $8", a.Name, a.Email, a.Company, a.Role, a.IsStudent, a.HasAttended, a.HasPaid, a.ID) + return err +} + +func (r *AttendeeRepository) UpdateAttendance(id training.AttendeeID, hasAttended bool) error { + _, err := r.db.Exec("UPDATE attendee SET has_attended = $1 WHERE id = $2", hasAttended, id) + return err +} + +func (r *AttendeeRepository) UpdatePayment(id training.AttendeeID, hasPaid bool) error { + _, err := r.db.Exec("UPDATE attendee SET has_paid = $1 WHERE id = $2", hasPaid, id) + return err +} + +func (r *AttendeeRepository) Delete(id training.AttendeeID) error { + _, err := r.db.Exec("DELETE FROM attendee WHERE id = $1", id) + return err +} diff --git a/internal/postgres/date_repository.go b/internal/postgres/date_repository.go new file mode 100644 index 0000000..36d43d6 --- /dev/null +++ b/internal/postgres/date_repository.go @@ -0,0 +1,101 @@ +package postgres + +import ( + "database/sql" + + "gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training" +) + +type DateRepository struct { + db *sql.DB +} + +func NewDateRepository(db *sql.DB) *DateRepository { + return &DateRepository{db: db} +} + +func (r *DateRepository) Get(id training.DateID) (*training.Date, error) { + var d training.Date + err := r.db.QueryRow("SELECT * FROM date WHERE id = $1", id).Scan(&d.ID, &d.Date, &d.TrainingID, &d.StartTime, &d.Days, &d.Price, &d.IsOnline, &d.Location, &d.Address, &d.Capacity) + if err != nil { + return nil, err + } + + return &d, nil +} + +func (r *DateRepository) FindAll() ([]training.Date, error) { + rows, err := r.db.Query("SELECT * FROM date") + if err != nil { + return nil, err + } + + defer rows.Close() + + var dates []training.Date + for rows.Next() { + var d training.Date + err := rows.Scan(&d.ID, &d.Date, &d.TrainingID, &d.StartTime, &d.Days, &d.Price, &d.IsOnline, &d.Location, &d.Address, &d.Capacity) + if err != nil { + return nil, err + } + + dates = append(dates, d) + } + + return dates, nil +} + +func (r *DateRepository) FindAllForTraining(id training.ID) ([]training.Date, error) { + rows, err := r.db.Query("SELECT * FROM training.dates WHERE td.training_id = $1 ORDER BY date DESC", id) + if err != nil { + return nil, err + } + + defer rows.Close() + + var dates []training.Date + for rows.Next() { + var d training.Date + err := rows.Scan(&d.ID, &d.Date) + if err != nil { + return nil, err + } + + dates = append(dates, d) + } + + return dates, nil +} + +func (r *DateRepository) Create(d *training.Date) error { + d.ID = training.NewDateID() + _, err := r.db.Exec("INSERT INTO date (id, date, training_id, start_time, days, price, is_online, location, address, capacity) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", d.ID, d.Date, d.TrainingID, d.StartTime, d.Days, d.Price, d.IsOnline, d.Location, d.Address, d.Capacity) + return err +} + +func (r *DateRepository) Update(d *training.Date) error { + _, err := r.db.Exec("UPDATE date SET date = $1, start_time $2, days = $3, price = $4, is_online = $5, location = $6, address = $7, capacity = $8 WHERE id = $2", d.Date, d.StartTime, d.Days, d.Price, d.IsOnline, d.Location, d.Address, d.Capacity, d.ID) + return err +} + +func (r *DateRepository) Delete(id training.DateID) error { + _, err := r.db.Exec("DELETE FROM date WHERE id = $1", id) + return err +} + +func (r *DateRepository) IsFull(id training.DateID) (bool, error) { + var d training.Date + err := r.db.QueryRow("SELECT * FROM date WHERE id = $1", id).Scan(&d.ID, &d.Date, &d.TrainingID, &d.StartTime, &d.Days, &d.Price, &d.IsOnline, &d.Location, &d.Address, &d.Capacity) + if err != nil { + return false, err + } + + var count int8 + err = r.db.QueryRow("SELECT COUNT(*) FROM attendee WHERE date_id = $1", id).Scan(&count) + if err != nil { + return false, err + } + + return count >= d.Capacity, nil +} diff --git a/internal/postgres/training_repository.go b/internal/postgres/training_repository.go new file mode 100644 index 0000000..276217a --- /dev/null +++ b/internal/postgres/training_repository.go @@ -0,0 +1,62 @@ +package postgres + +import ( + "database/sql" + + "gitlab.mareshq.com/hq/backoffice/backoffice-api/pkg/training" +) + +type TrainingRepository struct { + db *sql.DB +} + +func NewTrainingRepository(db *sql.DB) *TrainingRepository { + return &TrainingRepository{db: db} +} + +func (r *TrainingRepository) Create(t *training.Training) error { + t.ID = training.NewID() + _, err := r.db.Exec("INSERT INTO training (id, name, days, description, price) VALUES ($1, $2, $3)", t.ID, t.Name, t.Days, t.Description, t.Price) + return err +} + +func (r *TrainingRepository) Get(id training.ID) (*training.Training, error) { + var t training.Training + err := r.db.QueryRow("SELECT * FROM training WHERE id = $1", id).Scan(&t.ID, &t.Name, &t.Days, &t.Description, &t.Price) + if err != nil { + return nil, err + } + + return &t, nil +} + +func (r *TrainingRepository) Update(t *training.Training) error { + _, err := r.db.Exec("UPDATE training SET name = $1, days = $2, description = $3, price = $4 WHERE id = $5", t.Name, t.Days, t.Description, t.Price, t.ID) + return err +} + +func (r *TrainingRepository) Delete(id training.ID) error { + _, err := r.db.Exec("DELETE FROM training WHERE id = $1", id) + return err +} + +func (r *TrainingRepository) FindAll() ([]training.Training, error) { + rows, err := r.db.Query("SELECT * FROM training") + if err != nil { + return nil, err + } + defer rows.Close() + + var trainings []training.Training + for rows.Next() { + var t training.Training + err := rows.Scan(&t.ID, &t.Name, &t.Days, &t.Description, &t.Price) + if err != nil { + return nil, err + } + + trainings = append(trainings, t) + } + + return trainings, nil +} diff --git a/internal/server/config.go b/internal/server/config.go new file mode 100644 index 0000000..9970144 --- /dev/null +++ b/internal/server/config.go @@ -0,0 +1,7 @@ +package server + +type Config struct { + PostgresURL string + Port int + ShutdownGraceSeconds int +} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..6be185b --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,145 @@ +package server + +import ( + "context" + "os" + "os/signal" + "sync" + "syscall" + "time" + + fiberzap "github.com/gofiber/contrib/fiberzap/v2" + "github.com/gofiber/contrib/swagger" + "github.com/gofiber/fiber/v2" + middleware "github.com/oapi-codegen/fiber-middleware" + "gitlab.mareshq.com/hq/backoffice/backoffice-api/internal/api" + "go.uber.org/zap" +) + +type Server struct { + ready ready +} + +func NewServer() *Server { + return &Server{} +} + +func (s *Server) Run() { + shutdownCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + logger, err := zap.NewProduction() + if err != nil { + logger.Fatal("failed to initialize logger", zap.Error(err)) + } + defer logger.Sync() + sugaredLogger := logger.Sugar() + + fiberConfig := fiber.Config{ + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + IdleTimeout: 5 * time.Second, + + DisableStartupMessage: true, + } + + app := fiber.New(fiberConfig) + + app.Get("/", func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusNotFound) + }) + + app.Get("/livez", func(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) + }) + + app.Get("/readyz", func(c *fiber.Ctx) error { + if s.ready.isReady() { + return c.SendStatus(fiber.StatusOK) + } + + return c.SendStatus(fiber.StatusServiceUnavailable) + }) + + // TODO: add /metrics endpoint + + app.Use(fiberzap.New(fiberzap.Config{ + Logger: logger, + })) + + swaggerConfig := swagger.Config{ + BasePath: "/", + FilePath: "./api/v1/openapi.yaml", + Path: "/swagger", + Title: "Swagger API Docs", + } + + app.Use(swagger.New(swaggerConfig)) + + app = registerAPIHandlers(app, sugaredLogger) + + <-shutdownCtx.Done() + stop() + s.ready.notReady() + + // wait till Pod is signaling not ready (no new requests) + time.Sleep(5 * time.Second) + + shutdownBegan := time.Now() + + sugaredLogger.Info("Shutdown signal recieved. Shutting down gracefully...") + + // * Gracefully shut down fiber server + // * If does not shutdown within `gracefulShutdownPeriod`, force shutdown + // ! Does not close keep-alive connections + // ! fiber.Config{ReadTimeout} must be set and non-zero and greater than the `gracefulShutdownPeriod` + // TODO: make shutdown timeout configurable + err = app.ShutdownWithTimeout(time.Second * 10) + if err != nil { + sugaredLogger.Errorw("Error during shutdown", "error", err) + } + + sugaredLogger.Infof("Shutdown completed in %v", time.Since(shutdownBegan)) +} + +func registerAPIHandlers(fiberApp *fiber.App, logger *zap.SugaredLogger) *fiber.App { + swagger, err := api.GetSwagger() + if err != nil { + logger.Fatalf("Error getting swagger: %v", err) + } + + // TODO: validate this. Copied from example + // See: https://github.com/deepmap/oapi-codegen/blob/master/examples/petstore-expanded/fiber/petstore.go#L41 + // Clear out the servers array in the swagger spec, that skips validating + // that server names match. We don't know how this thing will be run. + swagger.Servers = nil + + fiberApp.Use(middleware.OapiRequestValidator(swagger)) + + api.RegisterHandlers(fiberApp) + + return fiberApp +} + +type ready struct { + ready bool + lock sync.Mutex +} + +func (r *ready) setReady() { + r.lock.Lock() + defer r.lock.Unlock() + r.ready = true +} + +func (r *ready) isReady() bool { + r.lock.Lock() + defer r.lock.Unlock() + return r.ready +} + +func (r *ready) notReady() { + r.lock.Lock() + defer r.lock.Unlock() + r.ready = false +} diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..d555de3 --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,6 @@ +package version + +var ( + Version = "dev" + Commit = "N/A" +) diff --git a/oapi-codegen.yaml b/oapi-codegen.yaml index c692d8d..3b1220e 100644 --- a/oapi-codegen.yaml +++ b/oapi-codegen.yaml @@ -3,5 +3,6 @@ package: api generate: models: true fiber-server: true + strict-server: true embedded-spec: true output: ./internal/api/api.gen.go diff --git a/pkg/training/attendee.go b/pkg/training/attendee.go index 9e7c923..5334923 100644 --- a/pkg/training/attendee.go +++ b/pkg/training/attendee.go @@ -4,6 +4,11 @@ import "github.com/google/uuid" type AttendeeID uuid.UUID +func NewAttendeeID() AttendeeID { + id := uuid.Must(uuid.NewV7()) + return AttendeeID(id) +} + type Attendee struct { ID AttendeeID DateID DateID diff --git a/pkg/training/attendee_repository.go b/pkg/training/attendee_repository.go index 37a8ac6..7bc8b7a 100644 --- a/pkg/training/attendee_repository.go +++ b/pkg/training/attendee_repository.go @@ -5,7 +5,7 @@ type AttendeeRepository interface { FindAll() ([]Attendee, error) FindAllForDate(DateID) ([]Attendee, error) CountForDate(DateID) (int, error) - Save(*Attendee) error + Create(*Attendee) error Update(*Attendee) error UpdateAttendance(AttendeeID, bool) error UpdatePayment(AttendeeID, bool) error diff --git a/pkg/training/date.go b/pkg/training/date.go index dd283b0..365b205 100644 --- a/pkg/training/date.go +++ b/pkg/training/date.go @@ -8,15 +8,21 @@ import ( type DateID uuid.UUID -type Date struct { - ID DateID - TrainingID ID - Date time.Time - StartTime time.Time - Days int8 - Price Price - IsOnline bool - Location string // could be empty (null) for example: Prague, Brno, London, ... - Address string // could be empty (null) - Capacity int8 +func NewDateID() DateID { + id := uuid.Must(uuid.NewV7()) + return DateID(id) +} + +type Date struct { + ID DateID + TrainingID ID + Date time.Time + StartTime time.Time + Days int8 + IsOnline bool + Location string // could be empty (null) for example: Prague, Brno, London, ... + Address string // could be empty (null) + Capacity int8 + PriceAmount float64 + PriceCurrency string } diff --git a/pkg/training/date_repository.go b/pkg/training/date_repository.go index 06d6990..1b88b71 100644 --- a/pkg/training/date_repository.go +++ b/pkg/training/date_repository.go @@ -4,7 +4,7 @@ type DateRepository interface { Get(DateID) (*Date, error) FindAll() ([]Date, error) FindAllForTraining(ID) ([]Date, error) - Save(*Date) error + Create(*Date) error Update(*Date) error Delete(DateID) error IsFull(DateID) (bool, error) diff --git a/pkg/training/feedback.go b/pkg/training/feedback.go deleted file mode 100644 index 4b7a5dd..0000000 --- a/pkg/training/feedback.go +++ /dev/null @@ -1,14 +0,0 @@ -package training - -import "github.com/google/uuid" - -type FeedbackID uuid.UUID - -type Feedback struct { - ID FeedbackID - AttendeeID AttendeeID - Rating int - Comment string - IsAnonymous bool - IsSharingAllowed bool -} diff --git a/pkg/training/feedback_repository.go b/pkg/training/feedback_repository.go deleted file mode 100644 index a239768..0000000 --- a/pkg/training/feedback_repository.go +++ /dev/null @@ -1,9 +0,0 @@ -package training - -type FeedbackRepository interface { - Get(FeedbackID) (*Feedback, error) - FindAll() ([]Feedback, error) - Save(*Feedback) error - Update(*Feedback) error - Delete(FeedbackID) error -} diff --git a/pkg/training/price.go b/pkg/training/price.go index 7c19588..47cb16e 100644 --- a/pkg/training/price.go +++ b/pkg/training/price.go @@ -1,3 +1,22 @@ package training -type Price float32 +// type Price float32 + +type TrainingPrice struct { + Currency string `json:"currency"` + Amount float64 `json:"amount"` + Type PriceType `json:"type"` // open | corporate +} + +type PriceType string + +var ( + PriceTypes = []PriceType{OpenPrice, CorporatePrice, StudentPrice, GovernmentPrice} +) + +const ( + OpenPrice PriceType = "OPEN" + CorporatePrice PriceType = "CORPORATE" + StudentPrice PriceType = "STUDENT" + GovernmentPrice PriceType = "GOVERNMENT" +) diff --git a/pkg/training/repository.go b/pkg/training/repository.go index e623fcd..a180263 100644 --- a/pkg/training/repository.go +++ b/pkg/training/repository.go @@ -1,7 +1,7 @@ package training type Repository interface { - Save(*Training) error + Create(*Training) error Get(ID) (*Training, error) Update(*Training) error Delete(ID) error diff --git a/pkg/training/training.go b/pkg/training/training.go index 8e89aa1..366b2d9 100644 --- a/pkg/training/training.go +++ b/pkg/training/training.go @@ -6,10 +6,15 @@ import ( type ID uuid.UUID +func NewID() ID { + id := uuid.Must(uuid.NewV7()) + return ID(id) +} + type Training struct { ID ID Days int8 Name string Description string - Price Price + Price []TrainingPrice } diff --git a/redocly.yaml b/redocly.yaml new file mode 100644 index 0000000..f220d31 --- /dev/null +++ b/redocly.yaml @@ -0,0 +1,25 @@ +# See https://redocly.com/docs/cli/configuration/ + +extends: + - recommended + +apis: + backoffice@v1: + root: ./api/v1/openapi.yaml + rules: + no-ambiguous-paths: error + +rules: + no-unused-components: error + operation-singular-tag: warn + boolean-parameter-prefixes: + severity: error + prefixes: ["can", "is", "has"] + +theme: + openapi: + generateCodeSamples: + languages: + - lang: curl + - lang: JavaScript + - lang: Go