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(
|
||||
&model.User{},
|
||||
&model.Role{},
|
||||
&model.UserPin{},
|
||||
)
|
||||
if err != nil {
|
||||
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)
|
||||
presentation.AuthRouter(api)
|
||||
presentation.UserProfileRouter(api)
|
||||
presentation.UserPinRouter(api)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue