feat: add postgres repository for training
This commit is contained in:
parent
8c277ef692
commit
ff7e320481
19 changed files with 1277 additions and 414 deletions
27
Makefile
27
Makefile
|
|
@ -2,3 +2,30 @@
|
||||||
codegen:
|
codegen:
|
||||||
@echo "Generating code..."
|
@echo "Generating code..."
|
||||||
@oapi-codegen --config=./oapi-codegen.cli.yaml ./api/v1/openapi.yaml
|
@oapi-codegen --config=./oapi-codegen.cli.yaml ./api/v1/openapi.yaml
|
||||||
|
|
||||||
|
POSTGRES_URL="pgx5://yggdrasil_user:yggdrasil@localhost:5432/yggdrasil?sslmode=disable"
|
||||||
|
|
||||||
|
.PHONY: create-migration
|
||||||
|
create-migration:
|
||||||
|
migrate create -ext sql -dir ./migrations -seq -digits 3 ${name}
|
||||||
|
|
||||||
|
.PHONY: local-migrate-up
|
||||||
|
local-migrate-up:
|
||||||
|
migrate -database ${POSTGRES_URL} -path ./migrations up
|
||||||
|
|
||||||
|
.PHONY: local-migrate-force
|
||||||
|
local-migrate-force:
|
||||||
|
migrate -database ${POSTGRES_URL} -path ./migrations force
|
||||||
|
|
||||||
|
.PHONY: local-migrate-down
|
||||||
|
local-migrate-down:
|
||||||
|
migrate -database ${POSTGRES_URL} -path ./migrations down
|
||||||
|
|
||||||
|
.PHONY: local-migrate-drop
|
||||||
|
local-migrate-drop:
|
||||||
|
migrate -database ${POSTGRES_URL} -path ./migrations drop -f
|
||||||
|
|
||||||
|
.PHONY: local-db-seed
|
||||||
|
local-db-seed:
|
||||||
|
@echo "Seeding database..."
|
||||||
|
@APP_ENV="development" DATABASE_URL_FILE="postgres_url" go run ./cmd/seed/main.go
|
||||||
|
|
|
||||||
67
cmd/seed/main.go
Normal file
67
cmd/seed/main.go
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
pgxDeciaml "github.com/jackc/pgx-shopspring-decimal"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
pgxUUID "github.com/vgarvardt/pgx-google-uuid/v5"
|
||||||
|
"gitlab.mareshq.com/hq/yggdrasil/internal/bootstrap"
|
||||||
|
"gitlab.mareshq.com/hq/yggdrasil/internal/faker"
|
||||||
|
"gitlab.mareshq.com/hq/yggdrasil/pkg/training"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
shutdownCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
logger := bootstrap.Logger()
|
||||||
|
|
||||||
|
databaseURLFile := os.Getenv("DATABASE_URL_FILE")
|
||||||
|
if databaseURLFile == "" {
|
||||||
|
logger.Fatal("DATABASE_URL_FILE is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
dat, err := os.ReadFile(databaseURLFile)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Error reading DATABASE_URL_FILE", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
databaseURL := string(dat)
|
||||||
|
|
||||||
|
// clean up url from invalid characters
|
||||||
|
databaseURL = strings.ReplaceAll(databaseURL, "\n", "")
|
||||||
|
databaseURL = strings.ReplaceAll(databaseURL, "\t", "")
|
||||||
|
|
||||||
|
pgxConfig, err := pgxpool.ParseConfig(databaseURL)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Error parsing database url with pgx (database driver)", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
pgxConfig.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
|
||||||
|
pgxUUID.Register(conn.TypeMap())
|
||||||
|
pgxDeciaml.Register(conn.TypeMap())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := pgxpool.NewWithConfig(shutdownCtx, pgxConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Error creating pgx pool", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
trainingRepo := training.NewPostgresTrainingRepository(pool)
|
||||||
|
trainingDateRepo := training.NewPostgresTrainingDateRepository(pool)
|
||||||
|
//trainingDateAttendeeRepo := training.NewPostgresTrainingDateAttendeeRepository(pool)
|
||||||
|
|
||||||
|
f := faker.NewFaker(trainingRepo, trainingDateRepo)
|
||||||
|
if err := f.GenerateFakeData(); err != nil {
|
||||||
|
logger.Fatal("Error generating fake data", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("Fake data generated successfully!")
|
||||||
|
}
|
||||||
|
|
@ -2,36 +2,18 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"gitlab.mareshq.com/hq/yggdrasil/internal/bootstrap"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"gitlab.mareshq.com/hq/yggdrasil/internal/faker"
|
|
||||||
"gitlab.mareshq.com/hq/yggdrasil/internal/server"
|
|
||||||
"gitlab.mareshq.com/hq/yggdrasil/pkg/training"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen --config=../../oapi-codegen.yaml ../../api/v1/openapi.yaml
|
//go:generate go run github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen --config=../../oapi-codegen.yaml ../../api/v1/openapi.yaml
|
||||||
|
|
||||||
var port = 3000
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
shutdownCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
shutdownCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
logger := zap.Must(zap.NewDevelopment())
|
logger := bootstrap.Logger()
|
||||||
defer logger.Sync()
|
srv := bootstrap.Bootstrap(logger)
|
||||||
|
|
||||||
trainingRepository := training.NewInMemoryTrainingRepository()
|
|
||||||
trainingDateRepository := training.NewInMemoryTrainingDateRepository()
|
|
||||||
trainingDateAttendeeRepository := training.NewInMemoryTrainingDateAttendeeRepository()
|
|
||||||
|
|
||||||
f := faker.NewFaker(trainingRepository, trainingDateRepository)
|
|
||||||
if err := f.GenerateFakeData(); err != nil {
|
|
||||||
logger.Fatal("Error generating fake data", zap.Error(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
apiHandlers := server.NewAPIHandlers(trainingRepository, trainingDateRepository, trainingDateAttendeeRepository)
|
|
||||||
srv := server.NewServer(apiHandlers, port, logger)
|
|
||||||
srv.Run(shutdownCtx)
|
srv.Run(shutdownCtx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
go.mod
10
go.mod
|
|
@ -8,10 +8,14 @@ require (
|
||||||
github.com/gofiber/contrib/swagger v1.1.2
|
github.com/gofiber/contrib/swagger v1.1.2
|
||||||
github.com/gofiber/fiber/v2 v2.52.4
|
github.com/gofiber/fiber/v2 v2.52.4
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
|
||||||
|
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e
|
||||||
|
github.com/jackc/pgx/v5 v5.5.5
|
||||||
github.com/oapi-codegen/fiber-middleware v1.0.2
|
github.com/oapi-codegen/fiber-middleware v1.0.2
|
||||||
github.com/oapi-codegen/runtime v1.1.1
|
github.com/oapi-codegen/runtime v1.1.1
|
||||||
github.com/shopspring/decimal v1.4.0
|
github.com/shopspring/decimal v1.4.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
|
github.com/vgarvardt/pgx-google-uuid/v5 v5.0.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -33,6 +37,9 @@ require (
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/invopop/yaml v0.3.1 // indirect
|
github.com/invopop/yaml v0.3.1 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/klauspost/compress v1.17.8 // indirect
|
github.com/klauspost/compress v1.17.8 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
|
@ -50,7 +57,10 @@ require (
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
go.mongodb.org/mongo-driver v1.13.1 // indirect
|
go.mongodb.org/mongo-driver v1.13.1 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
golang.org/x/crypto v0.19.0 // indirect
|
||||||
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
21
go.sum
21
go.sum
|
|
@ -64,6 +64,18 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
||||||
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
||||||
|
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0=
|
||||||
|
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e h1:i3gQ/Zo7sk4LUVbsAjTNeC4gIjoPNIZVzs4EXstssV4=
|
||||||
|
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e/go.mod h1:zUHglCZ4mpDUPgIwqEKoba6+tcUQzRdb1+DPTuYe9pI=
|
||||||
|
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||||
|
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
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/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/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
|
|
@ -123,6 +135,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
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.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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.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.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.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
|
@ -137,6 +150,8 @@ github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7g
|
||||||
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
|
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 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
github.com/vgarvardt/pgx-google-uuid/v5 v5.0.0 h1:kIIQmW04MYKyRE2ZwREPl1NY4/Uxf5x48ABTQ+yFdFo=
|
||||||
|
github.com/vgarvardt/pgx-google-uuid/v5 v5.0.0/go.mod h1:fskJeXpJTJCU9JvsZQRgR4OhKKpciztvx4rdXWil7E0=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
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.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||||
|
|
@ -156,6 +171,8 @@ 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-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-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/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||||
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
|
@ -164,6 +181,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-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/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/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-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-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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -182,6 +201,8 @@ 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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
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.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-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.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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
|
|
||||||
95
internal/bootstrap/bootstrap.go
Normal file
95
internal/bootstrap/bootstrap.go
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
pgxDeciaml "github.com/jackc/pgx-shopspring-decimal"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
pgxUUID "github.com/vgarvardt/pgx-google-uuid/v5"
|
||||||
|
"gitlab.mareshq.com/hq/yggdrasil/internal/faker"
|
||||||
|
"gitlab.mareshq.com/hq/yggdrasil/internal/server"
|
||||||
|
"gitlab.mareshq.com/hq/yggdrasil/pkg/training"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Bootstrap(logger *zap.Logger) *server.Server {
|
||||||
|
portStr := os.Getenv("APP_PORT")
|
||||||
|
if portStr == "" {
|
||||||
|
logger.Fatal("APP_PORT is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Error parsing APP_PORT", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
databaseURLFile := os.Getenv("DATABASE_URL_FILE")
|
||||||
|
if databaseURLFile == "" {
|
||||||
|
logger.Fatal("DATABASE_URL_FILE is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
dat, err := os.ReadFile(databaseURLFile)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Error reading DATABASE_URL_FILE", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
databaseURL := string(dat)
|
||||||
|
|
||||||
|
// clean up url from invalid characters
|
||||||
|
databaseURL = strings.ReplaceAll(databaseURL, "\n", "")
|
||||||
|
databaseURL = strings.ReplaceAll(databaseURL, "\t", "")
|
||||||
|
|
||||||
|
pgxConfig, err := pgxpool.ParseConfig(databaseURL)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Error parsing database url with pgx (database driver)", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
pgxConfig.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
|
||||||
|
pgxUUID.Register(conn.TypeMap())
|
||||||
|
pgxDeciaml.Register(conn.TypeMap())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := pgxpool.NewWithConfig(context.TODO(), pgxConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Error creating pgx pool", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
trainingRepo := training.NewPostgresTrainingRepository(pool)
|
||||||
|
trainingDateRepo := training.NewPostgresTrainingDateRepository(pool)
|
||||||
|
trainingDateAttendeeRepo := training.NewPostgresTrainingDateAttendeeRepository(pool)
|
||||||
|
|
||||||
|
apiHandlers := server.NewAPIHandlers(trainingRepo, trainingDateRepo, trainingDateAttendeeRepo)
|
||||||
|
|
||||||
|
srv := server.NewServer(apiHandlers, port, logger, pool)
|
||||||
|
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
func BootstrapInMemory(logger *zap.Logger) *server.Server {
|
||||||
|
portStr := os.Getenv("APP_PORT")
|
||||||
|
if portStr == "" {
|
||||||
|
logger.Fatal("APP_PORT is not set")
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal("Error parsing APP_PORT", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
trainingRepository := training.NewInMemoryTrainingRepository()
|
||||||
|
trainingDateRepository := training.NewInMemoryTrainingDateRepository()
|
||||||
|
trainingDateAttendeeRepository := training.NewInMemoryTrainingDateAttendeeRepository()
|
||||||
|
|
||||||
|
f := faker.NewFaker(trainingRepository, trainingDateRepository)
|
||||||
|
if err := f.GenerateFakeData(); err != nil {
|
||||||
|
logger.Fatal("Error generating fake data", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
apiHandlers := server.NewAPIHandlers(trainingRepository, trainingDateRepository, trainingDateAttendeeRepository)
|
||||||
|
srv := server.NewServer(apiHandlers, port, logger, nil)
|
||||||
|
|
||||||
|
return srv
|
||||||
|
}
|
||||||
23
internal/bootstrap/logger.go
Normal file
23
internal/bootstrap/logger.go
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Logger() *zap.Logger {
|
||||||
|
if os.Getenv("APP_ENV") == "development" {
|
||||||
|
logger, err := zap.NewDevelopment()
|
||||||
|
if err != nil {
|
||||||
|
return zap.NewNop()
|
||||||
|
}
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, err := zap.NewProduction()
|
||||||
|
if err != nil {
|
||||||
|
return zap.NewNop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
@ -207,11 +207,11 @@ func (f *Faker) GenerateFakeData() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
min := time.Date(now.Year(), now.Month(), now.Day(), 8, 0, 0, 0, time.UTC)
|
minT := time.Date(now.Year(), now.Month(), now.Day(), 8, 0, 0, 0, time.UTC)
|
||||||
max := time.Date(now.Year()+1, now.Month(), now.Day(), 8, 0, 0, 0, time.UTC)
|
maxT := time.Date(now.Year()+1, now.Month(), now.Day(), 8, 0, 0, 0, time.UTC)
|
||||||
delta := max.Sub(min)
|
delta := maxT.Sub(minT)
|
||||||
|
|
||||||
sec := rand.Int64N(int64(delta.Seconds())) + min.UnixNano()/1000000000
|
sec := rand.Int64N(int64(delta.Seconds())) + minT.UnixNano()/1000000000
|
||||||
date := time.Unix(sec, 0)
|
date := time.Unix(sec, 0)
|
||||||
|
|
||||||
amount := decimal.NewFromInt(4900)
|
amount := decimal.NewFromInt(4900)
|
||||||
|
|
@ -231,21 +231,19 @@ func (f *Faker) GenerateFakeData() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
td := training.TrainingDate{
|
td := training.TrainingDate{
|
||||||
Date: date,
|
Date: date,
|
||||||
StartTime: date.Truncate(24 * time.Hour),
|
StartTime: date.Truncate(24 * time.Hour),
|
||||||
Days: t.Days,
|
Days: t.Days,
|
||||||
IsOnline: online,
|
IsOnline: online,
|
||||||
Location: location,
|
Location: location,
|
||||||
Address: "TBD",
|
Address: "TBD",
|
||||||
Capacity: 12,
|
Capacity: 12,
|
||||||
Price: money.Price{
|
PriceAmount: amount,
|
||||||
Amount: amount,
|
PriceCurrency: cur,
|
||||||
Currency: cur,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
err := f.trainingDateRepository.Create(t.ID, &td)
|
dateErr := f.trainingDateRepository.Create(t.ID, &td)
|
||||||
if err != nil {
|
if dateErr != nil {
|
||||||
return err
|
return dateErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
internal/money/money.go
Normal file
8
internal/money/money.go
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package money
|
||||||
|
|
||||||
|
import "github.com/shopspring/decimal"
|
||||||
|
|
||||||
|
type Money struct {
|
||||||
|
Amount decimal.Decimal `db:"amount"`
|
||||||
|
Currency Currency `db:"currency"`
|
||||||
|
}
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package money
|
|
||||||
|
|
||||||
import "github.com/shopspring/decimal"
|
|
||||||
|
|
||||||
type Price struct {
|
|
||||||
Amount decimal.Decimal
|
|
||||||
Currency Currency
|
|
||||||
}
|
|
||||||
|
|
@ -238,8 +238,8 @@ func (h *APIHandlers) ListTrainingDates(ctx context.Context, req ListTrainingDat
|
||||||
Address: td.Address,
|
Address: td.Address,
|
||||||
Capacity: td.Capacity,
|
Capacity: td.Capacity,
|
||||||
Price: Price{
|
Price: Price{
|
||||||
Amount: td.Price.Amount.String(),
|
Amount: td.PriceAmount.String(),
|
||||||
Currency: td.Price.Currency,
|
Currency: td.PriceCurrency,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -258,7 +258,7 @@ func (h *APIHandlers) CreateTrainingDate(ctx context.Context, req CreateTraining
|
||||||
}}, nil
|
}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
price := money.Price{
|
price := money.Money{
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
Currency: req.Body.Price.Currency,
|
Currency: req.Body.Price.Currency,
|
||||||
}
|
}
|
||||||
|
|
@ -274,14 +274,15 @@ func (h *APIHandlers) CreateTrainingDate(ctx context.Context, req CreateTraining
|
||||||
}
|
}
|
||||||
|
|
||||||
td := training.TrainingDate{
|
td := training.TrainingDate{
|
||||||
Date: req.Body.Date.Time,
|
Date: req.Body.Date.Time,
|
||||||
StartTime: startTime,
|
StartTime: startTime,
|
||||||
Days: req.Body.Days,
|
Days: req.Body.Days,
|
||||||
IsOnline: req.Body.IsOnline,
|
IsOnline: req.Body.IsOnline,
|
||||||
Location: req.Body.Location,
|
Location: req.Body.Location,
|
||||||
Address: req.Body.Address,
|
Address: req.Body.Address,
|
||||||
Capacity: req.Body.Capacity,
|
Capacity: req.Body.Capacity,
|
||||||
Price: price,
|
PriceAmount: price.Amount,
|
||||||
|
PriceCurrency: price.Currency,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.trainingDateRepository.Create(req.TrainingID, &td)
|
err = h.trainingDateRepository.Create(req.TrainingID, &td)
|
||||||
|
|
@ -304,8 +305,8 @@ func (h *APIHandlers) CreateTrainingDate(ctx context.Context, req CreateTraining
|
||||||
Address: td.Address,
|
Address: td.Address,
|
||||||
Capacity: td.Capacity,
|
Capacity: td.Capacity,
|
||||||
Price: Price{
|
Price: Price{
|
||||||
Amount: td.Price.Amount.String(),
|
Amount: td.PriceAmount.String(),
|
||||||
Currency: td.Price.Currency,
|
Currency: td.PriceCurrency,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -357,8 +358,8 @@ func (h *APIHandlers) GetTrainingDate(ctx context.Context, req GetTrainingDateRe
|
||||||
Address: td.Address,
|
Address: td.Address,
|
||||||
Capacity: td.Capacity,
|
Capacity: td.Capacity,
|
||||||
Price: Price{
|
Price: Price{
|
||||||
Amount: td.Price.Amount.String(),
|
Amount: td.PriceAmount.String(),
|
||||||
Currency: td.Price.Currency,
|
Currency: td.PriceCurrency,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -374,7 +375,7 @@ func (h *APIHandlers) UpdateTrainingDate(ctx context.Context, req UpdateTraining
|
||||||
}}, nil
|
}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
price := money.Price{
|
price := money.Money{
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
Currency: req.Body.Price.Currency,
|
Currency: req.Body.Price.Currency,
|
||||||
}
|
}
|
||||||
|
|
@ -390,15 +391,16 @@ func (h *APIHandlers) UpdateTrainingDate(ctx context.Context, req UpdateTraining
|
||||||
}
|
}
|
||||||
|
|
||||||
td := training.TrainingDate{
|
td := training.TrainingDate{
|
||||||
ID: req.TrainingDateID,
|
ID: req.TrainingDateID,
|
||||||
Date: req.Body.Date.Time,
|
Date: req.Body.Date.Time,
|
||||||
StartTime: startTime,
|
StartTime: startTime,
|
||||||
Days: req.Body.Days,
|
Days: req.Body.Days,
|
||||||
IsOnline: req.Body.IsOnline,
|
IsOnline: req.Body.IsOnline,
|
||||||
Location: req.Body.Location,
|
Location: req.Body.Location,
|
||||||
Address: req.Body.Address,
|
Address: req.Body.Address,
|
||||||
Capacity: req.Body.Capacity,
|
Capacity: req.Body.Capacity,
|
||||||
Price: price,
|
PriceAmount: price.Amount,
|
||||||
|
PriceCurrency: price.Currency,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.trainingDateRepository.Update(&td)
|
err = h.trainingDateRepository.Update(&td)
|
||||||
|
|
@ -421,8 +423,8 @@ func (h *APIHandlers) UpdateTrainingDate(ctx context.Context, req UpdateTraining
|
||||||
Address: td.Address,
|
Address: td.Address,
|
||||||
Capacity: td.Capacity,
|
Capacity: td.Capacity,
|
||||||
Price: Price{
|
Price: Price{
|
||||||
Amount: td.Price.Amount.String(),
|
Amount: td.PriceAmount.String(),
|
||||||
Currency: td.Price.Currency,
|
Currency: td.PriceCurrency,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -463,8 +465,8 @@ func (h *APIHandlers) ListAllUpcomingTrainingDates(ctx context.Context, req List
|
||||||
Address: td.Address,
|
Address: td.Address,
|
||||||
Capacity: td.Capacity,
|
Capacity: td.Capacity,
|
||||||
Price: Price{
|
Price: Price{
|
||||||
Amount: td.Price.Amount.String(),
|
Amount: td.PriceAmount.String(),
|
||||||
Currency: td.Price.Currency,
|
Currency: td.PriceCurrency,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -496,8 +498,8 @@ func (h *APIHandlers) ListTrainingUpcomingDates(ctx context.Context, req ListTra
|
||||||
Address: td.Address,
|
Address: td.Address,
|
||||||
Capacity: td.Capacity,
|
Capacity: td.Capacity,
|
||||||
Price: Price{
|
Price: Price{
|
||||||
Amount: td.Price.Amount.String(),
|
Amount: td.PriceAmount.String(),
|
||||||
Currency: td.Price.Currency,
|
Currency: td.PriceCurrency,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -529,8 +531,8 @@ func (h *APIHandlers) ListTrainingDateAttendees(ctx context.Context, req ListTra
|
||||||
HasPaid: a.HasPaid,
|
HasPaid: a.HasPaid,
|
||||||
HasAttended: a.HasAttended,
|
HasAttended: a.HasAttended,
|
||||||
Bill: Price{
|
Bill: Price{
|
||||||
Amount: a.Bill.Amount.String(),
|
Amount: a.BillAmount.String(),
|
||||||
Currency: a.Bill.Currency,
|
Currency: a.BillCurrency,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -576,15 +578,16 @@ func (h *APIHandlers) CreateTrainingDateAttendee(ctx context.Context, req Create
|
||||||
}
|
}
|
||||||
|
|
||||||
ta := training.TrainingDateAttendee{
|
ta := training.TrainingDateAttendee{
|
||||||
Name: req.Body.Name,
|
Name: req.Body.Name,
|
||||||
Email: string(req.Body.Email),
|
Email: string(req.Body.Email),
|
||||||
Phone: req.Body.Phone,
|
Phone: req.Body.Phone,
|
||||||
Company: req.Body.Company,
|
Company: req.Body.Company,
|
||||||
Position: req.Body.Position,
|
Position: req.Body.Position,
|
||||||
IsStudent: *req.Body.IsStudent,
|
IsStudent: *req.Body.IsStudent,
|
||||||
HasPaid: false,
|
HasPaid: false,
|
||||||
HasAttended: false,
|
HasAttended: false,
|
||||||
Bill: td.Price,
|
BillAmount: td.PriceAmount,
|
||||||
|
BillCurrency: td.PriceCurrency,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.trainingDateAttendeeRepository.Create(req.TrainingDateID, &ta)
|
err = h.trainingDateAttendeeRepository.Create(req.TrainingDateID, &ta)
|
||||||
|
|
@ -608,8 +611,8 @@ func (h *APIHandlers) CreateTrainingDateAttendee(ctx context.Context, req Create
|
||||||
HasAttended: ta.HasAttended,
|
HasAttended: ta.HasAttended,
|
||||||
HasPaid: ta.HasPaid,
|
HasPaid: ta.HasPaid,
|
||||||
Bill: Price{
|
Bill: Price{
|
||||||
Amount: ta.Bill.Amount.String(),
|
Amount: ta.BillAmount.String(),
|
||||||
Currency: ta.Bill.Currency,
|
Currency: ta.BillCurrency,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -714,8 +717,8 @@ func (h *APIHandlers) GetTrainingDateAttendee(ctx context.Context, req GetTraini
|
||||||
HasAttended: ta.HasAttended,
|
HasAttended: ta.HasAttended,
|
||||||
HasPaid: ta.HasPaid,
|
HasPaid: ta.HasPaid,
|
||||||
Bill: Price{
|
Bill: Price{
|
||||||
Amount: ta.Bill.Amount.String(),
|
Amount: ta.BillAmount.String(),
|
||||||
Currency: ta.Bill.Currency,
|
Currency: ta.BillCurrency,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -803,8 +806,8 @@ func (h *APIHandlers) UpdateTrainingDateAttendee(ctx context.Context, req Update
|
||||||
HasAttended: ta.HasAttended,
|
HasAttended: ta.HasAttended,
|
||||||
HasPaid: ta.HasPaid,
|
HasPaid: ta.HasPaid,
|
||||||
Bill: Price{
|
Bill: Price{
|
||||||
Amount: ta.Bill.Amount.String(),
|
Amount: ta.BillAmount.String(),
|
||||||
Currency: ta.Bill.Currency,
|
Currency: ta.BillCurrency,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -887,8 +890,8 @@ func (h *APIHandlers) UpdateTrainingDateAttendeePayment(ctx context.Context, req
|
||||||
HasAttended: ta.HasAttended,
|
HasAttended: ta.HasAttended,
|
||||||
HasPaid: ta.HasPaid,
|
HasPaid: ta.HasPaid,
|
||||||
Bill: Price{
|
Bill: Price{
|
||||||
Amount: ta.Bill.Amount.String(),
|
Amount: ta.BillAmount.String(),
|
||||||
Currency: ta.Bill.Currency,
|
Currency: ta.BillCurrency,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -971,8 +974,8 @@ func (h *APIHandlers) UpdateTrainingDateAttendeeAttendance(ctx context.Context,
|
||||||
HasAttended: ta.HasAttended,
|
HasAttended: ta.HasAttended,
|
||||||
HasPaid: ta.HasPaid,
|
HasPaid: ta.HasPaid,
|
||||||
Bill: Price{
|
Bill: Price{
|
||||||
Amount: ta.Bill.Amount.String(),
|
Amount: ta.BillAmount.String(),
|
||||||
Currency: ta.Bill.Currency,
|
Currency: ta.BillCurrency,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package server
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
fiberzap "github.com/gofiber/contrib/fiberzap/v2"
|
fiberzap "github.com/gofiber/contrib/fiberzap/v2"
|
||||||
|
|
@ -16,13 +17,15 @@ type Server struct {
|
||||||
port int
|
port int
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
apiHandlers *APIHandlers
|
apiHandlers *APIHandlers
|
||||||
|
pool *pgxpool.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(apiHandlers *APIHandlers, port int, logger *zap.Logger) *Server {
|
func NewServer(apiHandlers *APIHandlers, port int, logger *zap.Logger, pool *pgxpool.Pool) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
apiHandlers: apiHandlers,
|
apiHandlers: apiHandlers,
|
||||||
port: port,
|
port: port,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
pool: pool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +57,11 @@ func (s *Server) Run(ctx context.Context) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gracefully shutdown/close database pool
|
||||||
|
if s.pool != nil {
|
||||||
|
s.pool.Close()
|
||||||
|
}
|
||||||
|
|
||||||
s.logger.Info("HTTP server shut down gracefully.", zap.Duration("duration", time.Since(shutdownBegan)))
|
s.logger.Info("HTTP server shut down gracefully.", zap.Duration("duration", time.Since(shutdownBegan)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -338,7 +338,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -384,7 +384,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -447,7 +447,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -510,7 +510,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -584,7 +584,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -599,7 +599,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -614,7 +614,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -663,7 +663,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -705,7 +705,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -765,7 +765,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -782,7 +782,7 @@ func TestServer(t *testing.T) {
|
||||||
Position: "Software Engineer",
|
Position: "Software Engineer",
|
||||||
Phone: "+420 123 456 789",
|
Phone: "+420 123 456 789",
|
||||||
IsStudent: false,
|
IsStudent: false,
|
||||||
Bill: money.Price{
|
Bill: money.Money{
|
||||||
Amount: td.Price.Amount,
|
Amount: td.Price.Amount,
|
||||||
Currency: td.Price.Currency,
|
Currency: td.Price.Currency,
|
||||||
},
|
},
|
||||||
|
|
@ -842,7 +842,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -856,7 +856,7 @@ func TestServer(t *testing.T) {
|
||||||
Position: "Software Engineer",
|
Position: "Software Engineer",
|
||||||
Phone: "+420 123 456 789",
|
Phone: "+420 123 456 789",
|
||||||
IsStudent: false,
|
IsStudent: false,
|
||||||
Bill: money.Price{
|
Bill: money.Money{
|
||||||
Amount: td.Price.Amount,
|
Amount: td.Price.Amount,
|
||||||
Currency: td.Price.Currency,
|
Currency: td.Price.Currency,
|
||||||
},
|
},
|
||||||
|
|
@ -909,7 +909,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -923,7 +923,7 @@ func TestServer(t *testing.T) {
|
||||||
Position: "Software Engineer",
|
Position: "Software Engineer",
|
||||||
Phone: "+420 123 456 789",
|
Phone: "+420 123 456 789",
|
||||||
IsStudent: false,
|
IsStudent: false,
|
||||||
Bill: money.Price{
|
Bill: money.Money{
|
||||||
Amount: td.Price.Amount,
|
Amount: td.Price.Amount,
|
||||||
Currency: td.Price.Currency,
|
Currency: td.Price.Currency,
|
||||||
},
|
},
|
||||||
|
|
@ -986,7 +986,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -1000,7 +1000,7 @@ func TestServer(t *testing.T) {
|
||||||
Position: "Software Engineer",
|
Position: "Software Engineer",
|
||||||
Phone: "+420 123 456 789",
|
Phone: "+420 123 456 789",
|
||||||
IsStudent: false,
|
IsStudent: false,
|
||||||
Bill: money.Price{
|
Bill: money.Money{
|
||||||
Amount: td.Price.Amount,
|
Amount: td.Price.Amount,
|
||||||
Currency: td.Price.Currency,
|
Currency: td.Price.Currency,
|
||||||
},
|
},
|
||||||
|
|
@ -1046,7 +1046,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -1064,7 +1064,7 @@ func TestServer(t *testing.T) {
|
||||||
Position: "Software Engineer",
|
Position: "Software Engineer",
|
||||||
Phone: "+420 123 456 789",
|
Phone: "+420 123 456 789",
|
||||||
IsStudent: false,
|
IsStudent: false,
|
||||||
Bill: money.Price{
|
Bill: money.Money{
|
||||||
Amount: td.Price.Amount,
|
Amount: td.Price.Amount,
|
||||||
Currency: td.Price.Currency,
|
Currency: td.Price.Currency,
|
||||||
},
|
},
|
||||||
|
|
@ -1123,7 +1123,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -1141,7 +1141,7 @@ func TestServer(t *testing.T) {
|
||||||
Position: "Software Engineer",
|
Position: "Software Engineer",
|
||||||
Phone: "+420 123 456 789",
|
Phone: "+420 123 456 789",
|
||||||
IsStudent: false,
|
IsStudent: false,
|
||||||
Bill: money.Price{
|
Bill: money.Money{
|
||||||
Amount: td.Price.Amount,
|
Amount: td.Price.Amount,
|
||||||
Currency: td.Price.Currency,
|
Currency: td.Price.Currency,
|
||||||
},
|
},
|
||||||
|
|
@ -1200,7 +1200,7 @@ func TestServer(t *testing.T) {
|
||||||
IsOnline: false,
|
IsOnline: false,
|
||||||
Location: "NYC",
|
Location: "NYC",
|
||||||
StartTime: date,
|
StartTime: date,
|
||||||
Price: money.Price{
|
Price: money.Money{
|
||||||
Amount: decimal.NewFromInt(200),
|
Amount: decimal.NewFromInt(200),
|
||||||
Currency: "EUR",
|
Currency: "EUR",
|
||||||
},
|
},
|
||||||
|
|
@ -1216,7 +1216,7 @@ func TestServer(t *testing.T) {
|
||||||
Position: "Software Engineer",
|
Position: "Software Engineer",
|
||||||
Phone: "+420 123 456 789",
|
Phone: "+420 123 456 789",
|
||||||
IsStudent: false,
|
IsStudent: false,
|
||||||
Bill: money.Price{
|
Bill: money.Money{
|
||||||
Amount: td.Price.Amount,
|
Amount: td.Price.Amount,
|
||||||
Currency: td.Price.Currency,
|
Currency: td.Price.Currency,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
9
migrations/001_trainings.down.sql
Normal file
9
migrations/001_trainings.down.sql
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
DROP TABLE IF EXISTS training.date_attendees;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS training.dates;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS training.prices;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS training.trainings;
|
||||||
|
|
||||||
|
DROP SCHEMA IF EXISTS training;
|
||||||
56
migrations/001_trainings.up.sql
Normal file
56
migrations/001_trainings.up.sql
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS training.prices (
|
||||||
|
training_id UUID REFERENCES training.trainings(id),
|
||||||
|
amount NUMERIC(10,4) NOT NULL,
|
||||||
|
currency VARCHAR(3) NOT NULL,
|
||||||
|
CONSTRAINT positive_amount CHECK (amount >= 0),
|
||||||
|
CONSTRAINT allowed_currencies CHECK (currency IN ('USD', 'EUR', 'CZK')),
|
||||||
|
type VARCHAR(10) NOT NULL,
|
||||||
|
CHECK (type IN ('OPEN', 'CORPORATE')),
|
||||||
|
PRIMARY KEY (training_id, currency, type) -- composite primary key
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS training.dates (
|
||||||
|
ID UUID PRIMARY KEY,
|
||||||
|
training_id UUID REFERENCES training.trainings(id),
|
||||||
|
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(500) NOT NULL,
|
||||||
|
capacity SMALLINT NOT NULL,
|
||||||
|
price_amount NUMERIC(10,4) NOT NULL,
|
||||||
|
price_currency VARCHAR(3) NOT NULL,
|
||||||
|
CONSTRAINT positive_amount CHECK (price_amount >= 0),
|
||||||
|
CONSTRAINT allowed_currencies CHECK (price_currency IN ('USD', 'EUR', 'CZK'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS training.date_attendees (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
date_id UUID REFERENCES training.dates(id),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(255) NOT NULL,
|
||||||
|
phone VARCHAR(20) NOT NULL,
|
||||||
|
company VARCHAR(255) NOT NULL,
|
||||||
|
position VARCHAR(255) NOT NULL,
|
||||||
|
is_student BOOLEAN NOT NULL,
|
||||||
|
has_paid BOOLEAN NOT NULL,
|
||||||
|
has_attended BOOLEAN NOT NULL,
|
||||||
|
bill_amount NUMERIC(10,4) NOT NULL,
|
||||||
|
bill_currency VARCHAR(3) NOT NULL,
|
||||||
|
CONSTRAINT positive_amount CHECK (bill_amount >= 0),
|
||||||
|
CONSTRAINT allowed_currencies CHECK (bill_currency IN ('USD', 'EUR', 'CZK'))
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
270
pkg/training/inmemory_repository.go
Normal file
270
pkg/training/inmemory_repository.go
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
package training
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InMemoryTrainingRepository struct {
|
||||||
|
trainings map[TrainingID]Training
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInMemoryTrainingRepository() *InMemoryTrainingRepository {
|
||||||
|
return &InMemoryTrainingRepository{
|
||||||
|
trainings: make(map[TrainingID]Training),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingRepository) Create(training *Training) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
training.ID = NewTrainingID()
|
||||||
|
r.trainings[training.ID] = *training
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingRepository) FindByID(id TrainingID) (*Training, error) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
training, ok := r.trainings[id]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrTrainingNotFound
|
||||||
|
}
|
||||||
|
return &training, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingRepository) FindAll() ([]Training, error) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
trainings := make([]Training, 0, len(r.trainings))
|
||||||
|
for _, training := range r.trainings {
|
||||||
|
trainings = append(trainings, training)
|
||||||
|
}
|
||||||
|
return trainings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingRepository) Update(training *Training) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
r.trainings[training.ID] = *training
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingRepository) Delete(id TrainingID) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
_, ok := r.trainings[id]
|
||||||
|
if !ok {
|
||||||
|
return ErrTrainingNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(r.trainings, id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type InMemoryTrainingDateRepository struct {
|
||||||
|
trainingDates map[TrainingDateID]TrainingDate
|
||||||
|
trainingToDates map[TrainingID][]TrainingDateID
|
||||||
|
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInMemoryTrainingDateRepository() *InMemoryTrainingDateRepository {
|
||||||
|
return &InMemoryTrainingDateRepository{
|
||||||
|
trainingDates: make(map[TrainingDateID]TrainingDate),
|
||||||
|
trainingToDates: make(map[TrainingID][]TrainingDateID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateRepository) Create(trainingID TrainingID, trainingDate *TrainingDate) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
trainingDate.ID = NewTrainingDateID()
|
||||||
|
trainingDate.trainingID = trainingID
|
||||||
|
|
||||||
|
r.trainingDates[trainingDate.ID] = *trainingDate
|
||||||
|
r.trainingToDates[trainingID] = append(r.trainingToDates[trainingID], trainingDate.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateRepository) FindByID(id TrainingDateID) (*TrainingDate, error) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
date, ok := r.trainingDates[id]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrTrainingDateNotFound
|
||||||
|
}
|
||||||
|
return &date, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateRepository) FindAll() ([]TrainingDate, error) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
dates := make([]TrainingDate, len(r.trainingDates))
|
||||||
|
for _, date := range r.trainingDates {
|
||||||
|
dates = append(dates, date)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateRepository) FindAllByTrainingID(trainingID TrainingID) ([]TrainingDate, error) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
dates := make([]TrainingDate, 0)
|
||||||
|
for _, id := range r.trainingToDates[trainingID] {
|
||||||
|
dates = append(dates, r.trainingDates[id])
|
||||||
|
}
|
||||||
|
|
||||||
|
return dates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateRepository) FindUpcomingByTrainingID(trainingID TrainingID) ([]TrainingDate, error) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
var dates []TrainingDate
|
||||||
|
for _, id := range r.trainingToDates[trainingID] {
|
||||||
|
date := r.trainingDates[id]
|
||||||
|
if date.Date.After(now) {
|
||||||
|
dates = append(dates, date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateRepository) Update(trainingDate *TrainingDate) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
oldDate := r.trainingDates[trainingDate.ID]
|
||||||
|
trainingDate.trainingID = oldDate.trainingID
|
||||||
|
r.trainingDates[trainingDate.ID] = *trainingDate
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateRepository) Delete(id TrainingDateID) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
date, ok := r.trainingDates[id]
|
||||||
|
if !ok {
|
||||||
|
return ErrTrainingDateNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(r.trainingDates, id)
|
||||||
|
dates := r.trainingToDates[date.trainingID]
|
||||||
|
for idx, d := range dates {
|
||||||
|
if d == id {
|
||||||
|
r.trainingToDates[date.trainingID] = append(dates[:idx], dates[idx+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type InMemoryTrainingDateAttendeeRepository struct {
|
||||||
|
attendees map[TrainingDateAttendeeID]TrainingDateAttendee
|
||||||
|
dateToAttendees map[TrainingDateID][]TrainingDateAttendeeID
|
||||||
|
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInMemoryTrainingDateAttendeeRepository() *InMemoryTrainingDateAttendeeRepository {
|
||||||
|
return &InMemoryTrainingDateAttendeeRepository{
|
||||||
|
attendees: make(map[TrainingDateAttendeeID]TrainingDateAttendee),
|
||||||
|
dateToAttendees: make(map[TrainingDateID][]TrainingDateAttendeeID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateAttendeeRepository) Create(trainingDateID TrainingDateID, attendee *TrainingDateAttendee) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
attendee.ID = NewTrainingDateAttendeeID()
|
||||||
|
attendee.trainingDateID = trainingDateID
|
||||||
|
|
||||||
|
r.attendees[attendee.ID] = *attendee
|
||||||
|
r.dateToAttendees[trainingDateID] = append(r.dateToAttendees[trainingDateID], attendee.ID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateAttendeeRepository) FindByID(id TrainingDateAttendeeID) (*TrainingDateAttendee, error) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
attendee, ok := r.attendees[id]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrTrainingDateAttendeeNotFound
|
||||||
|
}
|
||||||
|
return &attendee, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateAttendeeRepository) FindAll() ([]TrainingDateAttendee, error) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
attendees := make([]TrainingDateAttendee, len(r.attendees))
|
||||||
|
for _, attendee := range r.attendees {
|
||||||
|
attendees = append(attendees, attendee)
|
||||||
|
}
|
||||||
|
|
||||||
|
return attendees, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateAttendeeRepository) FindAllByTrainingDateID(trainingDateID TrainingDateID) ([]TrainingDateAttendee, error) {
|
||||||
|
r.lock.RLock()
|
||||||
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
|
attendees := make([]TrainingDateAttendee, 0)
|
||||||
|
for _, id := range r.dateToAttendees[trainingDateID] {
|
||||||
|
attendees = append(attendees, r.attendees[id])
|
||||||
|
}
|
||||||
|
|
||||||
|
return attendees, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateAttendeeRepository) Update(attendee *TrainingDateAttendee) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
oldAttendee := r.attendees[attendee.ID]
|
||||||
|
attendee.trainingDateID = oldAttendee.trainingDateID
|
||||||
|
r.attendees[attendee.ID] = *attendee
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InMemoryTrainingDateAttendeeRepository) Delete(id TrainingDateAttendeeID) error {
|
||||||
|
r.lock.Lock()
|
||||||
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
|
attendee, ok := r.attendees[id]
|
||||||
|
if !ok {
|
||||||
|
return ErrTrainingDateAttendeeNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(r.attendees, id)
|
||||||
|
attendees := r.dateToAttendees[attendee.trainingDateID]
|
||||||
|
for idx, a := range attendees {
|
||||||
|
if a == id {
|
||||||
|
r.dateToAttendees[attendee.trainingDateID] = append(attendees[:idx], attendees[idx+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package training
|
package training
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
"gitlab.mareshq.com/hq/yggdrasil/internal/money"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
"gitlab.mareshq.com/hq/yggdrasil/internal/money"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TrainingID = uuid.UUID
|
type TrainingID = uuid.UUID
|
||||||
|
|
@ -19,7 +19,7 @@ type Training struct {
|
||||||
Name string
|
Name string
|
||||||
Days int8
|
Days int8
|
||||||
Description string
|
Description string
|
||||||
Pricing []TrainingPrice
|
Pricing []TrainingPrice `db:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrainingPrice struct {
|
type TrainingPrice struct {
|
||||||
|
|
@ -44,15 +44,16 @@ func NewTrainingDateID() TrainingDateID {
|
||||||
type TrainingDate struct {
|
type TrainingDate struct {
|
||||||
trainingID TrainingID
|
trainingID TrainingID
|
||||||
|
|
||||||
ID TrainingDateID
|
ID TrainingDateID
|
||||||
Date time.Time
|
Date time.Time
|
||||||
StartTime time.Time
|
StartTime time.Time
|
||||||
Days int8
|
Days int8
|
||||||
IsOnline bool
|
IsOnline bool
|
||||||
Location string
|
Location string
|
||||||
Address string
|
Address string
|
||||||
Capacity int8
|
Capacity int8
|
||||||
Price money.Price
|
PriceAmount decimal.Decimal `db:"price_amount"`
|
||||||
|
PriceCurrency money.Currency `db:"price_currency"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrainingDateAttendeeID = uuid.UUID
|
type TrainingDateAttendeeID = uuid.UUID
|
||||||
|
|
@ -64,14 +65,15 @@ func NewTrainingDateAttendeeID() TrainingDateAttendeeID {
|
||||||
type TrainingDateAttendee struct {
|
type TrainingDateAttendee struct {
|
||||||
trainingDateID TrainingDateID
|
trainingDateID TrainingDateID
|
||||||
|
|
||||||
ID TrainingDateAttendeeID
|
ID TrainingDateAttendeeID
|
||||||
Name string
|
Name string
|
||||||
Email string
|
Email string
|
||||||
Phone string
|
Phone string
|
||||||
Company string
|
Company string
|
||||||
Position string
|
Position string
|
||||||
Bill money.Price
|
IsStudent bool
|
||||||
IsStudent bool
|
HasPaid bool
|
||||||
HasPaid bool
|
HasAttended bool
|
||||||
HasAttended bool
|
BillAmount decimal.Decimal `db:"bill_amount"`
|
||||||
|
BillCurrency money.Currency `db:"bill_currency"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
561
pkg/training/postgres_repository.go
Normal file
561
pkg/training/postgres_repository.go
Normal file
|
|
@ -0,0 +1,561 @@
|
||||||
|
package training
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostgresTrainingRepository struct {
|
||||||
|
pg *pgxpool.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostgresTrainingRepository(pg *pgxpool.Pool) *PostgresTrainingRepository {
|
||||||
|
return &PostgresTrainingRepository{pg: pg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingRepository) Create(training *Training) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
training.ID = NewTrainingID()
|
||||||
|
|
||||||
|
tx, err := r.pg.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec(ctx, `
|
||||||
|
INSERT INTO training.trainings (id, name, description, days)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
`, training.ID, training.Name, training.Description, training.Days)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
queryBatch := &pgx.Batch{}
|
||||||
|
|
||||||
|
for _, price := range training.Pricing {
|
||||||
|
queryBatch.Queue(`
|
||||||
|
INSERT INTO training.prices (training_id, amount, currency, type)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
`, training.ID, price.Amount, price.Currency, price.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := tx.SendBatch(ctx, queryBatch)
|
||||||
|
defer func() {
|
||||||
|
if e := batch.Close(); e != nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback(ctx)
|
||||||
|
} else {
|
||||||
|
if e := tx.Commit(ctx); e != nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = batch.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingRepository) FindByID(id TrainingID) (*Training, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var training Training
|
||||||
|
err := r.pg.QueryRow(ctx, `
|
||||||
|
SELECT id, name, description, days
|
||||||
|
FROM training.trainings
|
||||||
|
WHERE id = $1
|
||||||
|
`, id).Scan(&training.ID, &training.Name, &training.Description, &training.Days)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.pg.Query(ctx, `
|
||||||
|
SELECT amount, currency, type
|
||||||
|
FROM training.prices
|
||||||
|
WHERE training_id = $1
|
||||||
|
`, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
training.Pricing = make([]TrainingPrice, 0)
|
||||||
|
training.Pricing, err = pgx.CollectRows[TrainingPrice](rows, func(row pgx.CollectableRow) (TrainingPrice, error) {
|
||||||
|
var price TrainingPrice
|
||||||
|
err := row.Scan(&price.Amount, &price.Currency, &price.Type)
|
||||||
|
if err != nil {
|
||||||
|
return TrainingPrice{}, err
|
||||||
|
}
|
||||||
|
return price, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &training, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingRepository) FindAll() ([]Training, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := r.pg.Query(ctx, `
|
||||||
|
SELECT *
|
||||||
|
FROM training.trainings
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
trainings, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (Training, error) {
|
||||||
|
var training Training
|
||||||
|
scanErr := row.Scan(&training.ID, &training.Name, &training.Description, &training.Days)
|
||||||
|
if scanErr != nil {
|
||||||
|
return Training{}, scanErr
|
||||||
|
}
|
||||||
|
|
||||||
|
priceRows, queryErr := r.pg.Query(ctx, `SELECT amount, currency, type FROM training.prices WHERE training_id = $1`, training.ID)
|
||||||
|
if queryErr != nil {
|
||||||
|
return Training{}, queryErr
|
||||||
|
}
|
||||||
|
|
||||||
|
training.Pricing, scanErr = pgx.CollectRows(priceRows, pgx.RowToStructByName[TrainingPrice])
|
||||||
|
if scanErr != nil {
|
||||||
|
return Training{}, scanErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return training, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return trainings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingRepository) Update(training *Training) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
tx, err := r.pg.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec(ctx, `
|
||||||
|
UPDATE training.trainings
|
||||||
|
SET name = $1, description = $2, days = $3
|
||||||
|
WHERE id = $4
|
||||||
|
`, training.Name, training.Description, training.Days, training.ID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
queryBatch := &pgx.Batch{}
|
||||||
|
|
||||||
|
for _, price := range training.Pricing {
|
||||||
|
queryBatch.Queue(`
|
||||||
|
INSERT INTO training.prices (training_id, amount, currency, type)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
`, training.ID, price.Amount, price.Currency, price.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := tx.SendBatch(ctx, queryBatch)
|
||||||
|
defer func() {
|
||||||
|
if e := batch.Close(); e != nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback(ctx)
|
||||||
|
} else {
|
||||||
|
if e := tx.Commit(ctx); e != nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = batch.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingRepository) Delete(id TrainingID) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
tx, err := r.pg.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec(context.Background(), `
|
||||||
|
DELETE FROM training.prices
|
||||||
|
WHERE training_id = $1
|
||||||
|
`, id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.Exec(context.Background(), `
|
||||||
|
DELETE FROM training.trainings
|
||||||
|
WHERE id = $1
|
||||||
|
`, id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostgresTrainingDateRepository struct {
|
||||||
|
pg *pgxpool.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostgresTrainingDateRepository(pg *pgxpool.Pool) *PostgresTrainingDateRepository {
|
||||||
|
return &PostgresTrainingDateRepository{pg: pg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateRepository) Create(trainingID TrainingID, trainingDate *TrainingDate) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
trainingDate.ID = NewTrainingDateID()
|
||||||
|
|
||||||
|
_, err := r.pg.Exec(ctx, `
|
||||||
|
INSERT INTO training.dates (id, training_id, date, start_time, days, is_online, location, address, capacity, price_amount, price_currency)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
|
||||||
|
trainingDate.ID,
|
||||||
|
trainingID,
|
||||||
|
trainingDate.Date,
|
||||||
|
trainingDate.StartTime,
|
||||||
|
trainingDate.Days,
|
||||||
|
trainingDate.IsOnline,
|
||||||
|
trainingDate.Location,
|
||||||
|
trainingDate.Address,
|
||||||
|
trainingDate.Capacity,
|
||||||
|
trainingDate.PriceAmount,
|
||||||
|
trainingDate.PriceCurrency,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateRepository) FindByID(id TrainingDateID) (*TrainingDate, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var trainingDate TrainingDate
|
||||||
|
err := r.pg.QueryRow(ctx, `SELECT * FROM training.dates WHERE id = $1`, id).
|
||||||
|
Scan(
|
||||||
|
&trainingDate.ID,
|
||||||
|
&trainingDate.trainingID,
|
||||||
|
&trainingDate.Date,
|
||||||
|
&trainingDate.StartTime,
|
||||||
|
&trainingDate.Days,
|
||||||
|
&trainingDate.IsOnline,
|
||||||
|
&trainingDate.Location,
|
||||||
|
&trainingDate.Address,
|
||||||
|
&trainingDate.Capacity,
|
||||||
|
&trainingDate.PriceAmount,
|
||||||
|
&trainingDate.PriceCurrency,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &trainingDate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateRepository) FindAll() ([]TrainingDate, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := r.pg.Query(ctx, `SELECT * FROM training.dates`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var trainingDates []TrainingDate
|
||||||
|
for rows.Next() {
|
||||||
|
var trainingDate TrainingDate
|
||||||
|
err := rows.Scan(
|
||||||
|
&trainingDate.ID,
|
||||||
|
&trainingDate.trainingID,
|
||||||
|
&trainingDate.Date,
|
||||||
|
&trainingDate.StartTime,
|
||||||
|
&trainingDate.Days,
|
||||||
|
&trainingDate.IsOnline,
|
||||||
|
&trainingDate.Location,
|
||||||
|
&trainingDate.Address,
|
||||||
|
&trainingDate.Capacity,
|
||||||
|
&trainingDate.PriceAmount,
|
||||||
|
&trainingDate.PriceCurrency,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
trainingDates = append(trainingDates, trainingDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
return trainingDates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateRepository) FindAllByTrainingID(trainingID TrainingID) ([]TrainingDate, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := r.pg.Query(ctx, `SELECT * FROM training.dates WHERE training_id = $1`, trainingID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var trainingDates []TrainingDate
|
||||||
|
for rows.Next() {
|
||||||
|
var trainingDate TrainingDate
|
||||||
|
err := rows.Scan(
|
||||||
|
&trainingDate.ID,
|
||||||
|
&trainingDate.trainingID,
|
||||||
|
&trainingDate.Date,
|
||||||
|
&trainingDate.StartTime,
|
||||||
|
&trainingDate.Days,
|
||||||
|
&trainingDate.IsOnline,
|
||||||
|
&trainingDate.Location,
|
||||||
|
&trainingDate.Address,
|
||||||
|
&trainingDate.Capacity,
|
||||||
|
&trainingDate.PriceAmount,
|
||||||
|
&trainingDate.PriceCurrency,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
trainingDates = append(trainingDates, trainingDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
return trainingDates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateRepository) FindUpcomingByTrainingID(trainingID TrainingID) ([]TrainingDate, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := r.pg.Query(ctx, `
|
||||||
|
SELECT * FROM training.dates
|
||||||
|
WHERE training_id = $1 AND date > $2
|
||||||
|
`, trainingID, time.Now())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var trainingDates []TrainingDate
|
||||||
|
for rows.Next() {
|
||||||
|
var trainingDate TrainingDate
|
||||||
|
err := rows.Scan(
|
||||||
|
&trainingDate.ID,
|
||||||
|
&trainingDate.trainingID,
|
||||||
|
&trainingDate.Date,
|
||||||
|
&trainingDate.StartTime,
|
||||||
|
&trainingDate.Days,
|
||||||
|
&trainingDate.IsOnline,
|
||||||
|
&trainingDate.Location,
|
||||||
|
&trainingDate.Address,
|
||||||
|
&trainingDate.Capacity,
|
||||||
|
&trainingDate.PriceAmount,
|
||||||
|
&trainingDate.PriceCurrency,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
trainingDates = append(trainingDates, trainingDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
return trainingDates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateRepository) Update(trainingDate *TrainingDate) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := r.pg.Exec(ctx, `
|
||||||
|
UPDATE training.dates
|
||||||
|
SET date = $1, start_time = $2, days = $3, is_online = $4, location = $5, address = $6, capacity = $7, price_amount = $8, price_currency = $9
|
||||||
|
WHERE id = $10
|
||||||
|
`, trainingDate.Date, trainingDate.StartTime, trainingDate.Days, trainingDate.IsOnline, trainingDate.Location, trainingDate.Address, trainingDate.Capacity, trainingDate.PriceAmount, trainingDate.PriceCurrency, trainingDate.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateRepository) Delete(id TrainingDateID) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := r.pg.Exec(ctx, `DELETE FROM training.dates WHERE id = $1`, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostgresTrainingDateAttendeeRepository struct {
|
||||||
|
pg *pgxpool.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostgresTrainingDateAttendeeRepository(pg *pgxpool.Pool) *PostgresTrainingDateAttendeeRepository {
|
||||||
|
return &PostgresTrainingDateAttendeeRepository{pg: pg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateAttendeeRepository) Create(trainingDateID TrainingDateID, attendee *TrainingDateAttendee) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
attendee.ID = NewTrainingDateAttendeeID()
|
||||||
|
|
||||||
|
_, err := r.pg.Exec(ctx, `
|
||||||
|
INSERT INTO training.date_attendees (id, date_id, name, email, phone, company, position, is_student, has_paid, has_attended, bill_amount, bill_currency)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||||
|
`, attendee.ID, trainingDateID, attendee.Name, attendee.Email, attendee.Phone, attendee.Company, attendee.Position, attendee.IsStudent, attendee.HasPaid, attendee.HasAttended, attendee.BillAmount, attendee.BillCurrency)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateAttendeeRepository) FindByID(id TrainingDateAttendeeID) (*TrainingDateAttendee, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var attendee TrainingDateAttendee
|
||||||
|
err := r.pg.QueryRow(ctx, `
|
||||||
|
SELECT id, date_id, name, email, phone, company, position, is_student, has_paid, has_attended, bill_amount, bill_currency
|
||||||
|
FROM training.date_attendees
|
||||||
|
WHERE id = $1
|
||||||
|
`, id).Scan(&attendee.ID, &attendee.trainingDateID, &attendee.Name, &attendee.Email, &attendee.Phone, &attendee.Company, &attendee.Position, &attendee.IsStudent, &attendee.HasPaid, &attendee.HasAttended, &attendee.BillAmount, &attendee.BillCurrency)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &attendee, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateAttendeeRepository) FindAll() ([]TrainingDateAttendee, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := r.pg.Query(ctx, `
|
||||||
|
SELECT id, date_id, name, email, phone, company, position, is_student, has_paid, has_attended, bill_amount, bill_currency
|
||||||
|
FROM training.date_attendees
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var attendees []TrainingDateAttendee
|
||||||
|
for rows.Next() {
|
||||||
|
var attendee TrainingDateAttendee
|
||||||
|
err := rows.Scan(&attendee.ID, &attendee.trainingDateID, &attendee.Name, &attendee.Email, &attendee.Phone, &attendee.Company, &attendee.Position, &attendee.IsStudent, &attendee.HasPaid, &attendee.HasAttended, &attendee.BillAmount, &attendee.BillCurrency)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
attendees = append(attendees, attendee)
|
||||||
|
}
|
||||||
|
|
||||||
|
return attendees, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateAttendeeRepository) FindAllByTrainingDateID(trainingDateID TrainingDateID) ([]TrainingDateAttendee, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := r.pg.Query(ctx, `
|
||||||
|
SELECT id, date_id, name, email, phone, company, position, is_student, has_paid, has_attended, bill_amount, bill_currency
|
||||||
|
FROM training.date_attendees
|
||||||
|
WHERE date_id = $1
|
||||||
|
`, trainingDateID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var attendees []TrainingDateAttendee
|
||||||
|
for rows.Next() {
|
||||||
|
var attendee TrainingDateAttendee
|
||||||
|
err := rows.Scan(&attendee.ID, &attendee.trainingDateID, &attendee.Name, &attendee.Email, &attendee.Phone, &attendee.Company, &attendee.Position, &attendee.IsStudent, &attendee.HasPaid, &attendee.HasAttended, &attendee.BillAmount, &attendee.BillCurrency)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
attendees = append(attendees, attendee)
|
||||||
|
}
|
||||||
|
|
||||||
|
return attendees, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateAttendeeRepository) Update(attendee *TrainingDateAttendee) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := r.pg.Exec(ctx, `
|
||||||
|
UPDATE training.date_attendees
|
||||||
|
SET name = $1, email = $2, phone = $3, company = $4, position = $5, is_student = $6, has_paid = $7, has_attended = $8, bill_amount = $9, bill_currency = $10
|
||||||
|
WHERE id = $10
|
||||||
|
`, attendee.Name, attendee.Email, attendee.Phone, attendee.Company, attendee.Position, attendee.IsStudent, attendee.HasPaid, attendee.HasAttended, attendee.BillAmount, attendee.BillCurrency, attendee.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PostgresTrainingDateAttendeeRepository) Delete(id TrainingDateAttendeeID) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := r.pg.Exec(ctx, `DELETE FROM training.date_attendees WHERE id = $1`, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
package training
|
package training
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TrainingRepository interface {
|
type TrainingRepository interface {
|
||||||
Create(training *Training) error
|
Create(training *Training) error
|
||||||
FindByID(id TrainingID) (*Training, error)
|
FindByID(id TrainingID) (*Training, error)
|
||||||
|
|
@ -13,69 +8,6 @@ type TrainingRepository interface {
|
||||||
Delete(id TrainingID) error
|
Delete(id TrainingID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type InMemoryTrainingRepository struct {
|
|
||||||
trainings map[TrainingID]Training
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInMemoryTrainingRepository() *InMemoryTrainingRepository {
|
|
||||||
return &InMemoryTrainingRepository{
|
|
||||||
trainings: make(map[TrainingID]Training),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingRepository) Create(training *Training) error {
|
|
||||||
r.lock.Lock()
|
|
||||||
defer r.lock.Unlock()
|
|
||||||
|
|
||||||
training.ID = NewTrainingID()
|
|
||||||
r.trainings[training.ID] = *training
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingRepository) FindByID(id TrainingID) (*Training, error) {
|
|
||||||
r.lock.RLock()
|
|
||||||
defer r.lock.RUnlock()
|
|
||||||
|
|
||||||
training, ok := r.trainings[id]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrTrainingNotFound
|
|
||||||
}
|
|
||||||
return &training, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingRepository) FindAll() ([]Training, error) {
|
|
||||||
r.lock.RLock()
|
|
||||||
defer r.lock.RUnlock()
|
|
||||||
|
|
||||||
trainings := make([]Training, 0, len(r.trainings))
|
|
||||||
for _, training := range r.trainings {
|
|
||||||
trainings = append(trainings, training)
|
|
||||||
}
|
|
||||||
return trainings, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingRepository) Update(training *Training) error {
|
|
||||||
r.lock.Lock()
|
|
||||||
defer r.lock.Unlock()
|
|
||||||
|
|
||||||
r.trainings[training.ID] = *training
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingRepository) Delete(id TrainingID) error {
|
|
||||||
r.lock.Lock()
|
|
||||||
defer r.lock.Unlock()
|
|
||||||
|
|
||||||
_, ok := r.trainings[id]
|
|
||||||
if !ok {
|
|
||||||
return ErrTrainingNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(r.trainings, id)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TrainingDateRepository interface {
|
type TrainingDateRepository interface {
|
||||||
Create(trainingID TrainingID, trainingDate *TrainingDate) error
|
Create(trainingID TrainingID, trainingDate *TrainingDate) error
|
||||||
FindByID(id TrainingDateID) (*TrainingDate, error)
|
FindByID(id TrainingDateID) (*TrainingDate, error)
|
||||||
|
|
@ -86,114 +18,6 @@ type TrainingDateRepository interface {
|
||||||
Delete(id TrainingDateID) error
|
Delete(id TrainingDateID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type InMemoryTrainingDateRepository struct {
|
|
||||||
trainingDates map[TrainingDateID]TrainingDate
|
|
||||||
trainingToDates map[TrainingID][]TrainingDateID
|
|
||||||
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInMemoryTrainingDateRepository() *InMemoryTrainingDateRepository {
|
|
||||||
return &InMemoryTrainingDateRepository{
|
|
||||||
trainingDates: make(map[TrainingDateID]TrainingDate),
|
|
||||||
trainingToDates: make(map[TrainingID][]TrainingDateID),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateRepository) Create(trainingID TrainingID, trainingDate *TrainingDate) error {
|
|
||||||
r.lock.Lock()
|
|
||||||
defer r.lock.Unlock()
|
|
||||||
|
|
||||||
trainingDate.ID = NewTrainingDateID()
|
|
||||||
trainingDate.trainingID = trainingID
|
|
||||||
|
|
||||||
r.trainingDates[trainingDate.ID] = *trainingDate
|
|
||||||
r.trainingToDates[trainingID] = append(r.trainingToDates[trainingID], trainingDate.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateRepository) FindByID(id TrainingDateID) (*TrainingDate, error) {
|
|
||||||
r.lock.RLock()
|
|
||||||
defer r.lock.RUnlock()
|
|
||||||
|
|
||||||
date, ok := r.trainingDates[id]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrTrainingDateNotFound
|
|
||||||
}
|
|
||||||
return &date, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateRepository) FindAll() ([]TrainingDate, error) {
|
|
||||||
r.lock.RLock()
|
|
||||||
defer r.lock.RUnlock()
|
|
||||||
|
|
||||||
dates := make([]TrainingDate, len(r.trainingDates))
|
|
||||||
for _, date := range r.trainingDates {
|
|
||||||
dates = append(dates, date)
|
|
||||||
}
|
|
||||||
|
|
||||||
return dates, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateRepository) FindAllByTrainingID(trainingID TrainingID) ([]TrainingDate, error) {
|
|
||||||
r.lock.RLock()
|
|
||||||
defer r.lock.RUnlock()
|
|
||||||
|
|
||||||
dates := make([]TrainingDate, 0)
|
|
||||||
for _, id := range r.trainingToDates[trainingID] {
|
|
||||||
dates = append(dates, r.trainingDates[id])
|
|
||||||
}
|
|
||||||
|
|
||||||
return dates, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateRepository) FindUpcomingByTrainingID(trainingID TrainingID) ([]TrainingDate, error) {
|
|
||||||
r.lock.RLock()
|
|
||||||
defer r.lock.RUnlock()
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
var dates []TrainingDate
|
|
||||||
for _, id := range r.trainingToDates[trainingID] {
|
|
||||||
date := r.trainingDates[id]
|
|
||||||
if date.Date.After(now) {
|
|
||||||
dates = append(dates, date)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dates, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateRepository) Update(trainingDate *TrainingDate) error {
|
|
||||||
r.lock.Lock()
|
|
||||||
defer r.lock.Unlock()
|
|
||||||
|
|
||||||
oldDate := r.trainingDates[trainingDate.ID]
|
|
||||||
trainingDate.trainingID = oldDate.trainingID
|
|
||||||
r.trainingDates[trainingDate.ID] = *trainingDate
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateRepository) Delete(id TrainingDateID) error {
|
|
||||||
r.lock.Lock()
|
|
||||||
defer r.lock.Unlock()
|
|
||||||
|
|
||||||
date, ok := r.trainingDates[id]
|
|
||||||
if !ok {
|
|
||||||
return ErrTrainingDateNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(r.trainingDates, id)
|
|
||||||
dates := r.trainingToDates[date.trainingID]
|
|
||||||
for idx, d := range dates {
|
|
||||||
if d == id {
|
|
||||||
r.trainingToDates[date.trainingID] = append(dates[:idx], dates[idx+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TrainingDateAttendeeRepository interface {
|
type TrainingDateAttendeeRepository interface {
|
||||||
Create(trainingDateID TrainingDateID, attendee *TrainingDateAttendee) error
|
Create(trainingDateID TrainingDateID, attendee *TrainingDateAttendee) error
|
||||||
FindByID(id TrainingDateAttendeeID) (*TrainingDateAttendee, error)
|
FindByID(id TrainingDateAttendeeID) (*TrainingDateAttendee, error)
|
||||||
|
|
@ -202,96 +26,3 @@ type TrainingDateAttendeeRepository interface {
|
||||||
Update(attendee *TrainingDateAttendee) error
|
Update(attendee *TrainingDateAttendee) error
|
||||||
Delete(id TrainingDateAttendeeID) error
|
Delete(id TrainingDateAttendeeID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type InMemoryTrainingDateAttendeeRepository struct {
|
|
||||||
attendees map[TrainingDateAttendeeID]TrainingDateAttendee
|
|
||||||
dateToAttendees map[TrainingDateID][]TrainingDateAttendeeID
|
|
||||||
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInMemoryTrainingDateAttendeeRepository() *InMemoryTrainingDateAttendeeRepository {
|
|
||||||
return &InMemoryTrainingDateAttendeeRepository{
|
|
||||||
attendees: make(map[TrainingDateAttendeeID]TrainingDateAttendee),
|
|
||||||
dateToAttendees: make(map[TrainingDateID][]TrainingDateAttendeeID),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateAttendeeRepository) Create(trainingDateID TrainingDateID, attendee *TrainingDateAttendee) error {
|
|
||||||
r.lock.Lock()
|
|
||||||
defer r.lock.Unlock()
|
|
||||||
|
|
||||||
attendee.ID = NewTrainingDateAttendeeID()
|
|
||||||
attendee.trainingDateID = trainingDateID
|
|
||||||
|
|
||||||
r.attendees[attendee.ID] = *attendee
|
|
||||||
r.dateToAttendees[trainingDateID] = append(r.dateToAttendees[trainingDateID], attendee.ID)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateAttendeeRepository) FindByID(id TrainingDateAttendeeID) (*TrainingDateAttendee, error) {
|
|
||||||
r.lock.RLock()
|
|
||||||
defer r.lock.RUnlock()
|
|
||||||
|
|
||||||
attendee, ok := r.attendees[id]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrTrainingDateAttendeeNotFound
|
|
||||||
}
|
|
||||||
return &attendee, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateAttendeeRepository) FindAll() ([]TrainingDateAttendee, error) {
|
|
||||||
r.lock.RLock()
|
|
||||||
defer r.lock.RUnlock()
|
|
||||||
|
|
||||||
attendees := make([]TrainingDateAttendee, len(r.attendees))
|
|
||||||
for _, attendee := range r.attendees {
|
|
||||||
attendees = append(attendees, attendee)
|
|
||||||
}
|
|
||||||
|
|
||||||
return attendees, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateAttendeeRepository) FindAllByTrainingDateID(trainingDateID TrainingDateID) ([]TrainingDateAttendee, error) {
|
|
||||||
r.lock.RLock()
|
|
||||||
defer r.lock.RUnlock()
|
|
||||||
|
|
||||||
attendees := make([]TrainingDateAttendee, 0)
|
|
||||||
for _, id := range r.dateToAttendees[trainingDateID] {
|
|
||||||
attendees = append(attendees, r.attendees[id])
|
|
||||||
}
|
|
||||||
|
|
||||||
return attendees, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateAttendeeRepository) Update(attendee *TrainingDateAttendee) error {
|
|
||||||
r.lock.Lock()
|
|
||||||
defer r.lock.Unlock()
|
|
||||||
|
|
||||||
oldAttendee := r.attendees[attendee.ID]
|
|
||||||
attendee.trainingDateID = oldAttendee.trainingDateID
|
|
||||||
r.attendees[attendee.ID] = *attendee
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *InMemoryTrainingDateAttendeeRepository) Delete(id TrainingDateAttendeeID) error {
|
|
||||||
r.lock.Lock()
|
|
||||||
defer r.lock.Unlock()
|
|
||||||
|
|
||||||
attendee, ok := r.attendees[id]
|
|
||||||
if !ok {
|
|
||||||
return ErrTrainingDateAttendeeNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(r.attendees, id)
|
|
||||||
attendees := r.dateToAttendees[attendee.trainingDateID]
|
|
||||||
for idx, a := range attendees {
|
|
||||||
if a == id {
|
|
||||||
r.dateToAttendees[attendee.trainingDateID] = append(attendees[:idx], attendees[idx+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Reference in a new issue