feat: post store feature
This commit is contained in:
parent
98e1b81213
commit
b144ac7750
|
@ -0,0 +1,68 @@
|
|||
package dto
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ResponseStoreDTO struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"userId"`
|
||||
StoreName string `json:"storeName"`
|
||||
StoreLogo string `json:"storeLogo"`
|
||||
StoreBanner string `json:"storeBanner"`
|
||||
StoreInfo string `json:"storeInfo"`
|
||||
StoreAddressID string `json:"storeAddressId"`
|
||||
TotalProduct int `json:"TotalProduct"`
|
||||
Followers int `json:"followers"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type RequestStoreDTO struct {
|
||||
StoreName string `json:"store_name"`
|
||||
StoreLogo string `json:"store_logo"`
|
||||
StoreBanner string `json:"store_banner"`
|
||||
StoreInfo string `json:"store_info"`
|
||||
StoreAddressID string `json:"store_address_id"`
|
||||
}
|
||||
|
||||
func (r *RequestStoreDTO) ValidateStoreInput() (map[string][]string, bool) {
|
||||
errors := make(map[string][]string)
|
||||
|
||||
if strings.TrimSpace(r.StoreName) == "" {
|
||||
errors["storeName"] = append(errors["storeName"], "Store name is required")
|
||||
} else if len(r.StoreName) < 3 {
|
||||
errors["storeName"] = append(errors["storeName"], "Store name must be at least 3 characters long")
|
||||
} else {
|
||||
validNameRegex := `^[a-zA-Z0-9_.\s]+$`
|
||||
if matched, _ := regexp.MatchString(validNameRegex, r.StoreName); !matched {
|
||||
errors["storeName"] = append(errors["storeName"], "Store name can only contain letters, numbers, underscores, and periods")
|
||||
}
|
||||
}
|
||||
|
||||
if strings.TrimSpace(r.StoreLogo) == "" {
|
||||
errors["storeLogo"] = append(errors["storeLogo"], "Store logo is required")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(r.StoreBanner) == "" {
|
||||
errors["storeBanner"] = append(errors["storeBanner"], "Store banner is required")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(r.StoreInfo) == "" {
|
||||
errors["storeInfo"] = append(errors["storeInfo"], "Store info is required")
|
||||
}
|
||||
|
||||
uuidRegex := `^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`
|
||||
if r.StoreAddressID == "" {
|
||||
errors["storeAddressId"] = append(errors["storeAddressId"], "Store address ID is required")
|
||||
} else if matched, _ := regexp.MatchString(uuidRegex, r.StoreAddressID); !matched {
|
||||
errors["storeAddressId"] = append(errors["storeAddressId"], "Invalid Store Address ID format")
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return errors, false
|
||||
}
|
||||
|
||||
return nil, true
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/pahmiudahgede/senggoldong/dto"
|
||||
"github.com/pahmiudahgede/senggoldong/internal/services"
|
||||
"github.com/pahmiudahgede/senggoldong/utils"
|
||||
)
|
||||
|
||||
type StoreHandler struct {
|
||||
StoreService services.StoreService
|
||||
}
|
||||
|
||||
func NewStoreHandler(storeService services.StoreService) *StoreHandler {
|
||||
return &StoreHandler{StoreService: storeService}
|
||||
}
|
||||
|
||||
func (h *StoreHandler) CreateStore(c *fiber.Ctx) error {
|
||||
|
||||
var requestStoreDTO dto.RequestStoreDTO
|
||||
if err := c.BodyParser(&requestStoreDTO); err != nil {
|
||||
|
||||
log.Printf("Error parsing body: %v", err)
|
||||
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid request body"}})
|
||||
}
|
||||
|
||||
errors, valid := requestStoreDTO.ValidateStoreInput()
|
||||
if !valid {
|
||||
|
||||
return utils.ValidationErrorResponse(c, errors)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
store, err := h.StoreService.CreateStore(userID, &requestStoreDTO)
|
||||
if err != nil {
|
||||
|
||||
log.Printf("Error creating store: %v", err)
|
||||
return utils.GenericResponse(c, fiber.StatusConflict, err.Error())
|
||||
}
|
||||
|
||||
return utils.CreateResponse(c, store, "store created successfully")
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package repositories
|
||||
|
||||
import (
|
||||
"github.com/pahmiudahgede/senggoldong/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type StoreRepository interface {
|
||||
FindStoreByUserID(userID string) (*model.Store, error)
|
||||
FindAddressByID(addressID string) (*model.Address, error)
|
||||
CreateStore(store *model.Store) error
|
||||
}
|
||||
|
||||
type storeRepository struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewStoreRepository(DB *gorm.DB) StoreRepository {
|
||||
return &storeRepository{DB}
|
||||
}
|
||||
|
||||
func (r *storeRepository) FindStoreByUserID(userID string) (*model.Store, error) {
|
||||
var store model.Store
|
||||
if err := r.DB.Where("user_id = ?", userID).First(&store).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &store, nil
|
||||
}
|
||||
|
||||
func (r *storeRepository) FindAddressByID(addressID string) (*model.Address, error) {
|
||||
var address model.Address
|
||||
if err := r.DB.Where("id = ?", addressID).First(&address).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &address, nil
|
||||
}
|
||||
|
||||
func (r *storeRepository) CreateStore(store *model.Store) error {
|
||||
if err := r.DB.Create(store).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pahmiudahgede/senggoldong/dto"
|
||||
"github.com/pahmiudahgede/senggoldong/internal/repositories"
|
||||
"github.com/pahmiudahgede/senggoldong/model"
|
||||
"github.com/pahmiudahgede/senggoldong/utils"
|
||||
)
|
||||
|
||||
type StoreService interface {
|
||||
CreateStore(userID string, storeDTO *dto.RequestStoreDTO) (*dto.ResponseStoreDTO, error)
|
||||
}
|
||||
|
||||
type storeService struct {
|
||||
storeRepo repositories.StoreRepository
|
||||
}
|
||||
|
||||
func NewStoreService(storeRepo repositories.StoreRepository) StoreService {
|
||||
return &storeService{storeRepo}
|
||||
}
|
||||
|
||||
func (s *storeService) CreateStore(userID string, storeDTO *dto.RequestStoreDTO) (*dto.ResponseStoreDTO, error) {
|
||||
|
||||
existingStore, err := s.storeRepo.FindStoreByUserID(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error checking if user already has a store: %w", err)
|
||||
}
|
||||
if existingStore != nil {
|
||||
return nil, fmt.Errorf("user already has a store")
|
||||
}
|
||||
|
||||
address, err := s.storeRepo.FindAddressByID(storeDTO.StoreAddressID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error validating store address ID: %w", err)
|
||||
}
|
||||
if address == nil {
|
||||
return nil, fmt.Errorf("store address ID not found")
|
||||
}
|
||||
|
||||
store := model.Store{
|
||||
UserID: userID,
|
||||
StoreName: storeDTO.StoreName,
|
||||
StoreLogo: storeDTO.StoreLogo,
|
||||
StoreBanner: storeDTO.StoreBanner,
|
||||
StoreInfo: storeDTO.StoreInfo,
|
||||
StoreAddressID: storeDTO.StoreAddressID,
|
||||
}
|
||||
|
||||
if err := s.storeRepo.CreateStore(&store); err != nil {
|
||||
return nil, fmt.Errorf("failed to create store: %w", err)
|
||||
}
|
||||
|
||||
createdAt, _ := utils.FormatDateToIndonesianFormat(store.CreatedAt)
|
||||
updatedAt, _ := utils.FormatDateToIndonesianFormat(store.UpdatedAt)
|
||||
|
||||
storeResponseDTO := &dto.ResponseStoreDTO{
|
||||
ID: store.ID,
|
||||
UserID: store.UserID,
|
||||
StoreName: store.StoreName,
|
||||
StoreLogo: store.StoreLogo,
|
||||
StoreBanner: store.StoreBanner,
|
||||
StoreInfo: store.StoreInfo,
|
||||
StoreAddressID: store.StoreAddressID,
|
||||
TotalProduct: store.TotalProduct,
|
||||
Followers: store.Followers,
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
}
|
||||
|
||||
return storeResponseDTO, nil
|
||||
}
|
|
@ -3,20 +3,20 @@ package model
|
|||
import "time"
|
||||
|
||||
type Product struct {
|
||||
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
|
||||
StoreID string `gorm:"type:uuid;not null" json:"storeId"`
|
||||
Store Store `gorm:"foreignKey:StoreID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"store"`
|
||||
ProductName string `gorm:"not null" json:"productName"`
|
||||
ProductImages []ProductImage `gorm:"foreignKey:ProductID;constraint:OnDelete:CASCADE;" json:"productImages"`
|
||||
Quantity int `gorm:"not null" json:"quantity"`
|
||||
Saled int `gorm:"default:0" json:"saled"`
|
||||
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
|
||||
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
|
||||
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null;column:id" json:"id"`
|
||||
StoreID string `gorm:"type:uuid;not null;column:store_id" json:"storeId"`
|
||||
Store Store `gorm:"foreignKey:StoreID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-"`
|
||||
ProductName string `gorm:"not null;column:product_name;index" json:"productName"`
|
||||
ProductImages []ProductImage `gorm:"foreignKey:ProductID;constraint:OnDelete:CASCADE;" json:"productImages,omitempty"`
|
||||
Quantity int `gorm:"not null;column:quantity" json:"quantity"`
|
||||
Saled int `gorm:"default:0;column:saled" json:"saled"`
|
||||
CreatedAt time.Time `gorm:"default:current_timestamp;column:created_at" json:"createdAt"`
|
||||
UpdatedAt time.Time `gorm:"default:current_timestamp;column:updated_at" json:"updatedAt"`
|
||||
}
|
||||
|
||||
type ProductImage struct {
|
||||
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
|
||||
ProductID string `gorm:"type:uuid;not null" json:"productId"`
|
||||
Product Product `gorm:"foreignKey:ProductID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"product"`
|
||||
ImageURL string `gorm:"not null" json:"imageURL"`
|
||||
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null;column:id" json:"id"`
|
||||
ProductID string `gorm:"type:uuid;not null;column:product_id" json:"productId"`
|
||||
Product Product `gorm:"foreignKey:ProductID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-"`
|
||||
ImageURL string `gorm:"not null;column:image_url" json:"imageURL"`
|
||||
}
|
||||
|
|
|
@ -3,17 +3,18 @@ package model
|
|||
import "time"
|
||||
|
||||
type Store struct {
|
||||
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
|
||||
UserID string `gorm:"type:uuid;not null" json:"userId"`
|
||||
User User `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user"`
|
||||
StoreName string `gorm:"not null" json:"storeName"`
|
||||
StoreLogo string `gorm:"not null" json:"storeLogo"`
|
||||
StoreBanner string `gorm:"not null" json:"storeBanner"`
|
||||
StoreInfo string `gorm:"not null" json:"storeInfo"`
|
||||
StoreAddressID string `gorm:"type:uuid;not null" json:"storeAddressId"`
|
||||
StoreAddress Address `gorm:"foreignKey:StoreAddressID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"storeAddress"`
|
||||
Followers int `gorm:"default:0" json:"followers"`
|
||||
Products []Product `gorm:"foreignKey:StoreID;constraint:OnDelete:CASCADE;" json:"products"`
|
||||
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
|
||||
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
|
||||
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null;column:id" json:"id"`
|
||||
UserID string `gorm:"type:uuid;not null;column:user_id" json:"userId"`
|
||||
User User `gorm:"foreignKey:UserID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-"`
|
||||
StoreName string `gorm:"not null;column:store_name;index" json:"storeName"`
|
||||
StoreLogo string `gorm:"not null;column:store_logo" json:"storeLogo"`
|
||||
StoreBanner string `gorm:"not null;column:store_banner" json:"storeBanner"`
|
||||
StoreInfo string `gorm:"not null;column:store_info" json:"storeInfo"`
|
||||
StoreAddressID string `gorm:"type:uuid;not null;column:store_address_id" json:"storeAddressId"`
|
||||
StoreAddress Address `gorm:"foreignKey:StoreAddressID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-"`
|
||||
Followers int `gorm:"default:0;column:followers" json:"followers,omitempty"`
|
||||
Products []Product `gorm:"foreignKey:StoreID;constraint:OnDelete:CASCADE;" json:"products,omitempty"`
|
||||
TotalProduct int `gorm:"default:0;column:total_product" json:"totalProduct,omitempty"`
|
||||
CreatedAt time.Time `gorm:"default:current_timestamp;column:created_at" json:"createdAt"`
|
||||
UpdatedAt time.Time `gorm:"default:current_timestamp;column:updated_at" json:"updatedAt"`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
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 StoreRouter(api fiber.Router) {
|
||||
|
||||
storeRepo := repositories.NewStoreRepository(config.DB)
|
||||
storeService := services.NewStoreService(storeRepo)
|
||||
storeHandler := handler.NewStoreHandler(storeService)
|
||||
|
||||
storeAPI := api.Group("/storerijig")
|
||||
storeAPI.Post("/create", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), storeHandler.CreateStore)
|
||||
}
|
|
@ -20,4 +20,5 @@ func SetupRoutes(app *fiber.App) {
|
|||
presentation.BannerRouter(api)
|
||||
presentation.InitialCointRoute(api)
|
||||
presentation.TrashRouter(api)
|
||||
presentation.StoreRouter(api)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue