Skip to content

Commit

Permalink
feat: added complete create product
Browse files Browse the repository at this point in the history
feat: added complete create product

with injection

missing unit testing
  • Loading branch information
sebajax committed Feb 21, 2024
1 parent b38129c commit 872df7d
Show file tree
Hide file tree
Showing 15 changed files with 169 additions and 110 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ https://github.com/sebajax/go-vertical-slice-architecture/blob/d4501917930ef2263
### Formatting, Linting and Vetting

```bash
# Clean dependencies
go mod tidy

# Run formating
go fmt ./...

Expand Down
17 changes: 12 additions & 5 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/helmet"
"github.com/sebajax/go-vertical-slice-architecture/internal/user/handler"
producthandler "github.com/sebajax/go-vertical-slice-architecture/internal/product/handler"
userhandler "github.com/sebajax/go-vertical-slice-architecture/internal/user/handler"
"github.com/sebajax/go-vertical-slice-architecture/pkg/injection"
"github.com/sebajax/go-vertical-slice-architecture/pkg/middleware"
)
Expand Down Expand Up @@ -37,10 +38,16 @@ func main() {
return c.SendString("Status ok - api running")
})

// add api group for users
api := app.Group("/api") // /api
userApi := api.Group("/users") // /api/user
handler.UserRouter(userApi, injection.UserServiceProvider)
// create api group
api := app.Group("/api") // /api

// add api group for user
userApi := api.Group("/user") // /api/user
userhandler.UserRouter(userApi, injection.UserServiceProvider)

// add api group for product
productApi := api.Group("/product") // /api/product
producthandler.ProductRouter(productApi, injection.ProductServiceProvider)

// listen in port 8080
log.Fatal(app.Listen(fmt.Sprintf(":%s", os.Getenv("API_PORT"))))
Expand Down
16 changes: 8 additions & 8 deletions internal/product/handler/createProduct.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handler
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/sebajax/go-vertical-slice-architecture/internal/product"
"github.com/sebajax/go-vertical-slice-architecture/internal/product/service"
"github.com/sebajax/go-vertical-slice-architecture/pkg/apperror"
"github.com/sebajax/go-vertical-slice-architecture/pkg/message"
Expand All @@ -18,7 +19,7 @@ type ProductSchema struct {
}

// Creates a new product into the database
func CreateProduct(s service.CreateUserService) fiber.Handler {
func CreateProduct(s *service.CreateProductService) fiber.Handler {
return func(c *fiber.Ctx) error {
// Get body request
var body ProductSchema
Expand All @@ -38,16 +39,15 @@ func CreateProduct(s service.CreateUserService) fiber.Handler {
}

// No schema errores then map body to domain
user := &product{
IdentityNumber: body.IdentityNumber,
FirstName: body.FirstName,
LastName: body.LastName,
Email: body.Email,
DateOfBirth: body.DateOfBirth,
p := &product.Product{
Name: body.Name,
Sku: body.Sku,
Category: product.ParseProductCategory(body.Category),
Price: body.Price,
}

// Execute the service
result, err := s.CreateUser(user)
result, err := s.CreateProduct(p)
if err != nil {
// if service response an error return via the middleware
log.Error(err)
Expand Down
8 changes: 4 additions & 4 deletions internal/product/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package handler

import (
"github.com/gofiber/fiber/v2"
"github.com/sebajax/go-vertical-slice-architecture/internal/user/service"
"github.com/sebajax/go-vertical-slice-architecture/internal/product/service"
)

// UserRouter is the Router for GoFiber App
func UserRouter(app fiber.Router, s *service.UserService) {
app.Post("/", CreateUser(s.CreateUserServiceProvider))
// ProductRouter is the Router for GoFiber App
func ProductRouter(app fiber.Router, s *service.ProductService) {
app.Post("/", CreateProduct(s.CreateProductServiceProvider))
}
44 changes: 24 additions & 20 deletions internal/product/infrastructure/productRepository.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,46 @@ import (
"database/sql"
"fmt"

"github.com/sebajax/go-vertical-slice-architecture/internal/user"
"github.com/sebajax/go-vertical-slice-architecture/internal/product"
"github.com/sebajax/go-vertical-slice-architecture/pkg/database"
)

// User repository for querying the database
type userRepository struct {
// Product repository for querying the database
type productRepository struct {
db *database.DbConn
}

// Create a user instance repository
func NewUserRepository(dbcon *database.DbConn) user.UserRepository {
return &userRepository{db: dbcon}
// Create a product instance repository
func NewProductRepository(dbcon *database.DbConn) product.ProductRepository {
return &productRepository{db: dbcon}
}

// Stores a new user in the database
func (repo *userRepository) Save(u *user.User) (int64, error) {
// Stores a new product in the database
func (repo *productRepository) Save(p *product.Product) (int64, error) {
// Get the id inserted in the database
var id int64

query := `INSERT INTO client (identity_number, first_name, last_name, email, date_of_birth)
VALUES ($1, $2, $3, $4, $5) RETURNING id`
err := repo.db.DbPool.QueryRow(query, u.IdentityNumber, u.FirstName, u.LastName, u.Email, u.DateOfBirth).Scan(&id)
query := `INSERT INTO product (name, sku, category, price)
VALUES ($1, $2, $3, $4) RETURNING id`
err := repo.db.DbPool.QueryRow(query, p.Name, p.Sku, p.Category, p.Price).Scan(&id)
if err != nil {
return 0, err
}

fmt.Println("id: ", id)

// No errors return the user id inserted
// No errors return the product id inserted
return id, nil
}

// Gets the user by the email
func (repo *userRepository) GetByEmail(email string) (*user.User, bool, error) {
u := user.User{}
query := `SELECT id, identity_number, first_name, last_name, email, date_of_birth, created_at
FROM client
WHERE email = $1`
err := repo.db.DbPool.QueryRow(query, email).Scan(&u.Id, &u.IdentityNumber, &u.FirstName, &u.LastName, &u.Email, &u.DateOfBirth, &u.CreatedAt)
// Gets the product by the sku
func (repo *productRepository) GetBySku(sku string) (*product.Product, bool, error) {
p := product.Product{}
var category string
query := `SELECT id, name, sku, category, price, created_at
FROM product
WHERE sku = $1`
err := repo.db.DbPool.QueryRow(query, sku).Scan(&p.Id, &p.Name, &p.Sku, &category, &p.Price, &p.CreatedAt)
if err != nil {
// Not found, but not an error
if err == sql.ErrNoRows {
Expand All @@ -52,6 +53,9 @@ func (repo *userRepository) GetByEmail(email string) (*user.User, bool, error) {
return nil, false, err
}

// Parse to Enum category
p.Category = product.ParseProductCategory(category)

// Found the item
return &u, true, nil
return &p, true, nil
}
30 changes: 17 additions & 13 deletions internal/product/mock/mockProductRepository.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
package mocks

import "github.com/sebajax/go-vertical-slice-architecture/internal/user"
import (
"time"

type mockUserRepository struct{}
"github.com/sebajax/go-vertical-slice-architecture/internal/product"
)

func NewMockUserRepository() user.UserRepository {
return &mockUserRepository{}
type mockProductRepository struct{}

func NewMockProductRepository() product.ProductRepository {
return &mockProductRepository{}
}

func (mock *mockUserRepository) Save(u *user.User) (int64, error) {
func (mock *mockProductRepository) Save(p *product.Product) (int64, error) {
return 1, nil
}

func (mock *mockUserRepository) GetByEmail(email string) (*user.User, bool, error) {
/*return &user.User{
Id: 1,
Email: "[email protected]",
Name: "Juan",
DateOfBirth: time.Now(),
func (mock *mockProductRepository) GetBySku(email string) (*product.Product, bool, error) {
return &product.Product{
Id: 1,
Name: "ps5",
Sku: "123456sony",
Category: product.SmartWatch,
Price: 300.00,
CreatedAt: time.Now(),
}, true, nil*/
return nil, true, nil
}, true, nil
}
8 changes: 4 additions & 4 deletions internal/product/port.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package product

// User port interface definition for depedency injection
type UserRepository interface {
Save(u *User) (int64, error)
GetByEmail(email string) (*User, bool, error)
// Product port interface definition for depedency injection
type ProductRepository interface {
Save(p *Product) (int64, error)
GetBySku(sku string) (*Product, bool, error)
}
27 changes: 26 additions & 1 deletion internal/product/product.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package product

import "time"
import (
"strings"
"time"
)

// ProductCategory represents the categories of electronic products
type ProductCategory int
Expand Down Expand Up @@ -31,6 +34,28 @@ func (p ProductCategory) String() string {
}[p]
}

// Parse ProductCategory converts a string to ProductCategory
func ParseProductCategory(s string) ProductCategory {
switch strings.ToLower(s) {
case "laptop":
return Laptop
case "smartphone":
return Smartphone
case "tablet":
return Tablet
case "smartwatch":
return SmartWatch
case "headphones":
return Headphones
case "camera":
return Camera
case "television":
return Television
default:
return Other
}
}

// Const for error messages
const (
ErrorSkuExists string = "ERROR_SKU_EXISTS"
Expand Down
45 changes: 20 additions & 25 deletions internal/product/service/createProduct.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,49 @@ package service
import (
"log"

"github.com/sebajax/go-vertical-slice-architecture/internal/user"
"github.com/sebajax/go-vertical-slice-architecture/internal/product"
"github.com/sebajax/go-vertical-slice-architecture/pkg/apperror"
)

// CreateUserService interface for DI
type CreateUserService interface {
CreateUser(user *user.User) (int64, error)
// Product use cases (port injection)
type CreateProductService struct {
productRepository product.ProductRepository
}

// User use cases (port injection)
type createUserService struct {
userRepository user.UserRepository
}

// Create a new user service use case instance
func NewCreateUserService(repository user.UserRepository) CreateUserService {
// return the pointer to user service
return &createUserService{
userRepository: repository,
// Create a new product service use case instance
func NewCreateProductService(repository product.ProductRepository) *CreateProductService {
// return the pointer to product service
return &CreateProductService{
productRepository: repository,
}
}

// Create a new user and store the user in the database
func (service *createUserService) CreateUser(u *user.User) (int64, error) {
_, check, err := service.userRepository.GetByEmail(u.Email)
// check if user does not exist and no database error ocurred
// Create a new product and store the product into the database
func (service *CreateProductService) CreateProduct(p *product.Product) (int64, error) {
_, check, err := service.productRepository.GetBySku(p.Sku)
// check if product sky does not exist and no database error ocurred
if err != nil {
// database error
log.Fatalln(err)
err := apperror.InternalServerError()
return 0, err
}
if check {
// user found
log.Println(u, user.ErrorEmailExists)
err := apperror.BadRequest(user.ErrorEmailExists)
// product sku found
log.Println(p, product.ErrorSkuExists)
err := apperror.BadRequest(product.ErrorSkuExists)
return 0, err
}

// create the new user and return the id
userId, err := service.userRepository.Save(u)
// create the new product and return the id
id, err := service.productRepository.Save(p)
if err != nil {
// database error
log.Fatalln(err)
err := apperror.InternalServerError()
return 0, err
}

// user created successfuly
return userId, nil
// product created successfuly
return id, nil
}
28 changes: 14 additions & 14 deletions internal/product/service/service.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
package service

import (
"github.com/sebajax/go-vertical-slice-architecture/internal/user/infrastructure"
"github.com/sebajax/go-vertical-slice-architecture/internal/product/infrastructure"
"go.uber.org/dig"
)

// user service instance
type UserService struct {
CreateUserServiceProvider CreateUserService
// product service instance
type ProductService struct {
CreateProductServiceProvider *CreateProductService
}

func NewUserService() *UserService {
return &UserService{}
func NewProductService() *ProductService {
return &ProductService{}
}

// provide components for injection
func ProvideUserComponents(c *dig.Container) {
func ProvideProductComponents(c *dig.Container) {
// repositorory provider injection
err := c.Provide(infrastructure.NewUserRepository)
err := c.Provide(infrastructure.NewProductRepository)
if err != nil {
panic(err)
}

//service provider injection
err = c.Provide(NewCreateUserService)
// service provider injection
err = c.Provide(NewCreateProductService)
if err != nil {
panic(err)
}
}

// init service container
func (us *UserService) InitUserComponents(c *dig.Container) error {
// create user service
func (ps *ProductService) InitProductComponents(c *dig.Container) error {
// create product service
err := c.Invoke(
func(s CreateUserService) {
us.CreateUserServiceProvider = s
func(s *CreateProductService) {
ps.CreateProductServiceProvider = s
},
)

Expand Down
Loading

0 comments on commit 872df7d

Please sign in to comment.