From e0594892d5261cefa015834ec1437704514ac04e Mon Sep 17 00:00:00 2001 From: Vojtech Mares Date: Thu, 27 Jun 2024 09:07:33 +0200 Subject: [PATCH] feat(training): add Repository.FindBySlug() method --- pkg/training/error.go | 7 ++-- pkg/training/inmemory_repository.go | 29 +++++++++++++++ pkg/training/postgres_repository.go | 55 ++++++++++++++++++++++++++--- pkg/training/repository.go | 1 + 4 files changed, 84 insertions(+), 8 deletions(-) diff --git a/pkg/training/error.go b/pkg/training/error.go index aedd058..39c8eb2 100644 --- a/pkg/training/error.go +++ b/pkg/training/error.go @@ -3,7 +3,8 @@ package training import "errors" var ( - ErrTrainingNotFound = errors.New("training not found") - ErrTrainingDateNotFound = errors.New("training date not found") - ErrTrainingDateAttendeeNotFound = errors.New("training date attendee not found") + ErrTrainingNotFound = errors.New("training not found") + ErrTrainingDateNotFound = errors.New("training date not found") + ErrTrainingDateAttendeeNotFound = errors.New("training date attendee not found") + ErrTrainingWithSlugAlreadyExists = errors.New("training with slug already exists") ) diff --git a/pkg/training/inmemory_repository.go b/pkg/training/inmemory_repository.go index 9aa6f9b..0f382aa 100644 --- a/pkg/training/inmemory_repository.go +++ b/pkg/training/inmemory_repository.go @@ -8,6 +8,7 @@ import ( type InMemoryTrainingRepository struct { trainings map[ID]Training + slugToID map[string]ID lock sync.RWMutex ai int } @@ -15,6 +16,7 @@ type InMemoryTrainingRepository struct { func NewInMemoryTrainingRepository() *InMemoryTrainingRepository { return &InMemoryTrainingRepository{ trainings: make(map[ID]Training), + slugToID: make(map[string]ID), ai: 1, } } @@ -35,7 +37,12 @@ func (r *InMemoryTrainingRepository) Create(training *Training) error { } } + if _, ok := r.slugToID[training.Slug]; ok { + return ErrTrainingWithSlugAlreadyExists + } + r.trainings[training.ID] = *training + r.slugToID[training.Slug] = training.ID return nil } @@ -50,6 +57,18 @@ func (r *InMemoryTrainingRepository) FindByID(id ID) (*Training, error) { return &training, nil } +func (r *InMemoryTrainingRepository) FindBySlug(slug string) (*Training, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + id, ok := r.slugToID[slug] + if !ok { + return nil, ErrTrainingNotFound + } + + return r.FindByID(id) +} + func (r *InMemoryTrainingRepository) FindAll() ([]Training, error) { r.lock.RLock() defer r.lock.RUnlock() @@ -74,7 +93,16 @@ func (r *InMemoryTrainingRepository) Update(training *Training) error { } } + if _, ok := r.slugToID[training.Slug]; ok { + return ErrTrainingWithSlugAlreadyExists + } + r.trainings[training.ID] = *training + + if r.trainings[training.ID].Slug != training.Slug { + delete(r.slugToID, r.trainings[training.ID].Slug) + r.slugToID[training.Slug] = training.ID + } return nil } @@ -87,6 +115,7 @@ func (r *InMemoryTrainingRepository) Delete(id ID) error { return ErrTrainingNotFound } + delete(r.slugToID, r.trainings[id].Slug) delete(r.trainings, id) return nil } diff --git a/pkg/training/postgres_repository.go b/pkg/training/postgres_repository.go index 615da33..5300d4b 100644 --- a/pkg/training/postgres_repository.go +++ b/pkg/training/postgres_repository.go @@ -4,7 +4,7 @@ import ( "context" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" - "gitlab.mareshq.com/hq/yggdrasil/pkg/slug" + slugpkg "gitlab.mareshq.com/hq/yggdrasil/pkg/slug" "time" ) @@ -21,7 +21,7 @@ func (r *PostgresTrainingRepository) Create(training *Training) error { defer cancel() if training.Slug == "" { - training.Slug = slug.NewString(training.Name) + training.Slug = slugpkg.NewString(training.Name) } tx, txErr := r.pg.Begin(ctx) @@ -77,7 +77,7 @@ func (r *PostgresTrainingRepository) FindByID(id ID) (*Training, error) { var training Training err := r.pg.QueryRow(ctx, ` - SELECT id, name, description, days + SELECT id, name, slug, description, days FROM training.trainings WHERE id = $1 `, id).Scan(&training.ID, &training.Name, &training.Slug, &training.Description, &training.Days) @@ -112,6 +112,51 @@ func (r *PostgresTrainingRepository) FindByID(id ID) (*Training, error) { return &training, nil } +func (r *PostgresTrainingRepository) FindBySlug(slug string) (*Training, error) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := slugpkg.Validate(slug); err != nil { + return nil, ErrTrainingNotFound + } + + var training Training + err := r.pg.QueryRow(ctx, ` + SELECT id, name, slug, description, days + FROM training.trainings + WHERE slug = $1 + `, slug).Scan(&training.ID, &training.Name, &training.Slug, &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 + `, training.ID) + if err != nil { + return nil, err + } + defer rows.Close() + + training.Pricing = make([]Price, 0) + training.Pricing, err = pgx.CollectRows[Price](rows, func(row pgx.CollectableRow) (Price, error) { + var price Price + err := row.Scan(&price.Amount, &price.Currency, &price.Type) + if err != nil { + return Price{}, 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() @@ -157,9 +202,9 @@ func (r *PostgresTrainingRepository) Update(training *Training) error { defer cancel() if training.Slug == "" { - training.Slug = slug.NewString(training.Name) + training.Slug = slugpkg.NewString(training.Name) } else { - slugValidateErr := slug.Validate(training.Slug) + slugValidateErr := slugpkg.Validate(training.Slug) if slugValidateErr != nil { return slugValidateErr } diff --git a/pkg/training/repository.go b/pkg/training/repository.go index 00de3ef..0e695cf 100644 --- a/pkg/training/repository.go +++ b/pkg/training/repository.go @@ -3,6 +3,7 @@ package training type Repository interface { Create(training *Training) error FindByID(id ID) (*Training, error) + FindBySlug(slug string) (*Training, error) FindAll() ([]Training, error) Update(training *Training) error Delete(id ID) error