WIP: EOL
This commit is contained in:
parent
7ed1e05284
commit
49e05cac10
23 changed files with 613 additions and 253 deletions
34
.redocly.lint-ignore.yaml
Normal file
34
.redocly.lint-ignore.yaml
Normal 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'
|
||||
18
Makefile
18
Makefile
|
|
@ -1,3 +1,21 @@
|
|||
.PHONY: generate
|
||||
generate:
|
||||
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
|
||||
|
|
|
|||
|
|
@ -493,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
|
||||
|
|
@ -679,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:
|
||||
|
|
@ -710,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:
|
||||
|
|
@ -776,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
20
cmd/migrate.go
Normal 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
26
cmd/migrate_up.go
Normal 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
20
cmd/server.go
Normal 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
21
cmd/version.go
Normal 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)
|
||||
},
|
||||
}
|
||||
|
|
@ -10,40 +10,52 @@ CREATE TABLE IF NOT EXISTS training.trainings (
|
|||
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,
|
||||
price decimal NOT NULL,
|
||||
is_online boolean NOT NULL,
|
||||
location varchar(255) NOT NULL,
|
||||
address varchar(255) NOT NULL,
|
||||
capacity smallint 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,
|
||||
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,
|
||||
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)
|
||||
);
|
||||
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -5,12 +5,14 @@ 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 (
|
||||
|
|
|
|||
|
|
@ -29,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"`
|
||||
|
|
@ -90,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"`
|
||||
|
|
@ -1675,40 +1673,39 @@ func (sh *strictHandler) ListTrainingFeedback(ctx *fiber.Ctx, trainingID trainin
|
|||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
"RgAA",
|
||||
"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",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
|
|
|||
126
internal/api/server.go
Normal file
126
internal/api/server.go
Normal 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")
|
||||
}
|
||||
13
internal/currency/currency.go
Normal file
13
internal/currency/currency.go
Normal 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"
|
||||
)
|
||||
|
|
@ -77,6 +77,7 @@ func (r *AttendeeRepository) CountForDate(dateID training.DateID) (int, error) {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ func (r *DateRepository) FindAllForTraining(id training.ID) ([]training.Date, er
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ func NewTrainingRepository(db *sql.DB) *TrainingRepository {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
7
internal/server/config.go
Normal file
7
internal/server/config.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package server
|
||||
|
||||
type Config struct {
|
||||
PostgresURL string
|
||||
Port int
|
||||
ShutdownGraceSeconds int
|
||||
}
|
||||
145
internal/server/server.go
Normal file
145
internal/server/server.go
Normal 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
|
||||
}
|
||||
6
internal/version/version.go
Normal file
6
internal/version/version.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package version
|
||||
|
||||
var (
|
||||
Version = "dev"
|
||||
Commit = "N/A"
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
25
redocly.yaml
Normal 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
|
||||
Reference in a new issue