1
0
Fork 0

Compare commits

..

10 commits

31 changed files with 2277 additions and 263 deletions

34
.redocly.lint-ignore.yaml Normal file
View file

@ -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'

View file

@ -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

View file

@ -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:

20
cmd/migrate.go Normal file
View file

@ -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")
},
}

26
cmd/migrate_up.go Normal file
View file

@ -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
},
}

20
cmd/server.go Normal file
View file

@ -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()
},
}

21
cmd/version.go Normal file
View file

@ -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)
},
}

View file

@ -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;

View file

@ -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;

55
go.mod
View file

@ -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
)

216
go.sum
View file

@ -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=

File diff suppressed because it is too large Load diff

126
internal/api/server.go Normal file
View file

@ -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")
}

View file

@ -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"
)

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -0,0 +1,7 @@
package server
type Config struct {
PostgresURL string
Port int
ShutdownGraceSeconds int
}

145
internal/server/server.go Normal file
View file

@ -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
}

View file

@ -0,0 +1,6 @@
package version
var (
Version = "dev"
Commit = "N/A"
)

View file

@ -3,5 +3,6 @@ package: api
generate:
models: true
fiber-server: true
strict-server: true
embedded-spec: true
output: ./internal/api/api.gen.go

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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)

View file

@ -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
}

View file

@ -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
}

View file

@ -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"
)

View file

@ -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

View file

@ -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
}

25
redocly.yaml Normal file
View file

@ -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