feat: add feature set pin for user

This commit is contained in:
pahmiudahgede 2024-12-10 06:39:32 +07:00
parent add86d7d83
commit 5636d99dc9
13 changed files with 339 additions and 10 deletions

View File

@ -53,6 +53,9 @@ func InitDatabase() {
err = DB.AutoMigrate(
&domain.User{},
&domain.UserRole{},
&domain.UserPin{},
&domain.MenuAccess{},
&domain.PlatformHandle{},
&domain.Address{},
)
if err != nil {

View File

@ -1,8 +1,6 @@
package domain
import (
"time"
)
import "time"
type Address struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`

14
domain/menuaccess.go Normal file
View File

@ -0,0 +1,14 @@
package domain
import "time"
type MenuAccess struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
RoleID string `gorm:"not null" json:"roleId"`
Role UserRole `gorm:"foreignKey:RoleID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"role"`
MenuName string `gorm:"not null" json:"menuName"`
Path string `gorm:"not null" json:"path"`
IconURL string `gorm:"not null" json:"iconUrl"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
}

7
domain/platform.go Normal file
View File

@ -0,0 +1,7 @@
package domain
type PlatformHandle struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
Platform string `gorm:"not null" json:"platform"`
Description string `gorm:"not null" json:"description"`
}

View File

@ -1,18 +1,17 @@
package domain
import (
"time"
)
import "time"
type User struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
Avatar *string `json:"avatar,omitempty"`
Username string `gorm:"unique;not null" json:"username"`
Name string `gorm:"not null" json:"name"`
Name string `gorm:"not null" json:"name"`
Phone string `gorm:"not null" json:"phone"`
Email string `gorm:"unique;not null" json:"email"`
EmailVerified bool `gorm:"default:false" json:"emailVerified"`
Password string `gorm:"not null" json:"password"`
Pin UserPin `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"pin"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
RoleID string `gorm:"not null" json:"roleId"`

11
domain/userpin.go Normal file
View File

@ -0,0 +1,11 @@
package domain
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"`
}

View File

@ -16,8 +16,6 @@ type AddressInput struct {
Geography string `json:"geography" validate:"required"`
}
var validate = validator.New()
func (c *AddressInput) ValidatePost() error {
err := validate.Struct(c)
if err != nil {

44
dto/userpin.go Normal file
View File

@ -0,0 +1,44 @@
package dto
import (
"fmt"
"github.com/go-playground/validator/v10"
)
type PinInput struct {
Pin string `json:"pin" validate:"required,len=6,numeric"`
}
func (p *PinInput) ValidateCreate() error {
err := validate.Struct(p)
if err != nil {
for _, e := range err.(validator.ValidationErrors) {
switch e.Field() {
case "Pin":
return fmt.Errorf("PIN harus terdiri dari 6 digit angka")
}
}
}
return nil
}
type PinUpdateInput struct {
OldPin string `json:"old_pin" validate:"required,len=6,numeric"`
NewPin string `json:"new_pin" validate:"required,len=6,numeric"`
}
func (p *PinUpdateInput) ValidateUpdate() error {
err := validate.Struct(p)
if err != nil {
for _, e := range err.(validator.ValidationErrors) {
switch e.Field() {
case "OldPin":
return fmt.Errorf("PIN lama harus terdiri dari 6 digit angka")
case "NewPin":
return fmt.Errorf("PIN baru harus terdiri dari 6 digit angka")
}
}
}
return nil
}

5
dto/validator.go Normal file
View File

@ -0,0 +1,5 @@
package dto
import "github.com/go-playground/validator/v10"
var validate = validator.New()

View File

@ -7,16 +7,24 @@ import (
)
func AppRouter(app *fiber.App) {
// # authentication
app.Post("/register", controllers.Register)
app.Post("/login", controllers.Login)
// # userinfo
app.Get("/user", middleware.AuthMiddleware, controllers.GetUserInfo)
app.Put("/update-user", middleware.AuthMiddleware, controllers.UpdateUser)
app.Post("/user/update-password", middleware.AuthMiddleware, controllers.UpdatePassword)
// # user set pin
app.Post("/user/set-pin", middleware.AuthMiddleware, controllers.CreatePin)
app.Get("/user/get-pin", middleware.AuthMiddleware, controllers.GetPin)
app.Put("/user/update-pin", middleware.AuthMiddleware, controllers.UpdatePin)
// # address routing
app.Get("/list-address", middleware.AuthMiddleware, controllers.GetListAddress)
app.Get("/address/:id", middleware.AuthMiddleware, controllers.GetAddressByID)
app.Post("/create-address", middleware.AuthMiddleware, controllers.CreateAddress)
app.Put("/address/:id", middleware.AuthMiddleware, controllers.UpdateAddress)
app.Delete("/address/:id", middleware.AuthMiddleware, controllers.DeleteAddress)
}
}

View File

@ -0,0 +1,132 @@
package controllers
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
func CreatePin(c *fiber.Ctx) error {
var input dto.PinInput
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Data input tidak valid",
nil,
))
}
if err := input.ValidateCreate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
err.Error(),
nil,
))
}
userID := c.Locals("userID").(string)
existingPin, err := services.GetPinByUserID(userID)
if err == nil && existingPin.ID != "" {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"PIN sudah ada, tidak perlu dibuat lagi",
nil,
))
}
pin, err := services.CreatePin(userID, input)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to create PIN",
nil,
))
}
pinResponse := map[string]interface{}{
"id": pin.ID,
"createdAt": pin.CreatedAt,
"updatedAt": pin.UpdatedAt,
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"PIN created successfully",
pinResponse,
))
}
func GetPin(c *fiber.Ctx) error {
var input dto.PinInput
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Data input tidak valid",
nil,
))
}
userID := c.Locals("userID").(string)
pin, err := services.GetPinByUserID(userID)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse(
fiber.StatusNotFound,
"PIN tidak ditemukan",
nil,
))
}
isPinValid := services.CheckPin(pin.Pin, input.Pin)
if isPinValid {
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"PIN benar",
true,
))
}
return c.Status(fiber.StatusUnauthorized).JSON(utils.FormatResponse(
fiber.StatusUnauthorized,
"PIN salah",
false,
))
}
func UpdatePin(c *fiber.Ctx) error {
var input dto.PinUpdateInput
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
"Data input tidak valid",
nil,
))
}
if err := input.ValidateUpdate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse(
fiber.StatusBadRequest,
err.Error(),
nil,
))
}
userID := c.Locals("userID").(string)
updatedPin, err := services.UpdatePin(userID, input.OldPin, input.NewPin)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse(
fiber.StatusInternalServerError,
"Failed to update PIN",
nil,
))
}
return c.Status(fiber.StatusOK).JSON(utils.FormatResponse(
fiber.StatusOK,
"PIN updated successfully",
updatedPin,
))
}

View File

@ -0,0 +1,48 @@
package repositories
import (
"errors"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/domain"
"golang.org/x/crypto/bcrypt"
)
func CreatePin(pin *domain.UserPin) error {
result := config.DB.Create(pin)
if result.Error != nil {
return result.Error
}
return nil
}
func GetPinByUserID(userID string) (domain.UserPin, error) {
var pin domain.UserPin
err := config.DB.Where("user_id = ?", userID).First(&pin).Error
if err != nil {
return pin, errors.New("PIN tidak ditemukan")
}
return pin, nil
}
func UpdatePin(userID string, newPin string) (domain.UserPin, error) {
var pin domain.UserPin
err := config.DB.Where("user_id = ?", userID).First(&pin).Error
if err != nil {
return pin, errors.New("PIN tidak ditemukan")
}
hashedPin, err := bcrypt.GenerateFromPassword([]byte(newPin), bcrypt.DefaultCost)
if err != nil {
return pin, err
}
pin.Pin = string(hashedPin)
if err := config.DB.Save(&pin).Error; err != nil {
return pin, err
}
return pin, nil
}

View File

@ -0,0 +1,62 @@
package services
import (
"errors"
"github.com/pahmiudahgede/senggoldong/domain"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"golang.org/x/crypto/bcrypt"
)
func GetPinByUserID(userID string) (domain.UserPin, error) {
pin, err := repositories.GetPinByUserID(userID)
if err != nil {
return pin, errors.New("PIN tidak ditemukan")
}
return pin, nil
}
func CreatePin(userID string, input dto.PinInput) (domain.UserPin, error) {
hashedPin, err := bcrypt.GenerateFromPassword([]byte(input.Pin), bcrypt.DefaultCost)
if err != nil {
return domain.UserPin{}, err
}
pin := domain.UserPin{
UserID: userID,
Pin: string(hashedPin),
}
err = repositories.CreatePin(&pin)
if err != nil {
return domain.UserPin{}, err
}
return pin, nil
}
func UpdatePin(userID string, oldPin string, newPin string) (domain.UserPin, error) {
pin, err := repositories.GetPinByUserID(userID)
if err != nil {
return pin, errors.New("PIN tidak ditemukan")
}
if err := bcrypt.CompareHashAndPassword([]byte(pin.Pin), []byte(oldPin)); err != nil {
return pin, errors.New("PIN lama tidak cocok")
}
updatedPin, err := repositories.UpdatePin(userID, newPin)
if err != nil {
return updatedPin, err
}
return updatedPin, nil
}
func CheckPin(storedPinHash string, inputPin string) bool {
err := bcrypt.CompareHashAndPassword([]byte(storedPinHash), []byte(inputPin))
return err == nil
}