feat: add feature user pin verif and create
This commit is contained in:
parent
b91c35333d
commit
bb85fe64d7
|
@ -33,6 +33,7 @@ func ConnectDatabase() {
|
||||||
err = DB.AutoMigrate(
|
err = DB.AutoMigrate(
|
||||||
&model.User{},
|
&model.User{},
|
||||||
&model.Role{},
|
&model.Role{},
|
||||||
|
&model.UserPin{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error performing auto-migration: %v", err)
|
log.Fatalf("Error performing auto-migration: %v", err)
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserPinResponseDTO struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
Pin string `json:"userpin"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
UpdatedAt string `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestUserPinDTO struct {
|
||||||
|
Pin string `json:"userpin"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RequestUserPinDTO) Validate() (map[string][]string, bool) {
|
||||||
|
errors := make(map[string][]string)
|
||||||
|
|
||||||
|
if strings.TrimSpace(r.Pin) == "" {
|
||||||
|
errors["pin"] = append(errors["pin"], "Pin is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Pin) != 6 {
|
||||||
|
errors["pin"] = append(errors["pin"], "Pin harus terdiri dari 6 digit")
|
||||||
|
} else if !isNumeric(r.Pin) {
|
||||||
|
errors["pin"] = append(errors["pin"], "Pin harus berupa angka")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return errors, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNumeric(s string) bool {
|
||||||
|
re := regexp.MustCompile(`^[0-9]+$`)
|
||||||
|
return re.MatchString(s)
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
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 UserPinHandler struct {
|
||||||
|
UserPinService services.UserPinService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserPinHandler(userPinService services.UserPinService) *UserPinHandler {
|
||||||
|
return &UserPinHandler{UserPinService: userPinService}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *UserPinHandler) VerifyUserPin(c *fiber.Ctx) error {
|
||||||
|
var requestUserPinDTO dto.RequestUserPinDTO
|
||||||
|
if err := c.BodyParser(&requestUserPinDTO); err != nil {
|
||||||
|
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
|
||||||
|
}
|
||||||
|
|
||||||
|
errors, valid := requestUserPinDTO.Validate()
|
||||||
|
if !valid {
|
||||||
|
return utils.ValidationErrorResponse(c, errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, ok := c.Locals("userID").(string)
|
||||||
|
if !ok || userID == "" {
|
||||||
|
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := h.UserPinService.VerifyUserPin(requestUserPinDTO.Pin, userID)
|
||||||
|
if err != nil {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *UserPinHandler) CheckPinStatus(c *fiber.Ctx) error {
|
||||||
|
userID, ok := c.Locals("userID").(string)
|
||||||
|
if !ok || userID == "" {
|
||||||
|
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
status, _, err := h.UserPinService.CheckPinStatus(userID)
|
||||||
|
if err != nil {
|
||||||
|
return utils.GenericErrorResponse(c, fiber.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == "Pin not created" {
|
||||||
|
return utils.GenericErrorResponse(c, fiber.StatusBadRequest, "pin belum dibuat")
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.LogResponse(c, map[string]string{"data": "pin sudah dibuat"}, "Pin status retrieved successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *UserPinHandler) CreateUserPin(c *fiber.Ctx) error {
|
||||||
|
var requestUserPinDTO dto.RequestUserPinDTO
|
||||||
|
if err := c.BodyParser(&requestUserPinDTO); err != nil {
|
||||||
|
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
|
||||||
|
}
|
||||||
|
|
||||||
|
errors, valid := requestUserPinDTO.Validate()
|
||||||
|
if !valid {
|
||||||
|
return utils.ValidationErrorResponse(c, errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := c.Locals("userID").(string)
|
||||||
|
|
||||||
|
userPinResponse, err := h.UserPinService.CreateUserPin(userID, requestUserPinDTO.Pin)
|
||||||
|
if err != nil {
|
||||||
|
return utils.GenericErrorResponse(c, fiber.StatusConflict, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.LogResponse(c, userPinResponse, "User pin created successfully")
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pahmiudahgede/senggoldong/model"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserPinRepository interface {
|
||||||
|
FindByUserID(userID string) (*model.UserPin, error)
|
||||||
|
FindByPin(userPin string) (*model.UserPin, error)
|
||||||
|
Create(userPin *model.UserPin) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type userPinRepository struct {
|
||||||
|
DB *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserPinRepository(db *gorm.DB) UserPinRepository {
|
||||||
|
return &userPinRepository{DB: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *userPinRepository) FindByUserID(userID string) (*model.UserPin, error) {
|
||||||
|
var userPin model.UserPin
|
||||||
|
err := r.DB.Where("user_id = ?", userID).First(&userPin).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &userPin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *userPinRepository) FindByPin(pin string) (*model.UserPin, error) {
|
||||||
|
var userPin model.UserPin
|
||||||
|
err := r.DB.Where("pin = ?", pin).First(&userPin).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &userPin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *userPinRepository) Create(userPin *model.UserPin) error {
|
||||||
|
err := r.DB.Create(userPin).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pahmiudahgede/senggoldong/dto"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/internal/repositories"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/model"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/utils"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserPinService interface {
|
||||||
|
CreateUserPin(userID, pin string) (*dto.UserPinResponseDTO, error)
|
||||||
|
VerifyUserPin(userID, pin string) (*dto.UserPinResponseDTO, error)
|
||||||
|
CheckPinStatus(userID string) (string, *dto.UserPinResponseDTO, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type userPinService struct {
|
||||||
|
UserPinRepo repositories.UserPinRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserPinService(userPinRepo repositories.UserPinRepository) UserPinService {
|
||||||
|
return &userPinService{UserPinRepo: userPinRepo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *userPinService) VerifyUserPin(pin string, userID string) (*dto.UserPinResponseDTO, error) {
|
||||||
|
|
||||||
|
userPin, err := s.UserPinRepo.FindByUserID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("user pin not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bcrypt.CompareHashAndPassword([]byte(userPin.Pin), []byte(pin))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("incorrect pin")
|
||||||
|
}
|
||||||
|
|
||||||
|
createdAt, _ := utils.FormatDateToIndonesianFormat(userPin.CreatedAt)
|
||||||
|
updatedAt, _ := utils.FormatDateToIndonesianFormat(userPin.UpdatedAt)
|
||||||
|
|
||||||
|
userPinResponse := &dto.UserPinResponseDTO{
|
||||||
|
ID: userPin.ID,
|
||||||
|
UserID: userPin.UserID,
|
||||||
|
Pin: userPin.Pin,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return userPinResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *userPinService) CheckPinStatus(userID string) (string, *dto.UserPinResponseDTO, error) {
|
||||||
|
userPin, err := s.UserPinRepo.FindByUserID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return "Pin not created", nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
createdAt, _ := utils.FormatDateToIndonesianFormat(userPin.CreatedAt)
|
||||||
|
updatedAt, _ := utils.FormatDateToIndonesianFormat(userPin.UpdatedAt)
|
||||||
|
|
||||||
|
userPinResponse := &dto.UserPinResponseDTO{
|
||||||
|
ID: userPin.ID,
|
||||||
|
UserID: userPin.UserID,
|
||||||
|
Pin: userPin.Pin,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Pin already created", userPinResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *userPinService) CreateUserPin(userID, pin string) (*dto.UserPinResponseDTO, error) {
|
||||||
|
|
||||||
|
existingPin, err := s.UserPinRepo.FindByUserID(userID)
|
||||||
|
if err != nil && existingPin != nil {
|
||||||
|
return nil, fmt.Errorf("you have already created a pin, you don't need to create another one")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedPin, err := bcrypt.GenerateFromPassword([]byte(pin), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error hashing the pin: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newPin := model.UserPin{
|
||||||
|
UserID: userID,
|
||||||
|
Pin: string(hashedPin),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.UserPinRepo.Create(&newPin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating user pin: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createdAt, _ := utils.FormatDateToIndonesianFormat(newPin.CreatedAt)
|
||||||
|
updatedAt, _ := utils.FormatDateToIndonesianFormat(newPin.UpdatedAt)
|
||||||
|
|
||||||
|
userPinResponse := &dto.UserPinResponseDTO{
|
||||||
|
ID: newPin.ID,
|
||||||
|
UserID: newPin.UserID,
|
||||||
|
Pin: newPin.Pin,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheKey := fmt.Sprintf("userpin:%s", userID)
|
||||||
|
cacheData := map[string]interface{}{
|
||||||
|
"data": userPinResponse,
|
||||||
|
}
|
||||||
|
err = utils.SetJSONData(cacheKey, cacheData, time.Hour*24)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error caching new user pin to Redis: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return userPinResponse, nil
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type UserPin struct {
|
||||||
|
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
|
||||||
|
UserID string `gorm:"not null" json:"userId"`
|
||||||
|
Pin string `gorm:"not null" json:"pin"`
|
||||||
|
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
|
||||||
|
}
|
|
@ -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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UserPinRouter(api fiber.Router) {
|
||||||
|
|
||||||
|
userPinRepo := repositories.NewUserPinRepository(config.DB)
|
||||||
|
|
||||||
|
userPinService := services.NewUserPinService(userPinRepo)
|
||||||
|
|
||||||
|
userPinHandler := handler.NewUserPinHandler(userPinService)
|
||||||
|
|
||||||
|
api.Post("/user/set-pin", middleware.AuthMiddleware, userPinHandler.CreateUserPin)
|
||||||
|
api.Post("/user/verif-pin", middleware.AuthMiddleware, userPinHandler.VerifyUserPin)
|
||||||
|
api.Get("/user/cek-pin-status", middleware.AuthMiddleware, userPinHandler.CheckPinStatus)
|
||||||
|
|
||||||
|
}
|
|
@ -11,4 +11,5 @@ func SetupRoutes(app *fiber.App) {
|
||||||
api.Use(middleware.APIKeyMiddleware)
|
api.Use(middleware.APIKeyMiddleware)
|
||||||
presentation.AuthRouter(api)
|
presentation.AuthRouter(api)
|
||||||
presentation.UserProfileRouter(api)
|
presentation.UserProfileRouter(api)
|
||||||
|
presentation.UserPinRouter(api)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue