feat: add product list api

This commit is contained in:
amir-ys 2026-04-29 01:53:23 +03:30
parent 0948b36012
commit c01d3678ae
11 changed files with 191 additions and 25 deletions

View File

@ -1,6 +1,23 @@
package errmsg package errmsg
type ErrorResponse struct {
Message string `json:"message"` // General error message
Errors map[string]interface{} `json:"errors,omitempty"` // Additional detail of error
InternalErrCode string `json:"internal_error_code,omitempty"` // Custom error code (optional)
}
func (e ErrorResponse) Error() string {
return e.Message
}
const ( const (
ErrValidationFailed = "input validation failed"
ErrUnexpectedError = "unexpected error occurred"
ErrInvalidRequestFormat = "invalid request format"
ErrGetUserInfo = "get user info failed"
ErrFailedDecodeBase64 = "decode data to base 64 failed"
ErrFailedUnmarshalJson = "unmarshal data to JSON failed"
ErrUnauthorized = "unauthorized"
ErrorMsgAdminNotAllowed = "admin is not allowed" ErrorMsgAdminNotAllowed = "admin is not allowed"
ErrorMsgNotFound = "record not found" ErrorMsgNotFound = "record not found"
ErrorMsgSomethingWentWrong = "something went wrong" ErrorMsgSomethingWentWrong = "something went wrong"

View File

@ -3,6 +3,7 @@ package productapp
import ( import (
"context" "context"
"fmt" "fmt"
"git.gocasts.ir/ebhomengo/niki/productapp/service/product"
"log" "log"
httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server" httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server"
@ -25,6 +26,7 @@ type Config struct {
type Application struct { type Application struct {
Config Config Config Config
HTTPServer *producthttp.Server HTTPServer *producthttp.Server
productSvc product.Service
DB *productdb.DB DB *productdb.DB
Echo *echo.Echo Echo *echo.Echo
} }
@ -40,11 +42,14 @@ func Setup(cfg Config) *Application {
Router: e, Router: e,
} }
srv := producthttp.NewServer(hsrv) productSvc := product.New(productDB)
srv := producthttp.NewServer(hsrv, productSvc)
srv.RegisterRoutes() srv.RegisterRoutes()
return &Application{ return &Application{
Config: cfg, Config: cfg,
productSvc: productSvc,
HTTPServer: srv, HTTPServer: srv,
DB: productDB, DB: productDB,
Echo: e, Echo: e,

View File

@ -1,17 +1,38 @@
package http package http
import ( import (
"net/http" errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
"git.gocasts.ir/ebhomengo/niki/productapp/service/product"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"log/slog"
"net/http"
) )
type Handler struct{} type Handler struct {
productService product.Service
func NewHandler() *Handler { Logger *slog.Logger
return &Handler{}
} }
func (h Handler) HealthCheck(c echo.Context) error { func NewHandler(productService product.Service) *Handler {
return c.JSON(http.StatusOK, map[string]string{"status": "ok"}) return &Handler{
productService: productService,
}
}
func (h Handler) getProductList(c echo.Context) error {
var req product.GetProductListRequest
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, errmsg.ErrorResponse{
Message: errmsg.ErrInvalidRequestFormat,
})
}
response, err := h.productService.GetProducts(c.Request().Context(), req)
if err != nil {
// todo handle validation error
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return c.JSON(http.StatusOK, response)
} }

View File

@ -0,0 +1,11 @@
package http
import (
"net/http"
"github.com/labstack/echo/v4"
)
func (h Handler) HealthCheck(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"status": "healthy"})
}

View File

@ -1 +0,0 @@
package http

View File

@ -1,16 +1,19 @@
package http package http
import httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server" import (
httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server"
"git.gocasts.ir/ebhomengo/niki/productapp/service/product"
)
type Server struct { type Server struct {
HTTPServer *httpserver.Server HTTPServer *httpserver.Server
Handler *Handler Handler *Handler
} }
func NewServer(httpserver *httpserver.Server) *Server { func NewServer(httpserver *httpserver.Server, productSvc product.Service) *Server {
return &Server{ return &Server{
HTTPServer: httpserver, HTTPServer: httpserver,
Handler: NewHandler(), Handler: NewHandler(productSvc),
} }
} }
@ -22,5 +25,10 @@ func (s *Server) Stop() {}
func (s *Server) RegisterRoutes() { func (s *Server) RegisterRoutes() {
r := s.HTTPServer.Router r := s.HTTPServer.Router
r.GET("health-check", s.Handler.HealthCheck) r.GET("health-check/1", s.Handler.HealthCheck)
productGroup := r.Group("/v1/products")
productGroup.GET("", s.Handler.getProductList)
} }

View File

@ -1,6 +1,11 @@
package database package database
import "git.gocasts.ir/ebhomengo/niki/repository/mysql" import (
"context"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"git.gocasts.ir/ebhomengo/niki/productapp/service/product"
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
)
type DB struct { type DB struct {
conn *mysql.DB conn *mysql.DB
@ -11,3 +16,42 @@ func New(conn *mysql.DB) *DB {
conn: conn, conn: conn,
} }
} }
func (db *DB) GetProducts(ctx context.Context, page, pageSize uint) ([]product.Product, error) {
const Op = "domain.repository.mysql.order.get-shipping"
query := "SELECT * FROM products ORDER BY id DESC limit ? offset ?"
rows, err := db.conn.Conn().Query(query, page, pageSize)
if rows.Err() != nil {
return []product.Product{}, richerror.New(Op).WithErr(err)
}
defer rows.Close()
var products []product.Product
for rows.Next() {
var s product.Product
err := rows.Scan(
&s.ID,
&s.Name,
&s.Slug,
&s.Description,
&s.Price,
&s.Stock,
&s.IsActive,
&s.Features,
&s.CreatedAt,
&s.DeletedAt,
)
if err != nil {
return nil, richerror.New(Op).WithErr(err)
}
products = append(products, s)
}
return products, nil
}

View File

@ -6,16 +6,16 @@ import (
) )
type Product struct { type Product struct {
ID uint ID uint `json:"id"`
Name string Name string `json:"name"`
Slug string Slug string `json:"slug"`
Description string Description string `json:"description"`
Price float64 Price float64 `json:"price"`
Stock int Stock int `json:"stock"`
IsActive bool IsActive bool `json:"is_active"`
Features string Features string `json:"features"`
CreatedAt time.Time CreatedAt time.Time `json:"created_at"`
DeletedAt sql.NullTime DeletedAt sql.NullTime `json:"-"`
} }
type ProductImage struct { type ProductImage struct {

View File

@ -0,0 +1,13 @@
package product
import "errors"
var (
ErrFailedGetProductList = errors.New("failed to get products list")
ErrFailedCreateProduct = errors.New("failed to create product")
ErrFailedFindProduct = errors.New("failed to find product")
ErrCantAccessToUpdateProduct = errors.New("you are not allowed to edit this product")
ErrFailedUpdateProduct = errors.New("failed to update product")
ErrFailedToPreparedStatement = errors.New("failed to prepared statement: %v")
ErrCantAccess = errors.New("you are not allowed to this action")
)

View File

@ -1 +1,12 @@
package product package product
import "git.gocasts.ir/ebhomengo/niki/param"
type GetProductListRequest struct {
param.PaginationRequest
}
type GetProductListResponse struct {
Data []Product `json:"data"`
param.PaginationResponse
}

View File

@ -1 +1,38 @@
package product package product
import (
"context"
"fmt"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
)
type Repository interface {
GetProducts(ctx context.Context, page, pageSize uint) ([]Product, error)
}
type Service struct {
repository Repository
}
func New(repo Repository) Service {
return Service{
repository: repo,
}
}
func (s Service) GetProducts(ctx context.Context, req GetProductListRequest) (GetProductListResponse, error) {
page := req.GetPageNumber()
pageSize := req.PageSize
products, err := s.repository.GetProducts(ctx, page, pageSize)
if err != nil {
fmt.Println(err)
return GetProductListResponse{}, errmsg.ErrorResponse{
Message: ErrFailedGetProductList.Error(),
Errors: map[string]interface{}{"repository_error": err.Error()},
}
}
return GetProductListResponse{Data: products}, nil
}