feat&fix: add feature address and fixing some response format

This commit is contained in:
pahmiudahgede 2025-02-07 20:02:16 +07:00
parent 2e8afdb365
commit d997a75674
15 changed files with 268 additions and 28 deletions

View File

@ -42,6 +42,7 @@ func ConnectDatabase() {
&model.User{},
&model.Role{},
&model.UserPin{},
&model.Address{},
// ==main feature==
)
if err != nil {

61
dto/address_dto.go Normal file
View File

@ -0,0 +1,61 @@
package dto
import "strings"
type AddressResponseDTO struct {
UserID string `json:"user_id"`
ID string `json:"address_id"`
Province string `json:"province"`
Regency string `json:"regency"`
District string `json:"district"`
Village string `json:"village"`
PostalCode string `json:"postalCode"`
Detail string `json:"detail"`
Geography string `json:"geography"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type CreateAddressDTO struct {
Province string `json:"province_id"`
Regency string `json:"regency_id"`
District string `json:"district_id"`
Village string `json:"village_id"`
PostalCode string `json:"postalCode"`
Detail string `json:"detail"`
Geography string `json:"geography"`
}
func (r *CreateAddressDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Province) == "" {
errors["province_id"] = append(errors["province_id"], "Province ID is required")
}
if strings.TrimSpace(r.Regency) == "" {
errors["regency_id"] = append(errors["regency_id"], "Regency ID is required")
}
if strings.TrimSpace(r.District) == "" {
errors["district_id"] = append(errors["district_id"], "District ID is required")
}
if strings.TrimSpace(r.Village) == "" {
errors["village_id"] = append(errors["village_id"], "Village ID is required")
}
if strings.TrimSpace(r.PostalCode) == "" {
errors["postalCode"] = append(errors["village_id"], "PostalCode ID is required")
} else if len(r.PostalCode) < 5 {
errors["postalCode"] = append(errors["postalCode"], "kode pos belum sesuai")
}
if strings.TrimSpace(r.Detail) == "" {
errors["detail"] = append(errors["detail"], "Detail address is required")
}
if strings.TrimSpace(r.Geography) == "" {
errors["geography"] = append(errors["geography"], "Geographic coordinates are required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -0,0 +1,35 @@
package handler
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type AddressHandler struct {
AddressService services.AddressService
}
func NewAddressHandler(addressService services.AddressService) *AddressHandler {
return &AddressHandler{AddressService: addressService}
}
func (h *AddressHandler) CreateAddress(c *fiber.Ctx) error {
var requestAddressDTO dto.CreateAddressDTO
if err := c.BodyParser(&requestAddressDTO); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := requestAddressDTO.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
addressResponse, err := h.AddressService.CreateAddress(c.Locals("userID").(string), requestAddressDTO)
if err != nil {
return utils.GenericErrorResponse(c, fiber.StatusBadRequest, err.Error())
}
return utils.CreateResponse(c, addressResponse, "user address created successfully")
}

View File

@ -33,7 +33,7 @@ func (h *UserHandler) Login(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, err.Error())
}
return utils.LogResponse(c, user, "Login successful")
return utils.SuccessResponse(c, user, "Login successful")
}
func (h *UserHandler) Register(c *fiber.Ctx) error {
@ -67,7 +67,7 @@ func (h *UserHandler) Register(c *fiber.Ctx) error {
UpdatedAt: updatedAt,
}
return utils.LogResponse(c, userResponse, "Registration successful")
return utils.CreateResponse(c, userResponse, "Registration successful")
}
func (h *UserHandler) Logout(c *fiber.Ctx) error {

View File

@ -26,7 +26,7 @@ func (h *RoleHandler) GetRoles(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.LogResponse(c, roles, "Roles fetched successfully")
return utils.SuccessResponse(c, roles, "Roles fetched successfully")
}
func (h *RoleHandler) GetRoleByID(c *fiber.Ctx) error {
@ -42,5 +42,5 @@ func (h *RoleHandler) GetRoleByID(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusNotFound, "role id tidak ditemukan")
}
return utils.LogResponse(c, role, "Role fetched successfully")
return utils.SuccessResponse(c, role, "Role fetched successfully")
}

View File

@ -27,7 +27,7 @@ func (h *UserProfileHandler) GetUserProfile(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusNotFound, err.Error())
}
return utils.LogResponse(c, userProfile, "User profile retrieved successfully")
return utils.SuccessResponse(c, userProfile, "User profile retrieved successfully")
}
func (h *UserProfileHandler) UpdateUserProfile(c *fiber.Ctx) error {
@ -51,7 +51,7 @@ func (h *UserProfileHandler) UpdateUserProfile(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusConflict, err.Error())
}
return utils.LogResponse(c, userResponse, "User profile updated successfully")
return utils.SuccessResponse(c, userResponse, "User profile updated successfully")
}
func (h *UserProfileHandler) UpdateUserPassword(c *fiber.Ctx) error {
@ -75,7 +75,7 @@ func (h *UserProfileHandler) UpdateUserPassword(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusBadRequest, err.Error())
}
return utils.LogResponse(c, userResponse, "Password updated successfully")
return utils.SuccessResponse(c, userResponse, "Password updated successfully")
}
func (h *UserProfileHandler) UpdateUserAvatar(c *fiber.Ctx) error {
@ -94,5 +94,5 @@ func (h *UserProfileHandler) UpdateUserAvatar(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.LogResponse(c, userResponse, "Avatar updated successfully")
return utils.SuccessResponse(c, userResponse, "Avatar updated successfully")
}

View File

@ -36,7 +36,7 @@ func (h *UserPinHandler) VerifyUserPin(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "pin yang anda masukkan salah")
}
return utils.LogResponse(c, map[string]string{"data": "pin yang anda masukkan benar"}, "Pin verification successful")
return utils.SuccessResponse(c, map[string]string{"data": "pin yang anda masukkan benar"}, "Pin verification successful")
}
func (h *UserPinHandler) CheckPinStatus(c *fiber.Ctx) error {
@ -54,7 +54,7 @@ func (h *UserPinHandler) CheckPinStatus(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusBadRequest, "pin belum dibuat")
}
return utils.LogResponse(c, map[string]string{"data": "pin sudah dibuat"}, "Pin status retrieved successfully")
return utils.SuccessResponse(c, map[string]string{"data": "pin sudah dibuat"}, "Pin status retrieved successfully")
}
func (h *UserPinHandler) CreateUserPin(c *fiber.Ctx) error {
@ -75,7 +75,7 @@ func (h *UserPinHandler) CreateUserPin(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusConflict, err.Error())
}
return utils.LogResponse(c, userPinResponse, "User pin created successfully")
return utils.CreateResponse(c, userPinResponse, "User pin created successfully")
}
func (h *UserPinHandler) UpdateUserPin(c *fiber.Ctx) error {
@ -96,5 +96,5 @@ func (h *UserPinHandler) UpdateUserPin(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusBadRequest, err.Error())
}
return utils.LogResponse(c, userPinResponse, "User pin updated successfully")
return utils.SuccessResponse(c, userPinResponse, "User pin updated successfully")
}

View File

@ -23,7 +23,7 @@ func (h *WilayahIndonesiaHandler) ImportWilayahData(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.GenericErrorResponse(c, fiber.StatusCreated, "Data imported successfully")
return utils.SuccessResponse(c, fiber.StatusCreated, "Data imported successfully")
}
func (h *WilayahIndonesiaHandler) GetProvinces(c *fiber.Ctx) error {
@ -195,5 +195,5 @@ func (h *WilayahIndonesiaHandler) GetVillageByID(c *fiber.Ctx) error {
return utils.GenericErrorResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.LogResponse(c, village, "Village fetched successfully")
return utils.SuccessResponse(c, village, "Village fetched successfully")
}

View File

@ -0,0 +1,22 @@
package repositories
import (
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type AddressRepository interface {
CreateAddress(address *model.Address) error
}
type addressRepository struct {
DB *gorm.DB
}
func NewAddressRepository(db *gorm.DB) AddressRepository {
return &addressRepository{DB: db}
}
func (r *addressRepository) CreateAddress(address *model.Address) error {
return r.DB.Create(address).Error
}

View File

@ -0,0 +1,88 @@
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 AddressService interface {
CreateAddress(userID string, request dto.CreateAddressDTO) (*dto.AddressResponseDTO, error)
}
type addressService struct {
AddressRepo repositories.AddressRepository
WilayahRepo repositories.WilayahIndonesiaRepository
}
func NewAddressService(addressRepo repositories.AddressRepository, wilayahRepo repositories.WilayahIndonesiaRepository) AddressService {
return &addressService{
AddressRepo: addressRepo,
WilayahRepo: wilayahRepo,
}
}
func (s *addressService) CreateAddress(userID string, request dto.CreateAddressDTO) (*dto.AddressResponseDTO, error) {
errors, valid := request.Validate()
if !valid {
return nil, fmt.Errorf("validation failed: %v", errors)
}
province, _, err := s.WilayahRepo.FindProvinceByID(request.Province, 0, 0)
if err != nil {
return nil, fmt.Errorf("invalid province_id")
}
regency, _, err := s.WilayahRepo.FindRegencyByID(request.Regency, 0, 0)
if err != nil {
return nil, fmt.Errorf("invalid regency_id")
}
district, _, err := s.WilayahRepo.FindDistrictByID(request.District, 0, 0)
if err != nil {
return nil, fmt.Errorf("invalid district_id")
}
village, err := s.WilayahRepo.FindVillageByID(request.Village)
if err != nil {
return nil, fmt.Errorf("invalid village_id")
}
newAddress := &model.Address{
UserID: userID,
Province: province.Name,
Regency: regency.Name,
District: district.Name,
Village: village.Name,
PostalCode: request.PostalCode,
Detail: request.Detail,
Geography: request.Geography,
}
err = s.AddressRepo.CreateAddress(newAddress)
if err != nil {
return nil, fmt.Errorf("failed to create user address: %v", err)
}
createdAt, _ := utils.FormatDateToIndonesianFormat(newAddress.CreatedAt)
addressResponse := &dto.AddressResponseDTO{
UserID: newAddress.UserID,
ID: newAddress.ID,
Province: newAddress.Province,
Regency: newAddress.Regency,
District: newAddress.District,
Village: newAddress.Village,
PostalCode: newAddress.PostalCode,
Detail: newAddress.Detail,
Geography: newAddress.Geography,
CreatedAt: createdAt,
UpdatedAt: createdAt,
}
return addressResponse, nil
}

View File

@ -3,16 +3,16 @@ package model
import "time"
type Address struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
UserID string `gorm:"not null" json:"userId"`
User User `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user"`
Province string `gorm:"not null" json:"province"`
District string `gorm:"not null" json:"district"`
Subdistrict string `gorm:"not null" json:"subdistrict"`
PostalCode int `gorm:"not null" json:"postalCode"`
Village string `gorm:"not null" json:"village"`
Detail string `gorm:"not null" json:"detail"`
Geography string `gorm:"not null" json:"geography"`
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" json:"id"`
UserID string `gorm:"not null" json:"userId"`
User User `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user"`
Province string `gorm:"not null" json:"province"`
Regency string `gorm:"not null" json:"regency"`
District string `gorm:"not null" json:"district"`
Village string `gorm:"not null" json:"village"`
PostalCode string `gorm:"not null" json:"postalCode"`
Detail string `gorm:"not null" json:"detail"`
Geography string `gorm:"not null" json:"geography"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
}

View File

@ -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"
)
func AddressRouter(api fiber.Router) {
addressRepo := repositories.NewAddressRepository(config.DB)
wilayahRepo := repositories.NewWilayahIndonesiaRepository(config.DB)
addressService := services.NewAddressService(addressRepo, wilayahRepo)
addressHandler := handler.NewAddressHandler(addressService)
adddressAPI := api.Group("/user/address")
adddressAPI.Post("/create-address", middleware.AuthMiddleware, addressHandler.CreateAddress)
}

View File

@ -1,4 +1,3 @@
// presentation/role_route.go
package presentation
import (

View File

@ -9,9 +9,11 @@ import (
func SetupRoutes(app *fiber.App) {
api := app.Group("/apirijikid")
api.Use(middleware.APIKeyMiddleware)
presentation.AuthRouter(api)
presentation.UserProfileRouter(api)
presentation.UserPinRouter(api)
presentation.RoleRouter(api)
presentation.WilayahRouter(api)
presentation.AddressRouter(api)
}

View File

@ -84,7 +84,7 @@ func GenericErrorResponse(c *fiber.Ctx, status int, message string) error {
return c.Status(status).JSON(response)
}
func LogResponse(c *fiber.Ctx, data interface{}, message string) error {
func SuccessResponse(c *fiber.Ctx, data interface{}, message string) error {
response := APIResponse{
Meta: MetaData{
Status: fiber.StatusOK,
@ -94,3 +94,14 @@ func LogResponse(c *fiber.Ctx, data interface{}, message string) error {
}
return c.Status(fiber.StatusOK).JSON(response)
}
func CreateResponse(c *fiber.Ctx, data interface{}, message string) error {
response := APIResponse{
Meta: MetaData{
Status: fiber.StatusCreated,
Message: message,
},
Data: data,
}
return c.Status(fiber.StatusOK).JSON(response)
}