fix: fixing some code and base url refactoring
This commit is contained in:
parent
f22351ffbe
commit
4f586076e7
|
|
@ -1,3 +1,6 @@
|
||||||
|
#BASE URL
|
||||||
|
BASE_URL=
|
||||||
|
|
||||||
# SERVER SETTINGS
|
# SERVER SETTINGS
|
||||||
SERVER_HOST=
|
SERVER_HOST=
|
||||||
SERVER_PORT=
|
SERVER_PORT=
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,4 @@ go.work.sum
|
||||||
.env.dev
|
.env.dev
|
||||||
|
|
||||||
# Ignore public uploads
|
# Ignore public uploads
|
||||||
/public/uploads/
|
/public/apirijig/v2/uploads/
|
||||||
|
|
@ -8,10 +8,11 @@ import (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config.SetupConfig()
|
config.SetupConfig()
|
||||||
|
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
// app.Static(utils.BaseUrl+"/uploads", "./public"+utils.BaseUrl+"/uploads")
|
||||||
|
|
||||||
router.SetupRoutes(app)
|
router.SetupRoutes(app)
|
||||||
|
|
||||||
config.StartServer(app)
|
config.StartServer(app)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mime/multipart"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResponseProductImageDTO struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
ProductID string `json:"productId"`
|
||||||
|
ImageURL string `json:"imageURL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseProductDTO struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
StoreID string `json:"storeId"`
|
||||||
|
ProductName string `json:"productName"`
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
Saled int `json:"saled"`
|
||||||
|
ProductImages []ResponseProductImageDTO `json:"productImages,omitempty"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
UpdatedAt string `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestProductDTO struct {
|
||||||
|
ProductName string `json:"product_name"`
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
ProductImages []*multipart.FileHeader `json:"product_images,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RequestProductDTO) ValidateProductInput() (map[string][]string, bool) {
|
||||||
|
errors := make(map[string][]string)
|
||||||
|
|
||||||
|
if strings.TrimSpace(r.ProductName) == "" {
|
||||||
|
errors["product_name"] = append(errors["product_name"], "Product name is required")
|
||||||
|
} else if len(r.ProductName) < 3 {
|
||||||
|
errors["product_name"] = append(errors["product_name"], "Product name must be at least 3 characters long")
|
||||||
|
} else {
|
||||||
|
validNameRegex := `^[a-zA-Z0-9\s_.-]+$`
|
||||||
|
if matched, _ := regexp.MatchString(validNameRegex, r.ProductName); !matched {
|
||||||
|
errors["product_name"] = append(errors["product_name"], "Product name can only contain letters, numbers, spaces, underscores, and dashes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Quantity < 1 {
|
||||||
|
errors["quantity"] = append(errors["quantity"], "Quantity must be at least 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return errors, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/dto"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/internal/services"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProductHandler struct {
|
||||||
|
ProductService services.ProductService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProductHandler(productService services.ProductService) *ProductHandler {
|
||||||
|
return &ProductHandler{ProductService: productService}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertStringToInt(value string) (int, error) {
|
||||||
|
convertedValue, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("invalid integer format: %s", value)
|
||||||
|
}
|
||||||
|
return convertedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginationParams(c *fiber.Ctx) (int, int, error) {
|
||||||
|
pageStr := c.Query("page", "1")
|
||||||
|
limitStr := c.Query("limit", "50")
|
||||||
|
|
||||||
|
page, err := strconv.Atoi(pageStr)
|
||||||
|
if err != nil || page <= 0 {
|
||||||
|
return 0, 0, fmt.Errorf("invalid page value")
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, err := strconv.Atoi(limitStr)
|
||||||
|
if err != nil || limit <= 0 {
|
||||||
|
return 0, 0, fmt.Errorf("invalid limit value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return page, limit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ProductHandler) CreateProduct(c *fiber.Ctx) error {
|
||||||
|
userID, ok := c.Locals("userID").(string)
|
||||||
|
if !ok {
|
||||||
|
log.Println("User ID not found in Locals")
|
||||||
|
return utils.GenericResponse(c, fiber.StatusUnauthorized, "User ID not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
productName := c.FormValue("product_name")
|
||||||
|
quantityStr := c.FormValue("quantity")
|
||||||
|
productImages, err := c.MultipartForm()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error parsing form data: %v", err)
|
||||||
|
return utils.GenericResponse(c, fiber.StatusBadRequest, "Error parsing form data")
|
||||||
|
}
|
||||||
|
|
||||||
|
quantity, err := ConvertStringToInt(quantityStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Invalid quantity: %v", err)
|
||||||
|
return utils.GenericResponse(c, fiber.StatusBadRequest, "Invalid quantity")
|
||||||
|
}
|
||||||
|
|
||||||
|
productDTO := dto.RequestProductDTO{
|
||||||
|
ProductName: productName,
|
||||||
|
Quantity: quantity,
|
||||||
|
ProductImages: productImages.File["product_image"],
|
||||||
|
}
|
||||||
|
|
||||||
|
product, err := h.ProductService.CreateProduct(userID, &productDTO)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error creating product: %v", err)
|
||||||
|
return utils.GenericResponse(c, fiber.StatusConflict, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.CreateResponse(c, product, "Product created successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ProductHandler) GetAllProductsByStoreID(c *fiber.Ctx) error {
|
||||||
|
userID, ok := c.Locals("userID").(string)
|
||||||
|
if !ok {
|
||||||
|
log.Println("User ID not found in Locals")
|
||||||
|
return utils.GenericResponse(c, fiber.StatusUnauthorized, "User ID not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
page, limit, err := GetPaginationParams(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Invalid pagination params: %v", err)
|
||||||
|
return utils.GenericResponse(c, fiber.StatusBadRequest, "Invalid pagination parameters")
|
||||||
|
}
|
||||||
|
|
||||||
|
products, total, err := h.ProductService.GetAllProductsByStoreID(userID, page, limit)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error fetching products: %v", err)
|
||||||
|
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PaginatedResponse(c, products, page, limit, int(total), "Products fetched successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ProductHandler) GetProductByID(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
productID := c.Params("product_id")
|
||||||
|
if productID == "" {
|
||||||
|
log.Println("Product ID is required")
|
||||||
|
return utils.GenericResponse(c, fiber.StatusBadRequest, "Product ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
product, err := h.ProductService.GetProductByID(productID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error fetching product: %v", err)
|
||||||
|
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.SuccessResponse(c, product, "Product fetched successfully")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pahmiudahgede/senggoldong/model"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProductRepository interface {
|
||||||
|
CountProductsByStoreID(storeID string) (int64, error)
|
||||||
|
CreateProduct(product *model.Product) error
|
||||||
|
GetProductByID(productID string) (*model.Product, error)
|
||||||
|
GetProductsByStoreID(storeID string) ([]model.Product, error)
|
||||||
|
FindProductsByStoreID(storeID string, page, limit int) ([]model.Product, error)
|
||||||
|
FindProductImagesByProductID(productID string) ([]model.ProductImage, error)
|
||||||
|
UpdateProduct(product *model.Product) error
|
||||||
|
DeleteProduct(productID string) error
|
||||||
|
|
||||||
|
AddProductImages(images []model.ProductImage) error
|
||||||
|
DeleteProductImagesByProductID(productID string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type productRepository struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProductRepository(DB *gorm.DB) ProductRepository {
|
||||||
|
return &productRepository{DB}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *productRepository) CreateProduct(product *model.Product) error {
|
||||||
|
return r.DB.Create(product).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *productRepository) CountProductsByStoreID(storeID string) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
if err := r.DB.Model(&model.Product{}).Where("store_id = ?", storeID).Count(&count).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *productRepository) GetProductByID(productID string) (*model.Product, error) {
|
||||||
|
var product model.Product
|
||||||
|
if err := r.DB.Preload("ProductImages").Where("id = ?", productID).First(&product).Error; err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &product, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *productRepository) GetProductsByStoreID(storeID string) ([]model.Product, error) {
|
||||||
|
var products []model.Product
|
||||||
|
if err := r.DB.Where("store_id = ?", storeID).Preload("ProductImages").Find(&products).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return products, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *productRepository) FindProductsByStoreID(storeID string, page, limit int) ([]model.Product, error) {
|
||||||
|
var products []model.Product
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
|
||||||
|
if err := r.DB.
|
||||||
|
Where("store_id = ?", storeID).
|
||||||
|
Limit(limit).
|
||||||
|
Offset(offset).
|
||||||
|
Find(&products).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return products, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *productRepository) FindProductImagesByProductID(productID string) ([]model.ProductImage, error) {
|
||||||
|
var productImages []model.ProductImage
|
||||||
|
if err := r.DB.Where("product_id = ?", productID).Find(&productImages).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return productImages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *productRepository) UpdateProduct(product *model.Product) error {
|
||||||
|
return r.DB.Save(product).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *productRepository) DeleteProduct(productID string) error {
|
||||||
|
return r.DB.Delete(&model.Product{}, "id = ?", productID).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *productRepository) AddProductImages(images []model.ProductImage) error {
|
||||||
|
if len(images) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return r.DB.Create(&images).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *productRepository) DeleteProductImagesByProductID(productID string) error {
|
||||||
|
return r.DB.Where("product_id = ?", productID).Delete(&model.ProductImage{}).Error
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ func NewArticleService(articleRepo repositories.ArticleRepository) ArticleServic
|
||||||
|
|
||||||
func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error) {
|
func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error) {
|
||||||
|
|
||||||
coverImageDir := "./public/uploads/articles"
|
coverImageDir := "./public" + os.Getenv("BASE_URL") + "/uploads/articles"
|
||||||
if err := os.MkdirAll(coverImageDir, os.ModePerm); err != nil {
|
if err := os.MkdirAll(coverImageDir, os.ModePerm); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create directory for cover image: %v", err)
|
return nil, fmt.Errorf("failed to create directory for cover image: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -338,7 +338,7 @@ func (s *articleService) UpdateArticle(id string, request dto.RequestArticleDTO,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *articleService) saveCoverImage(coverImage *multipart.FileHeader, oldImagePath string) (string, error) {
|
func (s *articleService) saveCoverImage(coverImage *multipart.FileHeader, oldImagePath string) (string, error) {
|
||||||
coverImageDir := "./public/uploads/articles"
|
coverImageDir := "/uploads/articles"
|
||||||
if _, err := os.Stat(coverImageDir); os.IsNotExist(err) {
|
if _, err := os.Stat(coverImageDir); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(coverImageDir, os.ModePerm); err != nil {
|
if err := os.MkdirAll(coverImageDir, os.ModePerm); err != nil {
|
||||||
return "", fmt.Errorf("failed to create directory for cover image: %v", err)
|
return "", fmt.Errorf("failed to create directory for cover image: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ func NewBannerService(bannerRepo repositories.BannerRepository) BannerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *bannerService) saveBannerImage(bannerImage *multipart.FileHeader) (string, error) {
|
func (s *bannerService) saveBannerImage(bannerImage *multipart.FileHeader) (string, error) {
|
||||||
bannerImageDir := "./public/uploads/banners"
|
bannerImageDir := "./public" + os.Getenv("BASE_URL") + "/uploads/banners"
|
||||||
if _, err := os.Stat(bannerImageDir); os.IsNotExist(err) {
|
if _, err := os.Stat(bannerImageDir); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(bannerImageDir, os.ModePerm); err != nil {
|
if err := os.MkdirAll(bannerImageDir, os.ModePerm); err != nil {
|
||||||
return "", fmt.Errorf("failed to create directory for banner image: %v", err)
|
return "", fmt.Errorf("failed to create directory for banner image: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mime/multipart"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/dto"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/internal/repositories"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/model"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProductService interface {
|
||||||
|
SaveProductImage(file *multipart.FileHeader, imageType string) (string, error)
|
||||||
|
CreateProduct(userID string, productDTO *dto.RequestProductDTO) (*dto.ResponseProductDTO, error)
|
||||||
|
|
||||||
|
GetAllProductsByStoreID(userID string, page, limit int) ([]dto.ResponseProductDTO, int64, error)
|
||||||
|
GetProductByID(productID string) (*dto.ResponseProductDTO, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type productService struct {
|
||||||
|
productRepo repositories.ProductRepository
|
||||||
|
storeRepo repositories.StoreRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProductService(productRepo repositories.ProductRepository, storeRepo repositories.StoreRepository) ProductService {
|
||||||
|
return &productService{productRepo, storeRepo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *productService) CreateProduct(userID string, productDTO *dto.RequestProductDTO) (*dto.ResponseProductDTO, error) {
|
||||||
|
store, err := s.storeRepo.FindStoreByUserID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error retrieving store by user ID: %w", err)
|
||||||
|
}
|
||||||
|
if store == nil {
|
||||||
|
return nil, fmt.Errorf("store not found for user %s", userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var imagePaths []string
|
||||||
|
var productImages []model.ProductImage
|
||||||
|
for _, file := range productDTO.ProductImages {
|
||||||
|
imagePath, err := s.SaveProductImage(file, "product")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to save product image: %w", err)
|
||||||
|
}
|
||||||
|
imagePaths = append(imagePaths, imagePath)
|
||||||
|
|
||||||
|
productImages = append(productImages, model.ProductImage{
|
||||||
|
ImageURL: imagePath,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(imagePaths) == 0 {
|
||||||
|
return nil, fmt.Errorf("at least one image is required for the product")
|
||||||
|
}
|
||||||
|
|
||||||
|
product := model.Product{
|
||||||
|
StoreID: store.ID,
|
||||||
|
ProductName: productDTO.ProductName,
|
||||||
|
Quantity: productDTO.Quantity,
|
||||||
|
}
|
||||||
|
|
||||||
|
product.ProductImages = productImages
|
||||||
|
|
||||||
|
if err := s.productRepo.CreateProduct(&product); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create product: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createdAt, err := utils.FormatDateToIndonesianFormat(product.CreatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to format createdAt: %w", err)
|
||||||
|
}
|
||||||
|
updatedAt, err := utils.FormatDateToIndonesianFormat(product.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to format updatedAt: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var productImagesDTO []dto.ResponseProductImageDTO
|
||||||
|
for _, img := range product.ProductImages {
|
||||||
|
productImagesDTO = append(productImagesDTO, dto.ResponseProductImageDTO{
|
||||||
|
ID: img.ID,
|
||||||
|
ProductID: img.ProductID,
|
||||||
|
ImageURL: img.ImageURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
productDTOResponse := &dto.ResponseProductDTO{
|
||||||
|
ID: product.ID,
|
||||||
|
StoreID: product.StoreID,
|
||||||
|
ProductName: product.ProductName,
|
||||||
|
Quantity: product.Quantity,
|
||||||
|
ProductImages: productImagesDTO,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return productDTOResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *productService) GetAllProductsByStoreID(userID string, page, limit int) ([]dto.ResponseProductDTO, int64, error) {
|
||||||
|
|
||||||
|
store, err := s.storeRepo.FindStoreByUserID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("error retrieving store by user ID: %w", err)
|
||||||
|
}
|
||||||
|
if store == nil {
|
||||||
|
return nil, 0, fmt.Errorf("store not found for user %s", userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := s.productRepo.CountProductsByStoreID(store.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("error counting products: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
products, err := s.productRepo.FindProductsByStoreID(store.ID, page, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("error fetching products: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var productDTOs []dto.ResponseProductDTO
|
||||||
|
for _, product := range products {
|
||||||
|
productImages, err := s.productRepo.FindProductImagesByProductID(product.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("error fetching product images: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var productImagesDTO []dto.ResponseProductImageDTO
|
||||||
|
for _, img := range productImages {
|
||||||
|
productImagesDTO = append(productImagesDTO, dto.ResponseProductImageDTO{
|
||||||
|
ID: img.ID,
|
||||||
|
ProductID: img.ProductID,
|
||||||
|
ImageURL: img.ImageURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createdAt, _ := utils.FormatDateToIndonesianFormat(product.CreatedAt)
|
||||||
|
updatedAt, _ := utils.FormatDateToIndonesianFormat(product.UpdatedAt)
|
||||||
|
|
||||||
|
productDTOs = append(productDTOs, dto.ResponseProductDTO{
|
||||||
|
ID: product.ID,
|
||||||
|
StoreID: product.StoreID,
|
||||||
|
ProductName: product.ProductName,
|
||||||
|
Quantity: product.Quantity,
|
||||||
|
Saled: product.Saled,
|
||||||
|
ProductImages: productImagesDTO,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return productDTOs, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *productService) GetProductByID(productID string) (*dto.ResponseProductDTO, error) {
|
||||||
|
|
||||||
|
product, err := s.productRepo.GetProductByID(productID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve product: %w", err)
|
||||||
|
}
|
||||||
|
if product == nil {
|
||||||
|
return nil, fmt.Errorf("product not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
createdAt, _ := utils.FormatDateToIndonesianFormat(product.CreatedAt)
|
||||||
|
updatedAt, _ := utils.FormatDateToIndonesianFormat(product.UpdatedAt)
|
||||||
|
|
||||||
|
productDTO := &dto.ResponseProductDTO{
|
||||||
|
ID: product.ID,
|
||||||
|
StoreID: product.StoreID,
|
||||||
|
ProductName: product.ProductName,
|
||||||
|
Quantity: product.Quantity,
|
||||||
|
Saled: product.Saled,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
var productImagesDTO []dto.ResponseProductImageDTO
|
||||||
|
for _, image := range product.ProductImages {
|
||||||
|
productImagesDTO = append(productImagesDTO, dto.ResponseProductImageDTO{
|
||||||
|
ID: image.ID,
|
||||||
|
ProductID: image.ProductID,
|
||||||
|
ImageURL: image.ImageURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
productDTO.ProductImages = productImagesDTO
|
||||||
|
|
||||||
|
return productDTO, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *productService) SaveProductImage(file *multipart.FileHeader, imageType string) (string, error) {
|
||||||
|
imageDir := fmt.Sprintf("./public%s/uploads/store/%s", os.Getenv("BASE_URL"), imageType)
|
||||||
|
if _, err := os.Stat(imageDir); os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(imageDir, os.ModePerm); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create directory for %s image: %v", imageType, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowedExtensions := map[string]bool{".jpg": true, ".jpeg": true, ".png": true}
|
||||||
|
extension := filepath.Ext(file.Filename)
|
||||||
|
if !allowedExtensions[extension] {
|
||||||
|
return "", fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed for %s", imageType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := fmt.Sprintf("%s_%s%s", imageType, uuid.New().String(), extension)
|
||||||
|
filePath := filepath.Join(imageDir, fileName)
|
||||||
|
|
||||||
|
fileData, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to open file: %w", err)
|
||||||
|
}
|
||||||
|
defer fileData.Close()
|
||||||
|
|
||||||
|
outFile, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create %s image file: %v", imageType, err)
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
if _, err := outFile.ReadFrom(fileData); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to save %s image: %v", imageType, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join("/uploads/store/", imageType, fileName), nil
|
||||||
|
}
|
||||||
|
|
@ -237,7 +237,7 @@ func (s *storeService) DeleteStore(storeID string) error {
|
||||||
|
|
||||||
func (s *storeService) saveStoreImage(file *multipart.FileHeader, imageType string) (string, error) {
|
func (s *storeService) saveStoreImage(file *multipart.FileHeader, imageType string) (string, error) {
|
||||||
|
|
||||||
imageDir := fmt.Sprintf("./public/uploads/store/%s", imageType)
|
imageDir := fmt.Sprintf("./public%s/uploads/store/%s",os.Getenv("BASE_URL"), imageType)
|
||||||
if _, err := os.Stat(imageDir); os.IsNotExist(err) {
|
if _, err := os.Stat(imageDir); os.IsNotExist(err) {
|
||||||
|
|
||||||
if err := os.MkdirAll(imageDir, os.ModePerm); err != nil {
|
if err := os.MkdirAll(imageDir, os.ModePerm); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -16,8 +17,6 @@ import (
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const avatarDir = "./public/uploads/avatars"
|
|
||||||
|
|
||||||
var allowedExtensions = []string{".jpg", ".jpeg", ".png"}
|
var allowedExtensions = []string{".jpg", ".jpeg", ".png"}
|
||||||
|
|
||||||
type UserProfileService interface {
|
type UserProfileService interface {
|
||||||
|
|
@ -183,8 +182,13 @@ func (s *userProfileService) UpdateUserPassword(userID string, passwordData dto.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *userProfileService) UpdateUserAvatar(userID string, file *multipart.FileHeader) (string, error) {
|
func (s *userProfileService) UpdateUserAvatar(userID string, file *multipart.FileHeader) (string, error) {
|
||||||
|
baseURL := os.Getenv("BASE_URL")
|
||||||
|
if baseURL == "" {
|
||||||
|
return "", fmt.Errorf("BASE_URL is not set in environment variables")
|
||||||
|
}
|
||||||
|
|
||||||
if err := ensureAvatarDirectoryExists(); err != nil {
|
avatarDir := filepath.Join("./public", baseURL, "/uploads/avatars")
|
||||||
|
if err := ensureAvatarDirectoryExists(avatarDir); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,13 +202,19 @@ func (s *userProfileService) UpdateUserAvatar(userID string, file *multipart.Fil
|
||||||
}
|
}
|
||||||
|
|
||||||
if updatedUser.Avatar != nil && *updatedUser.Avatar != "" {
|
if updatedUser.Avatar != nil && *updatedUser.Avatar != "" {
|
||||||
oldAvatarPath := "./public" + *updatedUser.Avatar
|
oldAvatarPath := filepath.Join("./public", *updatedUser.Avatar)
|
||||||
if err := os.Remove(oldAvatarPath); err != nil {
|
if _, err := os.Stat(oldAvatarPath); err == nil {
|
||||||
return "", fmt.Errorf("failed to remove old avatar: %v", err)
|
|
||||||
|
if err := os.Remove(oldAvatarPath); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to remove old avatar: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
log.Printf("Old avatar file not found: %s", oldAvatarPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
avatarURL, err := saveAvatarFile(file, userID)
|
avatarURL, err := saveAvatarFile(file, userID, avatarDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -217,7 +227,7 @@ func (s *userProfileService) UpdateUserAvatar(userID string, file *multipart.Fil
|
||||||
return "Foto profil berhasil diupdate", nil
|
return "Foto profil berhasil diupdate", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureAvatarDirectoryExists() error {
|
func ensureAvatarDirectoryExists(avatarDir string) error {
|
||||||
if _, err := os.Stat(avatarDir); os.IsNotExist(err) {
|
if _, err := os.Stat(avatarDir); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(avatarDir, os.ModePerm); err != nil {
|
if err := os.MkdirAll(avatarDir, os.ModePerm); err != nil {
|
||||||
return fmt.Errorf("failed to create avatar directory: %v", err)
|
return fmt.Errorf("failed to create avatar directory: %v", err)
|
||||||
|
|
@ -236,7 +246,7 @@ func validateAvatarFile(file *multipart.FileHeader) error {
|
||||||
return fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
|
return fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveAvatarFile(file *multipart.FileHeader, userID string) (string, error) {
|
func saveAvatarFile(file *multipart.FileHeader, userID, avatarDir string) (string, error) {
|
||||||
extension := filepath.Ext(file.Filename)
|
extension := filepath.Ext(file.Filename)
|
||||||
avatarFileName := fmt.Sprintf("%s_avatar%s", userID, extension)
|
avatarFileName := fmt.Sprintf("%s_avatar%s", userID, extension)
|
||||||
avatarPath := filepath.Join(avatarDir, avatarFileName)
|
avatarPath := filepath.Join(avatarDir, avatarFileName)
|
||||||
|
|
@ -258,5 +268,6 @@ func saveAvatarFile(file *multipart.FileHeader, userID string) (string, error) {
|
||||||
return "", fmt.Errorf("failed to save avatar file: %v", err)
|
return "", fmt.Errorf("failed to save avatar file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("/uploads/avatars/%s", avatarFileName), nil
|
relativePath := filepath.Join("/uploads/avatars", avatarFileName)
|
||||||
|
return relativePath, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package presentation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/config"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/internal/handler"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/internal/repositories"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/internal/services"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/middleware"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProductRouter(api fiber.Router) {
|
||||||
|
productRepo := repositories.NewProductRepository(config.DB)
|
||||||
|
storeRepo := repositories.NewStoreRepository(config.DB)
|
||||||
|
productService := services.NewProductService(productRepo, storeRepo)
|
||||||
|
productHandler := handler.NewProductHandler(productService)
|
||||||
|
|
||||||
|
productAPI := api.Group("/productinstore")
|
||||||
|
|
||||||
|
productAPI.Post("/add-product", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator, utils.RolePengelola, utils.RolePengepul), productHandler.CreateProduct)
|
||||||
|
productAPI.Get("/getproductbyuser", middleware.AuthMiddleware, productHandler.GetAllProductsByStoreID)
|
||||||
|
productAPI.Get("getproduct/:product_id", middleware.AuthMiddleware, productHandler.GetProductByID)
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/pahmiudahgede/senggoldong/middleware"
|
"github.com/pahmiudahgede/senggoldong/middleware"
|
||||||
"github.com/pahmiudahgede/senggoldong/presentation"
|
"github.com/pahmiudahgede/senggoldong/presentation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetupRoutes(app *fiber.App) {
|
func SetupRoutes(app *fiber.App) {
|
||||||
api := app.Group("/apirijikid/v2")
|
app.Static(os.Getenv("BASE_URL")+"/uploads", "./public"+os.Getenv("BASE_URL")+"/uploads")
|
||||||
|
|
||||||
|
api := app.Group(os.Getenv("BASE_URL"))
|
||||||
api.Use(middleware.APIKeyMiddleware)
|
api.Use(middleware.APIKeyMiddleware)
|
||||||
|
|
||||||
presentation.AuthRouter(api)
|
presentation.AuthRouter(api)
|
||||||
|
|
@ -21,4 +25,5 @@ func SetupRoutes(app *fiber.App) {
|
||||||
presentation.InitialCointRoute(api)
|
presentation.InitialCointRoute(api)
|
||||||
presentation.TrashRouter(api)
|
presentation.TrashRouter(api)
|
||||||
presentation.StoreRouter(api)
|
presentation.StoreRouter(api)
|
||||||
|
presentation.ProductRouter(api)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue