feat: add basic app
This commit is contained in:
parent
d4c1af4831
commit
c94098afef
13 changed files with 1850 additions and 0 deletions
7
internal/training/errors.go
Normal file
7
internal/training/errors.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package training
|
||||
|
||||
type ErrNotFound struct{}
|
||||
|
||||
func (ErrNotFound) Error() string {
|
||||
return "not found"
|
||||
}
|
||||
105
internal/training/inmemory_repository.go
Normal file
105
internal/training/inmemory_repository.go
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package training
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type InMemoryRepository struct {
|
||||
m sync.Mutex
|
||||
nextID ID
|
||||
|
||||
trainings map[ID]*Training
|
||||
}
|
||||
|
||||
type InMemoryPricingRepository struct{}
|
||||
|
||||
func NewInMemoryRepository() *InMemoryRepository {
|
||||
return &InMemoryRepository{
|
||||
m: sync.Mutex{},
|
||||
nextID: 1,
|
||||
trainings: make(map[ID]*Training),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InMemoryRepository) FindAll(_ context.Context) ([]Training, error) {
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
var trainings []Training
|
||||
for _, t := range r.trainings {
|
||||
trainings = append(trainings, *t)
|
||||
}
|
||||
|
||||
return trainings, nil
|
||||
}
|
||||
|
||||
func (r *InMemoryRepository) FindByID(_ context.Context, id ID) (*Training, error) {
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
t, ok := r.trainings[id]
|
||||
if !ok {
|
||||
return nil, &ErrNotFound{}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (r *InMemoryRepository) Create(_ context.Context, training *Training) error {
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
training.id = r.nextID
|
||||
r.nextID++
|
||||
r.trainings[training.id] = training
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InMemoryRepository) Update(_ context.Context, training *Training) error {
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
if _, ok := r.trainings[training.id]; !ok {
|
||||
return &ErrNotFound{}
|
||||
}
|
||||
|
||||
r.trainings[training.id] = training
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InMemoryRepository) Publish(_ context.Context, id ID) error {
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
t, ok := r.trainings[id]
|
||||
if !ok {
|
||||
return &ErrNotFound{}
|
||||
}
|
||||
t.published = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InMemoryRepository) Unpublish(_ context.Context, id ID) error {
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
t, ok := r.trainings[id]
|
||||
if !ok {
|
||||
return &ErrNotFound{}
|
||||
}
|
||||
t.published = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InMemoryRepository) Retire(_ context.Context, id ID) error {
|
||||
r.m.Lock()
|
||||
defer r.m.Unlock()
|
||||
|
||||
t, ok := r.trainings[id]
|
||||
if !ok {
|
||||
return &ErrNotFound{}
|
||||
}
|
||||
t.retired = true
|
||||
return nil
|
||||
}
|
||||
76
internal/training/model.go
Normal file
76
internal/training/model.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package training
|
||||
|
||||
import (
|
||||
"github.com/shopspring/decimal"
|
||||
"gitlab.mareshq.com/hq/backoffice/backoffice-api/internal/currency"
|
||||
)
|
||||
|
||||
type ID = int
|
||||
|
||||
type Training struct {
|
||||
id ID // unique and auto-incrementing
|
||||
published bool
|
||||
retired bool
|
||||
|
||||
days int8
|
||||
|
||||
// In the future, this should be localized
|
||||
name string
|
||||
pricing []Price `db:"-"`
|
||||
}
|
||||
|
||||
type Price struct {
|
||||
Amount decimal.Decimal
|
||||
Currency currency.Currency
|
||||
Type PriceType
|
||||
}
|
||||
|
||||
type PriceType string
|
||||
|
||||
const (
|
||||
OpenTrainingPriceType PriceType = "OPEN"
|
||||
CorporateTrainingPriceType PriceType = "CORPORATE"
|
||||
)
|
||||
|
||||
func NewTraining(name string, days int8, pricing []Price) *Training {
|
||||
t := &Training{
|
||||
// metadata
|
||||
published: false,
|
||||
retired: false,
|
||||
|
||||
// data
|
||||
name: name,
|
||||
days: days,
|
||||
pricing: make([]Price, 0),
|
||||
}
|
||||
|
||||
if pricing != nil {
|
||||
t.pricing = pricing
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Training) ID() ID {
|
||||
return t.id
|
||||
}
|
||||
|
||||
func (t *Training) Days() int8 {
|
||||
return t.days
|
||||
}
|
||||
|
||||
func (t *Training) Published() bool {
|
||||
return t.published
|
||||
}
|
||||
|
||||
func (t *Training) Retired() bool {
|
||||
return t.retired
|
||||
}
|
||||
|
||||
func (t *Training) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t *Training) Pricing() []Price {
|
||||
return t.pricing
|
||||
}
|
||||
129
internal/training/postgres_repository.go
Normal file
129
internal/training/postgres_repository.go
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package training
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"log"
|
||||
)
|
||||
|
||||
type PostgresRepository struct {
|
||||
pg *pgxpool.Pool
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewPostgresRepository(logger *log.Logger, pg *pgxpool.Pool) *PostgresRepository {
|
||||
return &PostgresRepository{
|
||||
pg: pg,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *PostgresRepository) Create(ctx context.Context, training *Training) error {
|
||||
tx, txErr := r.pg.Begin(ctx)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
|
||||
queryErr := tx.QueryRow(ctx, `
|
||||
INSERT INTO training.trainings (name, days, published)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id
|
||||
`, training.name, training.days, training.published).Scan(&training.id)
|
||||
if queryErr != nil {
|
||||
return queryErr
|
||||
}
|
||||
|
||||
// TODO: insert pricing using the transaction (tx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgresRepository) Update(ctx context.Context, training *Training) error {
|
||||
_, err := r.pg.Exec(ctx, `
|
||||
UPDATE training.trainings
|
||||
SET title = $2
|
||||
WHERE id = $1
|
||||
`, training.ID, training.name)
|
||||
if err != nil {
|
||||
r.logger.Printf("error: %v\n", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgresRepository) FindByID(ctx context.Context, id ID) (*Training, error) {
|
||||
var t Training
|
||||
err := r.pg.QueryRow(ctx, `
|
||||
SELECT id, title, published, retired
|
||||
FROM training.trainings
|
||||
WHERE id = $1
|
||||
`, id).Scan(&t.id, &t.name, &t.published, &t.retired)
|
||||
if err != nil {
|
||||
r.logger.Printf("error: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
func (r *PostgresRepository) FindAll(ctx context.Context) ([]Training, error) {
|
||||
rows, err := r.pg.Query(ctx, `
|
||||
SELECT id, title, published, retired
|
||||
FROM training.trainings
|
||||
`)
|
||||
if err != nil {
|
||||
r.logger.Printf("error: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var trainings []Training
|
||||
for rows.Next() {
|
||||
var t Training
|
||||
err := rows.Scan(&t.id, &t.name, &t.published, &t.retired)
|
||||
if err != nil {
|
||||
r.logger.Printf("error: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
trainings = append(trainings, t)
|
||||
}
|
||||
return trainings, nil
|
||||
}
|
||||
|
||||
func (r *PostgresRepository) Publish(ctx context.Context, id ID) error {
|
||||
_, err := r.pg.Exec(ctx, `
|
||||
UPDATE training.trainings
|
||||
SET published = true
|
||||
WHERE id = $1
|
||||
`, id)
|
||||
if err != nil {
|
||||
r.logger.Printf("error: %v\n", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgresRepository) Unpublish(ctx context.Context, id ID) error {
|
||||
_, err := r.pg.Exec(ctx, `
|
||||
UPDATE training.trainings
|
||||
SET published = false
|
||||
WHERE id = $1
|
||||
`, id)
|
||||
if err != nil {
|
||||
r.logger.Printf("error: %v\n", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostgresRepository) Retire(ctx context.Context, id ID) error {
|
||||
_, err := r.pg.Exec(ctx, `
|
||||
UPDATE training.trainings
|
||||
SET retired = true
|
||||
WHERE id = $1
|
||||
`, id)
|
||||
if err != nil {
|
||||
r.logger.Printf("error: %v\n", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
21
internal/training/repository.go
Normal file
21
internal/training/repository.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package training
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
FindAll(ctx context.Context) ([]Training, error)
|
||||
FindByID(ctx context.Context, id ID) (*Training, error)
|
||||
Create(ctx context.Context, training *Training) error
|
||||
Update(ctx context.Context, training *Training) error
|
||||
Publish(ctx context.Context, id ID) error
|
||||
Unpublish(ctx context.Context, id ID) error
|
||||
Retire(ctx context.Context, id ID) error
|
||||
}
|
||||
|
||||
type PricingRepository interface {
|
||||
UpdateForCurrency(ctx context.Context, trainingID ID, currency string, price float64) error
|
||||
AddCurrency(ctx context.Context, trainingID ID, currency string, price float64) error
|
||||
RemoveCurrency(ctx context.Context, trainingID ID, currency string) error
|
||||
}
|
||||
Reference in a new issue