diff --git a/cmd/port-service/main.go b/cmd/port-service/main.go index 7be56ba..52a5353 100644 --- a/cmd/port-service/main.go +++ b/cmd/port-service/main.go @@ -9,9 +9,10 @@ import ( "syscall" "time" - "github.com/evt/port-api/config" - "github.com/evt/port-api/internal/app" - "github.com/evt/port-api/internal/app/ui" + "github.com/evt/port-api/internal/config" + "github.com/evt/port-api/internal/repository/inmem" + "github.com/evt/port-api/internal/services" + "github.com/evt/port-api/internal/transport" "github.com/gorilla/mux" ) @@ -26,14 +27,14 @@ func run() error { // read config from env cfg := config.Read() - // default context - ctx := context.Background() + // create port repository + portStoreRepo := inmem.NewPortStore() - // create application - application := app.New(ctx) + // create port service + portService := services.NewPortService(portStoreRepo) // create http server with application injected - httpServer := ui.NewHttpServer(application) + httpServer := transport.NewHttpServer(portService) // create http router router := mux.NewRouter() diff --git a/internal/app/adapters/model.go b/internal/app/adapters/model.go deleted file mode 100644 index 5ebf247..0000000 --- a/internal/app/adapters/model.go +++ /dev/null @@ -1,20 +0,0 @@ -package adapters - -import "time" - -type PortModel struct { - ID string - Name string - Code string - City string - Country string - Alias []any - Regions []any - Coordinates []float64 - Province string - Timezone string - Unlocs []string - - CreatedAt time.Time - UpdatedAt time.Time -} diff --git a/internal/app/adapters/port_store_inmem.go b/internal/app/adapters/port_store_inmem.go deleted file mode 100644 index ad9cf05..0000000 --- a/internal/app/adapters/port_store_inmem.go +++ /dev/null @@ -1,109 +0,0 @@ -package adapters - -import ( - "context" - "errors" - "sync" - "time" - - "github.com/evt/port-api/internal/app/store" -) - -var ( - ErrNotFound = errors.New("not found") - ErrNil = errors.New("nil data") -) - -type PortStoreInMem struct { - data map[string]*store.Port - mu sync.RWMutex -} - -func NewPortStoreInMem() *PortStoreInMem { - return &PortStoreInMem{ - data: make(map[string]*store.Port), - } -} - -func (s *PortStoreInMem) GetPort(_ context.Context, id string) (*store.Port, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - storePort, exists := s.data[id] - if !exists { - return nil, ErrNotFound - } - - return storePort.Copy(), nil -} - -func (s *PortStoreInMem) CountPorts(_ context.Context) (int, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - return len(s.data), nil -} - -func (s *PortStoreInMem) CreateOrUpdatePort(ctx context.Context, p *store.Port) error { - if p == nil { - return ErrNil - } - - s.mu.Lock() - defer s.mu.Unlock() - - _, exists := s.data[p.ID] - if exists { - return s.updatePort(ctx, p) - } else { - return s.createPort(ctx, p) - } -} - -func (s *PortStoreInMem) createPort(_ context.Context, p *store.Port) error { - if p == nil { - return ErrNil - } - - storePort := p.Copy() - - // set created and updated at - storePort.CreatedAt = time.Now() - storePort.UpdatedAt = storePort.CreatedAt - - s.data[p.ID] = storePort - - return nil -} - -func (s *PortStoreInMem) updatePort(_ context.Context, p *store.Port) error { - if p == nil { - return ErrNil - } - - // check if port exists - storePort, exists := s.data[p.ID] - if !exists { - return ErrNotFound - } - - storePortCopy := storePort.Copy() - - storePortCopy.Name = p.Name - storePortCopy.Code = p.Code - storePortCopy.City = p.City - storePortCopy.Country = p.Country - storePortCopy.Alias = append([]any(nil), p.Alias...) - storePortCopy.Regions = append([]any(nil), p.Regions...) - storePortCopy.Coordinates = append([]float64(nil), p.Coordinates...) - storePortCopy.Province = p.Province - storePortCopy.Timezone = p.Timezone - storePortCopy.Unlocs = append([]string(nil), p.Unlocs...) - - // set updated at - storePortCopy.UpdatedAt = time.Now() - - s.data[p.ID] = storePortCopy - - return nil -} diff --git a/internal/app/adapters/port_store_inmem_test.go b/internal/app/adapters/port_store_inmem_test.go deleted file mode 100644 index b8c0bdc..0000000 --- a/internal/app/adapters/port_store_inmem_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package adapters_test - -import ( - "context" - "testing" - - "github.com/evt/port-api/internal/app/adapters" - "github.com/evt/port-api/internal/app/store" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -func TestPortStore_CreateOrUpdatePort(t *testing.T) { - t.Parallel() - store := adapters.NewPortStoreInMem() - - t.Run("create port", func(t *testing.T) { - t.Parallel() - - port := newRandomPort(t) - - err := store.CreateOrUpdatePort(context.Background(), port) - require.NoError(t, err) - - port, err = store.GetPort(context.Background(), port.ID) - require.NoError(t, err) - - require.Equal(t, port.CreatedAt, port.UpdatedAt) - }) - t.Run("update port", func(t *testing.T) { - t.Parallel() - - port := newRandomPort(t) - - err := store.CreateOrUpdatePort(context.Background(), port) - require.NoError(t, err) - - beforeUpdatePort, err := store.GetPort(context.Background(), port.ID) - require.NoError(t, err) - - port.Name = "updated name" - - err = store.CreateOrUpdatePort(context.Background(), port) - require.NoError(t, err) - - updatedPort, err := store.GetPort(context.Background(), port.ID) - require.NoError(t, err) - - require.NotEqual(t, beforeUpdatePort.Name, updatedPort.Name) - }) - t.Run("nil port", func(t *testing.T) { - t.Parallel() - err := store.CreateOrUpdatePort(context.Background(), nil) - require.ErrorIs(t, err, adapters.ErrNil) - }) -} - -func newRandomPort(t *testing.T) *store.Port { - t.Helper() - randomID := uuid.New().String() - port := &store.Port{ - ID: randomID, - Name: randomID, - City: randomID, - Country: randomID, - Alias: []any{randomID}, - Regions: []any{randomID}, - Coordinates: []float64{1.0, 2.0}, - Province: randomID, - Timezone: randomID, - } - return port -} diff --git a/internal/app/app.go b/internal/app/app.go deleted file mode 100644 index af41221..0000000 --- a/internal/app/app.go +++ /dev/null @@ -1,46 +0,0 @@ -package app - -import ( - "context" - - "github.com/evt/port-api/internal/app/adapters" - "github.com/evt/port-api/internal/app/command" - "github.com/evt/port-api/internal/app/query" - "github.com/evt/port-api/internal/domain" -) - -type CommandHandler[C any] interface { - Handle(ctx context.Context, cmd C) error -} - -type QueryHandler[Q any, R any] interface { - Handle(ctx context.Context, q Q) (R, error) -} - -type Application struct { - Commands Commands - Queries Queries -} - -type Commands struct { - CreateOrUpdatePort CommandHandler[command.CreateOrUpdatePort] -} - -type Queries struct { - GetPort QueryHandler[query.GetPort, *domain.Port] - CountPorts QueryHandler[query.CountPorts, int] -} - -func New(_ context.Context) *Application { - portStore := adapters.NewPortStoreInMem() - - return &Application{ - Commands: Commands{ - CreateOrUpdatePort: command.NewCreateOrUpdatePortHandler(portStore), - }, - Queries: Queries{ - GetPort: query.NewGetPortHandler(portStore), - CountPorts: query.NewCountPortsHandler(portStore), - }, - } -} diff --git a/internal/app/command/create_or_update_port.go b/internal/app/command/create_or_update_port.go deleted file mode 100644 index 2558dbb..0000000 --- a/internal/app/command/create_or_update_port.go +++ /dev/null @@ -1,36 +0,0 @@ -package command - -import ( - "context" - - "github.com/evt/port-api/internal/app/store" - "github.com/evt/port-api/internal/domain" -) - -type CreateOrUpdatePort struct { - Port *domain.Port -} - -type CreateOrUpdatePortHandler struct { - store CreateOrUpdatePortStore -} - -func NewCreateOrUpdatePortHandler(store CreateOrUpdatePortStore) *CreateOrUpdatePortHandler { - if store == nil { - panic("create or update port store is not defined") - } - - return &CreateOrUpdatePortHandler{store: store} -} - -type CreateOrUpdatePortStore interface { - CreateOrUpdatePort(ctx context.Context, port *store.Port) error -} - -func (h *CreateOrUpdatePortHandler) Handle(ctx context.Context, cmd CreateOrUpdatePort) error { - // convert domain port to db port - dbPort := store.DomainToPort(cmd.Port) - - // create or update port in the store - return h.store.CreateOrUpdatePort(ctx, dbPort) -} diff --git a/internal/app/command/create_or_update_port_integration_test.go b/internal/app/command/create_or_update_port_integration_test.go deleted file mode 100644 index 358623e..0000000 --- a/internal/app/command/create_or_update_port_integration_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package command_test - -import ( - "context" - "testing" - - "github.com/evt/port-api/internal/app/adapters" - "github.com/evt/port-api/internal/app/command" - "github.com/evt/port-api/internal/app/store" - "github.com/evt/port-api/internal/domain" - "github.com/stretchr/testify/require" -) - -func TestCreateOrUpdatePort(t *testing.T) { - t.Parallel() - - portStore := adapters.NewPortStoreInMem() - handler := command.NewCreateOrUpdatePortHandler(portStore) - - port, err := domain.NewPort("port id", "port name", "port code", "port city", - "port country", []any{"alias1", "alias2"}, []any{"region 1", "region 2"}, []float64{1.0, 2.0}, - "port province", "port tz", []string{"unlock 1", "unlock 2"}) - require.NoError(t, err) - - err = handler.Handle(context.Background(), command.CreateOrUpdatePort{Port: port}) - require.NoError(t, err) - - storePort, err := portStore.GetPort(context.Background(), port.ID()) - require.NoError(t, err) - - portFromStore, err := store.PortToDomain(storePort) - require.NoError(t, err) - - require.Equal(t, portFromStore, port) -} diff --git a/internal/app/query/count_ports.go b/internal/app/query/count_ports.go deleted file mode 100644 index a278e67..0000000 --- a/internal/app/query/count_ports.go +++ /dev/null @@ -1,27 +0,0 @@ -package query - -import ( - "context" -) - -type CountPorts struct{} - -type CountPortsHandler struct { - store CountPortsStore -} - -func NewCountPortsHandler(store CountPortsStore) *CountPortsHandler { - if store == nil { - panic("count ports store is not defined") - } - - return &CountPortsHandler{store: store} -} - -type CountPortsStore interface { - CountPorts(ctx context.Context) (int, error) -} - -func (h *CountPortsHandler) Handle(ctx context.Context, _ CountPorts) (int, error) { - return h.store.CountPorts(ctx) -} diff --git a/internal/app/query/get_port.go b/internal/app/query/get_port.go deleted file mode 100644 index 60220c6..0000000 --- a/internal/app/query/get_port.go +++ /dev/null @@ -1,45 +0,0 @@ -package query - -import ( - "context" - "fmt" - - "github.com/evt/port-api/internal/app/store" - "github.com/evt/port-api/internal/domain" -) - -type GetPort struct { - ID string -} - -type GetPortHandler struct { - store GetPortStore -} - -func NewGetPortHandler(store GetPortStore) *GetPortHandler { - if store == nil { - panic("get port store is not defined") - } - - return &GetPortHandler{store: store} -} - -type GetPortStore interface { - GetPort(ctx context.Context, id string) (*store.Port, error) -} - -func (h *GetPortHandler) Handle(ctx context.Context, q GetPort) (*domain.Port, error) { - // get port from the store - dbPort, err := h.store.GetPort(ctx, q.ID) - if err != nil { - return nil, fmt.Errorf("store.GetPort failed: %w", err) - } - - // convert to domain port - port, err := store.PortToDomain(dbPort) - if err != nil { - return nil, fmt.Errorf("store.PortToDomain failed: %w", err) - } - - return port, nil -} diff --git a/config/config.go b/internal/config/config.go similarity index 100% rename from config/config.go rename to internal/config/config.go diff --git a/internal/domain/errors.go b/internal/domain/errors.go index 272a16a..507d592 100644 --- a/internal/domain/errors.go +++ b/internal/domain/errors.go @@ -4,4 +4,6 @@ import "errors" var ( ErrRequired = errors.New("required value") + ErrNotFound = errors.New("not found") + ErrNil = errors.New("nil data") ) diff --git a/internal/domain/port.go b/internal/domain/port.go index 74e0d03..4d9bfb4 100644 --- a/internal/domain/port.go +++ b/internal/domain/port.go @@ -1,6 +1,8 @@ package domain -import "fmt" +import ( + "fmt" +) // Port is a domain port. type Port struct { @@ -9,8 +11,8 @@ type Port struct { code string city string country string - alias []any - regions []any + alias []string + regions []string coordinates []float64 province string timezone string @@ -18,7 +20,7 @@ type Port struct { } // NewPort creates a new port. -func NewPort(id, name, code, city, country string, alias, regions []any, coords []float64, +func NewPort(id, name, code, city, country string, alias, regions []string, coords []float64, province, tz string, unlocs []string) (*Port, error) { if id == "" { return nil, fmt.Errorf("%w: port id is required", ErrRequired) @@ -58,6 +60,15 @@ func (p *Port) Name() string { return p.name } +// SetName sets the port name. +func (p *Port) SetName(name string) error { + if name == "" { + return fmt.Errorf("%w: port name is required", ErrRequired) + } + p.name = name + return nil +} + // Code returns the port code. func (p *Port) Code() string { return p.code @@ -74,12 +85,12 @@ func (p *Port) Country() string { } // Alias returns the port alias. -func (p *Port) Alias() []any { +func (p *Port) Alias() []string { return p.alias } // Regions returns the port regions. -func (p *Port) Regions() []any { +func (p *Port) Regions() []string { return p.regions } diff --git a/internal/domain/port_test.go b/internal/domain/port_test.go index d296d91..0c7095c 100644 --- a/internal/domain/port_test.go +++ b/internal/domain/port_test.go @@ -1,9 +1,8 @@ -package domain_test +package domain import ( "testing" - "github.com/evt/port-api/internal/domain" "github.com/stretchr/testify/require" ) @@ -17,7 +16,7 @@ func TestNewPort(t *testing.T) { portCountry := "port country" t.Run("valid", func(t *testing.T) { - port, err := domain.NewPort(portID, portName, portCode, portCity, portCountry, + port, err := NewPort(portID, portName, portCode, portCity, portCountry, nil, nil, nil, "", "", nil) require.NoError(t, err) @@ -29,25 +28,25 @@ func TestNewPort(t *testing.T) { }) t.Run("missing port ID", func(t *testing.T) { - _, err := domain.NewPort("", portName, portCode, portCity, portCountry, + _, err := NewPort("", portName, portCode, portCity, portCountry, nil, nil, nil, "", "", nil) require.Error(t, err) }) t.Run("missing port name", func(t *testing.T) { - _, err := domain.NewPort(portID, "", portCode, portCity, portCountry, + _, err := NewPort(portID, "", portCode, portCity, portCountry, nil, nil, nil, "", "", nil) require.Error(t, err) }) t.Run("missing port city", func(t *testing.T) { - _, err := domain.NewPort(portID, portName, portCode, "", portCountry, + _, err := NewPort(portID, portName, portCode, "", portCountry, nil, nil, nil, "", "", nil) require.Error(t, err) }) t.Run("missing port country", func(t *testing.T) { - _, err := domain.NewPort(portID, portName, portCode, portCity, "", + _, err := NewPort(portID, portName, portCode, portCity, "", nil, nil, nil, "", "", nil) require.Error(t, err) }) diff --git a/internal/app/store/port.go b/internal/repository/inmem/port.go similarity index 68% rename from internal/app/store/port.go rename to internal/repository/inmem/port.go index 11e9cfb..046bf3e 100644 --- a/internal/app/store/port.go +++ b/internal/repository/inmem/port.go @@ -1,6 +1,7 @@ -package store +package inmem import ( + "errors" "time" "github.com/evt/port-api/internal/domain" @@ -12,8 +13,8 @@ type Port struct { Code string City string Country string - Alias []any - Regions []any + Alias []string + Regions []string Coordinates []float64 Province string Timezone string @@ -24,14 +25,17 @@ type Port struct { } func (p *Port) Copy() *Port { + if p == nil { + return nil + } return &Port{ ID: p.ID, Name: p.Name, Code: p.Code, City: p.City, Country: p.Country, - Alias: append([]any(nil), p.Alias...), - Regions: append([]any(nil), p.Regions...), + Alias: append([]string(nil), p.Alias...), + Regions: append([]string(nil), p.Regions...), Coordinates: append([]float64(nil), p.Coordinates...), Province: p.Province, Timezone: p.Timezone, @@ -41,15 +45,18 @@ func (p *Port) Copy() *Port { } } -func PortToDomain(p *Port) (*domain.Port, error) { +func portStoreToDomain(p *Port) (*domain.Port, error) { + if p == nil { + return nil, errors.New("store port is nil") + } return domain.NewPort( p.ID, p.Name, p.Code, p.City, p.Country, - append([]any(nil), p.Alias...), - append([]any(nil), p.Regions...), + append([]string(nil), p.Alias...), + append([]string(nil), p.Regions...), append([]float64(nil), p.Coordinates...), p.Province, p.Timezone, @@ -57,15 +64,15 @@ func PortToDomain(p *Port) (*domain.Port, error) { ) } -func DomainToPort(p *domain.Port) *Port { +func portDomainToStore(p *domain.Port) *Port { return &Port{ ID: p.ID(), Name: p.Name(), Code: p.Code(), City: p.City(), Country: p.Country(), - Alias: append([]any(nil), p.Alias()...), - Regions: append([]any(nil), p.Regions()...), + Alias: append([]string(nil), p.Alias()...), + Regions: append([]string(nil), p.Regions()...), Coordinates: append([]float64(nil), p.Coordinates()...), Province: p.Province(), Timezone: p.Timezone(), diff --git a/internal/repository/inmem/port_store_inmem.go b/internal/repository/inmem/port_store_inmem.go new file mode 100644 index 0000000..a3bf7eb --- /dev/null +++ b/internal/repository/inmem/port_store_inmem.go @@ -0,0 +1,109 @@ +package inmem + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/evt/port-api/internal/domain" +) + +type PortStore struct { + data map[string]*Port + mu sync.RWMutex +} + +func NewPortStore() *PortStore { + return &PortStore{ + data: make(map[string]*Port), + } +} + +func (s *PortStore) GetPort(_ context.Context, id string) (*domain.Port, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + storePort, exists := s.data[id] + if !exists { + return nil, domain.ErrNotFound + } + + domainPort, err := portStoreToDomain(storePort) + if err != nil { + return nil, fmt.Errorf("portStoreToDomain failed: %w", err) + } + + return domainPort, nil +} + +func (s *PortStore) CountPorts(_ context.Context) (int, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + return len(s.data), nil +} + +func (s *PortStore) CreateOrUpdatePort(ctx context.Context, p *domain.Port) error { + if p == nil { + return domain.ErrNil + } + + storePort := portDomainToStore(p) + + s.mu.Lock() + defer s.mu.Unlock() + + _, exists := s.data[storePort.ID] + if exists { + return s.updatePort(ctx, storePort) + } else { + return s.createPort(ctx, storePort) + } +} + +func (s *PortStore) createPort(_ context.Context, storePort *Port) error { + if storePort == nil { + return domain.ErrNil + } + + // set created and updated at + storePort.CreatedAt = time.Now() + storePort.UpdatedAt = storePort.CreatedAt + + s.data[storePort.ID] = storePort + + return nil +} + +func (s *PortStore) updatePort(_ context.Context, p *Port) error { + if p == nil { + return domain.ErrNil + } + + // check if port exists + storePort, exists := s.data[p.ID] + if !exists { + return domain.ErrNotFound + } + + storePortCopy := storePort.Copy() + + storePortCopy.Name = p.Name + storePortCopy.Code = p.Code + storePortCopy.City = p.City + storePortCopy.Country = p.Country + storePortCopy.Alias = append([]string(nil), p.Alias...) + storePortCopy.Regions = append([]string(nil), p.Regions...) + storePortCopy.Coordinates = append([]float64(nil), p.Coordinates...) + storePortCopy.Province = p.Province + storePortCopy.Timezone = p.Timezone + storePortCopy.Unlocs = append([]string(nil), p.Unlocs...) + + // set updated at + storePortCopy.UpdatedAt = time.Now() + + s.data[p.ID] = storePortCopy + + return nil +} diff --git a/internal/repository/inmem/port_store_inmem_test.go b/internal/repository/inmem/port_store_inmem_test.go new file mode 100644 index 0000000..233eca0 --- /dev/null +++ b/internal/repository/inmem/port_store_inmem_test.go @@ -0,0 +1,68 @@ +package inmem + +import ( + "context" + "testing" + + "github.com/evt/port-api/internal/domain" + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +func TestPortStore_CreateOrUpdatePort(t *testing.T) { + t.Parallel() + store := NewPortStore() + + t.Run("create port", func(t *testing.T) { + t.Parallel() + + randomPort := newRandomDomainPort(t) + + err := store.CreateOrUpdatePort(context.Background(), randomPort) + require.NoError(t, err) + + port, err := store.GetPort(context.Background(), randomPort.ID()) + require.NoError(t, err) + + require.Equal(t, port, randomPort) + }) + t.Run("update port", func(t *testing.T) { + t.Parallel() + + randomPort := newRandomDomainPort(t) + + err := store.CreateOrUpdatePort(context.Background(), randomPort) + require.NoError(t, err) + + beforeUpdatePort, err := store.GetPort(context.Background(), randomPort.ID()) + require.NoError(t, err) + + require.Equal(t, beforeUpdatePort, randomPort) + + err = randomPort.SetName("updated name") + require.NoError(t, err) + + err = store.CreateOrUpdatePort(context.Background(), randomPort) + require.NoError(t, err) + + updatedPort, err := store.GetPort(context.Background(), randomPort.ID()) + require.NoError(t, err) + + require.NotEqual(t, beforeUpdatePort.Name(), updatedPort.Name()) + }) + t.Run("nil port", func(t *testing.T) { + t.Parallel() + err := store.CreateOrUpdatePort(context.Background(), nil) + require.ErrorIs(t, err, domain.ErrNil) + }) +} + +func newRandomDomainPort(t *testing.T) *domain.Port { + t.Helper() + randomID := uuid.New().String() + port, err := domain.NewPort(randomID, randomID, randomID, randomID, randomID, + []string{randomID}, []string{randomID}, []float64{1.0, 2.0}, randomID, randomID, nil) + require.NoError(t, err) + + return port +} diff --git a/internal/repository/inmem/port_test.go b/internal/repository/inmem/port_test.go new file mode 100644 index 0000000..d4f8fec --- /dev/null +++ b/internal/repository/inmem/port_test.go @@ -0,0 +1,79 @@ +package inmem + +import ( + "testing" + "time" + + "github.com/evt/port-api/internal/domain" + "github.com/stretchr/testify/require" +) + +func Test_portStoreToDomain(t *testing.T) { + type args struct { + p *Port + } + tests := []struct { + name string + args args + want *domain.Port + wantErr bool + }{ + { + name: "should return error when store port is nil", + args: args{ + p: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "should return domain port when store port is not nil", + args: args{ + p: newTestStorePort(t), + }, + want: newTestDomainPort(t), + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got, err := portStoreToDomain(tt.args.p) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, got) + } + }) + } +} + +const testString = "test" + +func newTestStorePort(t *testing.T) *Port { + t.Helper() + return &Port{ + ID: testString, + Name: testString, + Code: testString, + City: testString, + Country: testString, + Alias: []string{testString}, + Regions: []string{testString}, + Coordinates: []float64{1.0, 2.0}, + Province: testString, + Timezone: testString, + Unlocs: []string{testString}, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + +func newTestDomainPort(t *testing.T) *domain.Port { + t.Helper() + port, err := domain.NewPort(testString, testString, testString, testString, testString, + []string{testString}, []string{testString}, []float64{1.0, 2.0}, testString, testString, []string{testString}) + require.NoError(t, err) + return port +} diff --git a/internal/services/service.go b/internal/services/service.go new file mode 100644 index 0000000..92aae98 --- /dev/null +++ b/internal/services/service.go @@ -0,0 +1,41 @@ +package services + +import ( + "context" + + "github.com/evt/port-api/internal/domain" +) + +// PortRepository is a port repository for the port service +type PortRepository interface { + CreateOrUpdatePort(ctx context.Context, port *domain.Port) error + CountPorts(ctx context.Context) (int, error) + GetPort(ctx context.Context, id string) (*domain.Port, error) +} + +// PortService is a port service +type PortService struct { + repo PortRepository +} + +// NewPortService creates a new port service +func NewPortService(repo PortRepository) PortService { + return PortService{ + repo: repo, + } +} + +// GetPort returns a port by id +func (s PortService) GetPort(ctx context.Context, id string) (*domain.Port, error) { + return s.repo.GetPort(ctx, id) +} + +// CountPorts returns the number of ports +func (s PortService) CountPorts(ctx context.Context) (int, error) { + return s.repo.CountPorts(ctx) +} + +// CreateOrUpdatePort creates or updates a port +func (s PortService) CreateOrUpdatePort(ctx context.Context, port *domain.Port) error { + return s.repo.CreateOrUpdatePort(ctx, port) +} diff --git a/internal/app/ui/http.go b/internal/transport/http.go similarity index 68% rename from internal/app/ui/http.go rename to internal/transport/http.go index e42fef4..f13a441 100644 --- a/internal/app/ui/http.go +++ b/internal/transport/http.go @@ -1,32 +1,37 @@ -package ui +package transport import ( + "context" "errors" "log" "net/http" - "github.com/evt/port-api/internal/app" - "github.com/evt/port-api/internal/app/adapters" - "github.com/evt/port-api/internal/app/command" - "github.com/evt/port-api/internal/app/query" "github.com/evt/port-api/internal/common/server" + "github.com/evt/port-api/internal/domain" ) +// PortService is a port service +type PortService interface { + GetPort(ctx context.Context, id string) (*domain.Port, error) + CountPorts(ctx context.Context) (int, error) + CreateOrUpdatePort(ctx context.Context, port *domain.Port) error +} + // HttpServer is a HTTP server for ports type HttpServer struct { - app *app.Application + service PortService } // NewHttpServer creates a new HTTP server for ports -func NewHttpServer(application *app.Application) *HttpServer { - return &HttpServer{ - app: application, +func NewHttpServer(service PortService) HttpServer { + return HttpServer{ + service: service, } } // CountPorts returns total ports stored in DB -func (h *HttpServer) CountPorts(w http.ResponseWriter, r *http.Request) { - total, err := h.app.Queries.CountPorts.Handle(r.Context(), query.CountPorts{}) +func (h HttpServer) CountPorts(w http.ResponseWriter, r *http.Request) { + total, err := h.service.CountPorts(r.Context()) if err != nil { server.RespondWithError(err, w, r) return @@ -36,10 +41,10 @@ func (h *HttpServer) CountPorts(w http.ResponseWriter, r *http.Request) { } // GetPort returns a port by ID -func (h *HttpServer) GetPort(w http.ResponseWriter, r *http.Request) { - port, err := h.app.Queries.GetPort.Handle(r.Context(), query.GetPort{ID: r.URL.Query().Get("id")}) +func (h HttpServer) GetPort(w http.ResponseWriter, r *http.Request) { + port, err := h.service.GetPort(r.Context(), r.URL.Query().Get("id")) if err != nil { - if errors.Is(err, adapters.ErrNotFound) { + if errors.Is(err, domain.ErrNotFound) { server.NotFound("port-not-found", err, w, r) return } @@ -64,7 +69,7 @@ func (h *HttpServer) GetPort(w http.ResponseWriter, r *http.Request) { } // UploadPorts reads ports from JSON file and creates/updates them in DB -func (h *HttpServer) UploadPorts(w http.ResponseWriter, r *http.Request) { +func (h HttpServer) UploadPorts(w http.ResponseWriter, r *http.Request) { log.Println("uploading ports") portChan := make(chan Port) @@ -101,8 +106,7 @@ func (h *HttpServer) UploadPorts(w http.ResponseWriter, r *http.Request) { server.BadRequest("port-to-domain", err, w, r) return } - cmd := command.CreateOrUpdatePort{Port: p} - if err := h.app.Commands.CreateOrUpdatePort.Handle(r.Context(), cmd); err != nil { + if err := h.service.CreateOrUpdatePort(r.Context(), p); err != nil { server.RespondWithError(err, w, r) return } diff --git a/internal/app/ui/http_e2e_test.go b/internal/transport/http_e2e_test.go similarity index 79% rename from internal/app/ui/http_e2e_test.go rename to internal/transport/http_e2e_test.go index 00b148b..df8703a 100644 --- a/internal/app/ui/http_e2e_test.go +++ b/internal/transport/http_e2e_test.go @@ -1,4 +1,4 @@ -package ui_test +package transport_test import ( "bytes" @@ -10,30 +10,30 @@ import ( "os" "testing" - "github.com/evt/port-api/internal/app" - "github.com/evt/port-api/internal/app/query" - "github.com/evt/port-api/internal/app/ui" + "github.com/evt/port-api/internal/repository/inmem" + "github.com/evt/port-api/internal/services" + "github.com/evt/port-api/internal/transport" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) type HttpTestSuite struct { suite.Suite - app *app.Application - httpServer *ui.HttpServer + portService transport.PortService + httpServer transport.HttpServer } func NewHttpTestSuite() *HttpTestSuite { - // default context - ctx := context.Background() - suite := &HttpTestSuite{} - // create application - suite.app = app.New(ctx) + // create port repository + portStoreRepo := inmem.NewPortStore() + + // create port service + suite.portService = services.NewPortService(portStoreRepo) // create http server with application injected - suite.httpServer = ui.NewHttpServer(suite.app) + suite.httpServer = transport.NewHttpServer(suite.portService) return suite } @@ -70,7 +70,7 @@ func (suite *HttpTestSuite) TestUploadPorts() { require.Equal(suite.T(), portsResponse, data) // count ports in storage - storedPortsTotal, err := suite.app.Queries.CountPorts.Handle(context.Background(), query.CountPorts{}) + storedPortsTotal, err := suite.portService.CountPorts(context.Background()) require.NoError(suite.T(), err) // compare number of ports in request and storage diff --git a/internal/app/ui/model.go b/internal/transport/model.go similarity index 79% rename from internal/app/ui/model.go rename to internal/transport/model.go index 4147a0f..e3d20a5 100644 --- a/internal/app/ui/model.go +++ b/internal/transport/model.go @@ -1,4 +1,4 @@ -package ui +package transport type Port struct { ID string `json:"id"` @@ -6,8 +6,8 @@ type Port struct { Code string `json:"code"` City string `json:"city"` Country string `json:"country"` - Alias []any `json:"alias"` - Regions []any `json:"regions"` + Alias []string `json:"alias"` + Regions []string `json:"regions"` Coordinates []float64 `json:"coordinates"` Province string `json:"province"` Timezone string `json:"timezone"` diff --git a/internal/app/ui/testfixtures/ports_request.json b/internal/transport/testfixtures/ports_request.json similarity index 100% rename from internal/app/ui/testfixtures/ports_request.json rename to internal/transport/testfixtures/ports_request.json diff --git a/internal/app/ui/testfixtures/ports_response.json b/internal/transport/testfixtures/ports_response.json similarity index 100% rename from internal/app/ui/testfixtures/ports_response.json rename to internal/transport/testfixtures/ports_response.json diff --git a/internal/app/ui/utils.go b/internal/transport/utils.go similarity index 93% rename from internal/app/ui/utils.go rename to internal/transport/utils.go index 178a57c..ee08e54 100644 --- a/internal/app/ui/utils.go +++ b/internal/transport/utils.go @@ -1,4 +1,4 @@ -package ui +package transport import ( "context" @@ -16,8 +16,8 @@ func portHttpToDomain(p *Port) (*domain.Port, error) { p.Code, p.City, p.Country, - append([]any(nil), p.Alias...), - append([]any(nil), p.Regions...), + append([]string(nil), p.Alias...), + append([]string(nil), p.Regions...), append([]float64(nil), p.Coordinates...), p.Province, p.Timezone,