refact:auth optimize and session management

This commit is contained in:
pahmiudahgede 2025-05-05 02:18:17 +07:00
parent e65d6383fc
commit 7b9247014a
42 changed files with 2140 additions and 362 deletions

View File

@ -13,27 +13,33 @@ func main() {
app := fiber.New() app := fiber.New()
app.Use(cors.New(cors.Config{ app.Use(cors.New(cors.Config{
AllowOrigins: "http://localhost:3000", AllowOrigins: "*",
AllowMethods: "GET,POST,PUT,DELETE,OPTIONS", AllowMethods: "GET,POST,PUT,PATCH,DELETE",
AllowHeaders: "Origin, Content-Type, Accept, Authorization, x-api-key", AllowHeaders: "Content-Type,x-api-key",
AllowCredentials: true,
})) }))
app.Use(func(c *fiber.Ctx) error { // app.Use(cors.New(cors.Config{
c.Set("Access-Control-Allow-Origin", "http://localhost:3000") // AllowOrigins: "http://localhost:3000",
c.Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS") // AllowMethods: "GET,POST,PUT,DELETE,OPTIONS",
c.Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, x-api-key") // AllowHeaders: "Origin, Content-Type, Accept, Authorization, x-api-key",
c.Set("Access-Control-Allow-Credentials", "true") // AllowCredentials: true,
return c.Next() // }))
})
app.Options("*", func(c *fiber.Ctx) error { // app.Use(func(c *fiber.Ctx) error {
c.Set("Access-Control-Allow-Origin", "http://localhost:3000") // c.Set("Access-Control-Allow-Origin", "http://localhost:3000")
c.Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS") // c.Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
c.Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, x-api-key") // c.Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, x-api-key")
c.Set("Access-Control-Allow-Credentials", "true") // c.Set("Access-Control-Allow-Credentials", "true")
return c.SendStatus(fiber.StatusNoContent) // return c.Next()
}) // })
// app.Options("*", func(c *fiber.Ctx) error {
// c.Set("Access-Control-Allow-Origin", "http://localhost:3000")
// c.Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
// c.Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, x-api-key")
// c.Set("Access-Control-Allow-Credentials", "true")
// return c.SendStatus(fiber.StatusNoContent)
// })
router.SetupRoutes(app) router.SetupRoutes(app)

View File

@ -45,6 +45,8 @@ func ConnectDatabase() {
&model.Role{}, &model.Role{},
&model.UserPin{}, &model.UserPin{},
&model.Address{}, &model.Address{},
&model.IdentityCard{},
&model.CompanyProfile{},
// =>user preparation<= // =>user preparation<=
// =>store preparation<= // =>store preparation<=

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"strconv"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
) )
@ -13,13 +14,21 @@ var RedisClient *redis.Client
var Ctx = context.Background() var Ctx = context.Background()
func ConnectRedis() { func ConnectRedis() {
redisDBStr := os.Getenv("REDIS_DB")
redisDB, err := strconv.Atoi(redisDBStr)
if err != nil {
log.Fatalf("Error converting REDIS_DB to integer: %v", err)
}
RedisClient = redis.NewClient(&redis.Options{ RedisClient = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%s", os.Getenv("REDIS_HOST"), os.Getenv("REDIS_PORT")), Addr: fmt.Sprintf("%s:%s", os.Getenv("REDIS_HOST"), os.Getenv("REDIS_PORT")),
Password: os.Getenv("REDIS_PASSWORD"), Password: os.Getenv("REDIS_PASSWORD"),
DB: 0, DB: redisDB,
}) })
_, err := RedisClient.Ping(Ctx).Result() _, err = RedisClient.Ping(Ctx).Result()
if err != nil { if err != nil {
log.Fatalf("Error connecting to Redis: %v", err) log.Fatalf("Error connecting to Redis: %v", err)
} }

View File

@ -18,8 +18,8 @@ func StartServer(app *fiber.App) {
address := fmt.Sprintf("%s:%s", host, port) address := fmt.Sprintf("%s:%s", host, port)
log.Printf("Server is running on http://%s", address) log.Printf("server berjalan di http://%s", address)
if err := app.Listen(address); err != nil { if err := app.Listen(address); err != nil {
log.Fatalf("Error starting server: %v", err) log.Fatalf("gagal saat menjalankan server: %v", err)
} }
} }

130
dto/auth/auth_admin_dto.go Normal file
View File

@ -0,0 +1,130 @@
package dto
import (
"regexp"
"strings"
)
type LoginAdminRequest struct {
Deviceid string `json:"device_id"`
Email string `json:"email"`
Password string `json:"password"`
}
type LoginResponse struct {
UserID string `json:"user_id"`
Role string `json:"role"`
Token string `json:"token"`
}
type RegisterAdminRequest struct {
Name string `json:"name"`
Gender string `json:"gender"`
Dateofbirth string `json:"dateofbirth"`
Placeofbirth string `json:"placeofbirth"`
Phone string `json:"phone"`
Email string `json:"email"`
Password string `json:"password"`
PasswordConfirm string `json:"password_confirm"`
}
type UserAdminDataResponse struct {
UserID string `json:"user_id"`
Name string `json:"name"`
Gender string `json:"gender"`
Dateofbirth string `json:"dateofbirth"`
Placeofbirth string `json:"placeofbirth"`
Phone string `json:"phone"`
Email string `json:"email"`
Password string `json:"password"`
Role string `json:"role"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
func (r *RegisterAdminRequest) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Name) == "" {
errors["name"] = append(errors["name"], "Name is required")
}
if strings.TrimSpace(r.Gender) == "" {
errors["gender"] = append(errors["gender"], "Gender is required")
} else if r.Gender != "male" && r.Gender != "female" {
errors["gender"] = append(errors["gender"], "Gender must be either 'male' or 'female'")
}
if strings.TrimSpace(r.Dateofbirth) == "" {
errors["dateofbirth"] = append(errors["dateofbirth"], "Date of birth is required")
}
if strings.TrimSpace(r.Placeofbirth) == "" {
errors["placeofbirth"] = append(errors["placeofbirth"], "Place of birth is required")
}
if strings.TrimSpace(r.Phone) == "" {
errors["phone"] = append(errors["phone"], "Phone is required")
} else if !IsValidPhoneNumber(r.Phone) {
errors["phone"] = append(errors["phone"], "Invalid phone number format. Use 62 followed by 9-13 digits")
}
if strings.TrimSpace(r.Email) == "" {
errors["email"] = append(errors["email"], "Email is required")
} else if !IsValidEmail(r.Email) {
errors["email"] = append(errors["email"], "Invalid email format")
}
if len(r.Password) < 6 {
errors["password"] = append(errors["password"], "Password must be at least 6 characters")
} else if !IsValidPassword(r.Password) {
errors["password"] = append(errors["password"], "Password must contain at least one uppercase letter, one number, and one special character")
}
if r.Password != r.PasswordConfirm {
errors["password_confirm"] = append(errors["password_confirm"], "Password and confirmation do not match")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func IsValidPhoneNumber(phone string) bool {
re := regexp.MustCompile(`^62\d{9,13}$`)
return re.MatchString(phone)
}
func IsValidEmail(email string) bool {
re := regexp.MustCompile(`^[a-z0-9]+@[a-z0-9]+\.[a-z]{2,}$`)
return re.MatchString(email)
}
func IsValidPassword(password string) bool {
if len(password) < 6 {
return false
}
hasUpper := false
hasDigit := false
hasSpecial := false
for _, char := range password {
if char >= 'A' && char <= 'Z' {
hasUpper = true
} else if char >= '0' && char <= '9' {
hasDigit = true
} else if isSpecialCharacter(char) {
hasSpecial = true
}
}
return hasUpper && hasDigit && hasSpecial
}
func isSpecialCharacter(char rune) bool {
specialChars := "!@#$%^&*()-_=+[]{}|;:'\",.<>?/`~"
return strings.ContainsRune(specialChars, char)
}

View File

@ -0,0 +1 @@
package dto

View File

@ -0,0 +1,133 @@
package dto
import (
"regexp"
"rijig/utils"
)
type LoginPengelolaRequest struct {
Phone string `json:"phone"`
}
func (r *LoginPengelolaRequest) ValidateLogin() (map[string][]string, bool) {
errors := make(map[string][]string)
if r.Phone == "" {
errors["phone"] = append(errors["phone"], "Phone number is required")
} else if !utils.IsValidPhoneNumber(r.Phone) {
errors["phone"] = append(errors["phone"], "Phone number is not valid")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type VerifLoginPengelolaRequest struct {
Phone string `json:"phone"`
Otp string `json:"verif_otp"`
}
func (r *VerifLoginPengelolaRequest) ValidateVerifLogin() (map[string][]string, bool) {
errors := make(map[string][]string)
if r.Phone == "" {
errors["phone"] = append(errors["phone"], "Phone number is required")
} else if !utils.IsValidPhoneNumber(r.Phone) {
errors["phone"] = append(errors["phone"], "Phone number is not valid")
}
if r.Otp == "" {
errors["otp"] = append(errors["otp"], "OTP is required")
} else if len(r.Otp) != 6 {
errors["otp"] = append(errors["otp"], "OTP must be 6 digits")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type LoginPengelolaResponse struct {
UserID string `json:"user_id"`
Role string `json:"role"`
Token string `json:"token"`
}
type PengelolaIdentityCard struct {
Cardphoto string `json:"cardphoto"`
Identificationumber string `json:"identificationumber"`
Placeofbirth string `json:"placeofbirth"`
Dateofbirth string `json:"dateofbirth"`
Gender string `json:"gender"`
BloodType string `json:"bloodtype"`
District string `json:"district"`
Village string `json:"village"`
Neighbourhood string `json:"neighbourhood"`
Religion string `json:"religion"`
Maritalstatus string `json:"maritalstatus"`
Job string `json:"job"`
Citizenship string `json:"citizenship"`
Validuntil string `json:"validuntil"`
}
func (r *PengelolaIdentityCard) ValidateIDcard() (map[string][]string, bool) {
errors := make(map[string][]string)
if r.Cardphoto == "" {
errors["cardphoto"] = append(errors["cardphoto"], "Card photo is required")
}
if r.Identificationumber == "" {
errors["identificationumber"] = append(errors["identificationumber"], "Identification number is required")
}
if r.Dateofbirth == "" {
errors["dateofbirth"] = append(errors["dateofbirth"], "Date of birth is required")
} else if !isValidDate(r.Dateofbirth) {
errors["dateofbirth"] = append(errors["dateofbirth"], "Date of birth must be in DD-MM-YYYY format")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type PengelolaCompanyProfile struct {
CompanyName string `json:"company_name"`
CompanyPhone string `json:"company_phone"`
CompanyEmail string `json:"company_email"`
}
func (r *PengelolaCompanyProfile) ValidateCompany() (map[string][]string, bool) {
errors := make(map[string][]string)
if r.CompanyName == "" {
errors["company_name"] = append(errors["company_name"], "Company name is required")
}
if r.CompanyPhone == "" {
errors["company_phone"] = append(errors["company_phone"], "Company phone is required")
} else if !utils.IsValidPhoneNumber(r.CompanyPhone) {
errors["company_phone"] = append(errors["company_phone"], "Invalid phone number format")
}
if r.CompanyEmail == "" {
errors["company_email"] = append(errors["company_email"], "Company email is required")
} else if !utils.IsValidEmail(r.CompanyEmail) {
errors["company_email"] = append(errors["company_email"], "Invalid email format")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func isValidDate(date string) bool {
re := regexp.MustCompile(`^\d{2}-\d{2}-\d{4}$`)
return re.MatchString(date)
}

View File

@ -0,0 +1 @@
package dto

View File

@ -6,14 +6,13 @@ import (
) )
type RegisterRequest struct { type RegisterRequest struct {
RoleID string `json:"role_id"` Phone string `json:"phone"`
Phone string `json:"phone"`
} }
type VerifyOTPRequest struct { type VerifyOTPRequest struct {
RoleID string `json:"role_id"` Phone string `json:"phone"`
Phone string `json:"phone"`
OTP string `json:"otp"` OTP string `json:"otp"`
DeviceID string `json:"device_id"`
} }
type UserDataResponse struct { type UserDataResponse struct {
@ -25,10 +24,6 @@ type UserDataResponse struct {
func (r *RegisterRequest) Validate() (map[string][]string, bool) { func (r *RegisterRequest) Validate() (map[string][]string, bool) {
errors := make(map[string][]string) errors := make(map[string][]string)
if strings.TrimSpace(r.RoleID) == "" {
errors["role_id"] = append(errors["role_id"], "Role ID is required")
}
if strings.TrimSpace(r.Phone) == "" { if strings.TrimSpace(r.Phone) == "" {
errors["phone"] = append(errors["phone"], "Phone is required") errors["phone"] = append(errors["phone"], "Phone is required")
} else if !IsValidPhoneNumber(r.Phone) { } else if !IsValidPhoneNumber(r.Phone) {

View File

@ -37,12 +37,6 @@ func (r *UpdateUserDTO) Validate() (map[string][]string, bool) {
errors["phone"] = append(errors["phone"], "Invalid phone number format. Use +62 followed by 9-13 digits") errors["phone"] = append(errors["phone"], "Invalid phone number format. Use +62 followed by 9-13 digits")
} }
// if strings.TrimSpace(r.Email) == "" {
// errors["email"] = append(errors["email"], "Email is required")
// } else if !IsValidEmail(r.Email) {
// errors["email"] = append(errors["email"], "Invalid email format")
// }
if len(errors) > 0 { if len(errors) > 0 {
return errors, false return errors, false
} }

View File

@ -0,0 +1,80 @@
package handler
import (
"log"
dto "rijig/dto/auth"
services "rijig/internal/services/auth"
"rijig/utils"
"github.com/gofiber/fiber/v2"
)
type AuthAdminHandler struct {
UserService services.AuthAdminService
}
func NewAuthAdminHandler(userService services.AuthAdminService) *AuthAdminHandler {
return &AuthAdminHandler{UserService: userService}
}
func (h *AuthAdminHandler) RegisterAdmin(c *fiber.Ctx) error {
var request dto.RegisterAdminRequest
if err := c.BodyParser(&request); err != nil {
return utils.InternalServerErrorResponse(c, "Failed to parse request body")
}
errors, valid := request.Validate()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
user, err := h.UserService.RegisterAdmin(&request)
if err != nil {
return utils.GenericResponse(c, fiber.StatusBadRequest, err.Error())
}
return utils.SuccessResponse(c, user, "Admin registered successfully")
}
func (h *AuthAdminHandler) LoginAdmin(c *fiber.Ctx) error {
var request dto.LoginAdminRequest
if err := c.BodyParser(&request); err != nil {
return utils.InternalServerErrorResponse(c, "Failed to parse request body")
}
loginResponse, err := h.UserService.LoginAdmin(&request)
if err != nil {
return utils.GenericResponse(c, fiber.StatusUnauthorized, err.Error())
}
return utils.SuccessResponse(c, loginResponse, "Login successful")
}
func (h *AuthAdminHandler) LogoutAdmin(c *fiber.Ctx) error {
// Ambil userID dari c.Locals
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
log.Println("Error: UserID is nil or empty")
return utils.GenericResponse(c, fiber.StatusUnauthorized, "User not authenticated")
}
// Ambil deviceID dari header atau c.Locals
deviceID, ok := c.Locals("device_id").(string)
if !ok || deviceID == "" {
log.Println("Error: DeviceID is nil or empty")
return utils.ErrorResponse(c, "DeviceID is required")
}
log.Printf("UserID: %s, DeviceID: %s", userID, deviceID)
err := h.UserService.LogoutAdmin(userID, deviceID)
if err != nil {
log.Printf("Error during logout process for user %s: %v", userID, err)
return utils.ErrorResponse(c, err.Error())
}
return utils.GenericResponse(c, fiber.StatusOK, "Successfully logged out")
}

View File

@ -0,0 +1,81 @@
package handler
import (
"log"
"rijig/dto"
services "rijig/internal/services/auth"
"rijig/utils"
"github.com/gofiber/fiber/v2"
)
type AuthMasyarakatHandler struct {
authMasyarakatService services.AuthMasyarakatService
}
func NewAuthMasyarakatHandler(authMasyarakatService services.AuthMasyarakatService) *AuthMasyarakatHandler {
return &AuthMasyarakatHandler{authMasyarakatService}
}
func (h *AuthMasyarakatHandler) RegisterOrLoginHandler(c *fiber.Ctx) error {
var req dto.RegisterRequest
if err := c.BodyParser(&req); err != nil {
return utils.ErrorResponse(c, "Invalid request body")
}
if req.Phone == "" {
return utils.ErrorResponse(c, "Phone number is required")
}
if err := h.authMasyarakatService.RegisterOrLogin(&req); err != nil {
return utils.ErrorResponse(c, err.Error())
}
return utils.SuccessResponse(c, nil, "OTP sent successfully")
}
func (h *AuthMasyarakatHandler) VerifyOTPHandler(c *fiber.Ctx) error {
var req dto.VerifyOTPRequest
if err := c.BodyParser(&req); err != nil {
return utils.ErrorResponse(c, "Invalid request body")
}
if req.OTP == "" {
return utils.ErrorResponse(c, "OTP is required")
}
if req.DeviceID == "" {
return utils.ErrorResponse(c, "DeviceID is required")
}
response, err := h.authMasyarakatService.VerifyOTP(&req)
if err != nil {
return utils.ErrorResponse(c, err.Error())
}
return utils.SuccessResponse(c, response, "Registration/Login successful")
}
func (h *AuthMasyarakatHandler) LogoutHandler(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return utils.ErrorResponse(c, "User is not logged in or invalid session")
}
deviceID, ok := c.Locals("device_id").(string)
if !ok || deviceID == "" {
log.Println("Error: DeviceID is nil or empty")
return utils.ErrorResponse(c, "DeviceID is required")
}
err := h.authMasyarakatService.Logout(userID, deviceID)
if err != nil {
log.Printf("Error during logout process for user %s: %v", userID, err)
return utils.ErrorResponse(c, err.Error())
}
return utils.SuccessResponse(c, nil, "Logged out successfully")
}

View File

@ -0,0 +1,81 @@
package handler
import (
"log"
"rijig/dto"
services "rijig/internal/services/auth"
"rijig/utils"
"github.com/gofiber/fiber/v2"
)
type AuthPengepulHandler struct {
authPengepulService services.AuthMasyarakatService
}
func NewAuthPengepulHandler(authPengepulService services.AuthMasyarakatService) *AuthPengepulHandler {
return &AuthPengepulHandler{authPengepulService}
}
func (h *AuthPengepulHandler) RegisterOrLoginHandler(c *fiber.Ctx) error {
var req dto.RegisterRequest
if err := c.BodyParser(&req); err != nil {
return utils.ErrorResponse(c, "Invalid request body")
}
if req.Phone == "" {
return utils.ErrorResponse(c, "Phone number is required")
}
if err := h.authPengepulService.RegisterOrLogin(&req); err != nil {
return utils.ErrorResponse(c, err.Error())
}
return utils.SuccessResponse(c, nil, "OTP sent successfully")
}
func (h *AuthPengepulHandler) VerifyOTPHandler(c *fiber.Ctx) error {
var req dto.VerifyOTPRequest
if err := c.BodyParser(&req); err != nil {
return utils.ErrorResponse(c, "Invalid request body")
}
if req.OTP == "" {
return utils.ErrorResponse(c, "OTP is required")
}
if req.DeviceID == "" {
return utils.ErrorResponse(c, "DeviceID is required")
}
response, err := h.authPengepulService.VerifyOTP(&req)
if err != nil {
return utils.ErrorResponse(c, err.Error())
}
return utils.SuccessResponse(c, response, "Registration/Login successful")
}
func (h *AuthPengepulHandler) LogoutHandler(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return utils.ErrorResponse(c, "User is not logged in or invalid session")
}
deviceID, ok := c.Locals("device_id").(string)
if !ok || deviceID == "" {
log.Println("Error: DeviceID is nil or empty")
return utils.ErrorResponse(c, "DeviceID is required")
}
err := h.authPengepulService.Logout(userID, deviceID)
if err != nil {
log.Printf("Error during logout process for user %s: %v", userID, err)
return utils.ErrorResponse(c, err.Error())
}
return utils.SuccessResponse(c, nil, "Logged out successfully")
}

View File

@ -0,0 +1,81 @@
package handler
import (
"log"
"rijig/dto"
services "rijig/internal/services/auth"
"rijig/utils"
"github.com/gofiber/fiber/v2"
)
type AuthPengelolaHandler struct {
authPengelolaService services.AuthMasyarakatService
}
func NewAuthPengelolaHandler(authPengelolaService services.AuthMasyarakatService) *AuthPengelolaHandler {
return &AuthPengelolaHandler{authPengelolaService}
}
func (h *AuthPengelolaHandler) RegisterOrLoginHandler(c *fiber.Ctx) error {
var req dto.RegisterRequest
if err := c.BodyParser(&req); err != nil {
return utils.ErrorResponse(c, "Invalid request body")
}
if req.Phone == "" {
return utils.ErrorResponse(c, "Phone number is required")
}
if err := h.authPengelolaService.RegisterOrLogin(&req); err != nil {
return utils.ErrorResponse(c, err.Error())
}
return utils.SuccessResponse(c, nil, "OTP sent successfully")
}
func (h *AuthPengelolaHandler) VerifyOTPHandler(c *fiber.Ctx) error {
var req dto.VerifyOTPRequest
if err := c.BodyParser(&req); err != nil {
return utils.ErrorResponse(c, "Invalid request body")
}
if req.OTP == "" {
return utils.ErrorResponse(c, "OTP is required")
}
if req.DeviceID == "" {
return utils.ErrorResponse(c, "DeviceID is required")
}
response, err := h.authPengelolaService.VerifyOTP(&req)
if err != nil {
return utils.ErrorResponse(c, err.Error())
}
return utils.SuccessResponse(c, response, "Registration/Login successful")
}
func (h *AuthPengelolaHandler) LogoutHandler(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string)
if !ok || userID == "" {
return utils.ErrorResponse(c, "User is not logged in or invalid session")
}
deviceID, ok := c.Locals("device_id").(string)
if !ok || deviceID == "" {
log.Println("Error: DeviceID is nil or empty")
return utils.ErrorResponse(c, "DeviceID is required")
}
err := h.authPengelolaService.Logout(userID, deviceID)
if err != nil {
log.Printf("Error during logout process for user %s: %v", userID, err)
return utils.ErrorResponse(c, err.Error())
}
return utils.SuccessResponse(c, nil, "Logged out successfully")
}

View File

@ -1,80 +1,80 @@
package handler package handler
import ( // import (
"log" // "log"
"rijig/dto" // "rijig/dto"
"rijig/internal/services" // "rijig/internal/services"
"rijig/utils" // "rijig/utils"
"github.com/gofiber/fiber/v2" // "github.com/gofiber/fiber/v2"
) // )
type AuthHandler struct { // type AuthHandler struct {
authService services.AuthService // authService services.AuthService
} // }
func NewAuthHandler(authService services.AuthService) *AuthHandler { // func NewAuthHandler(authService services.AuthService) *AuthHandler {
return &AuthHandler{authService} // return &AuthHandler{authService}
} // }
func (h *AuthHandler) RegisterOrLoginHandler(c *fiber.Ctx) error { // func (h *AuthHandler) RegisterOrLoginHandler(c *fiber.Ctx) error {
var req dto.RegisterRequest // var req dto.RegisterRequest
if err := c.BodyParser(&req); err != nil { // if err := c.BodyParser(&req); err != nil {
return utils.ErrorResponse(c, "Invalid request body") // return utils.ErrorResponse(c, "Invalid request body")
} // }
if req.Phone == "" || req.RoleID == "" { // if req.Phone == "" || req.RoleID == "" {
return utils.ErrorResponse(c, "Phone number and role ID are required") // return utils.ErrorResponse(c, "Phone number and role ID are required")
} // }
if err := h.authService.RegisterOrLogin(&req); err != nil { // if err := h.authService.RegisterOrLogin(&req); err != nil {
return utils.ErrorResponse(c, err.Error()) // return utils.ErrorResponse(c, err.Error())
} // }
return utils.SuccessResponse(c, nil, "OTP sent successfully") // return utils.SuccessResponse(c, nil, "OTP sent successfully")
} // }
func (h *AuthHandler) VerifyOTPHandler(c *fiber.Ctx) error { // func (h *AuthHandler) VerifyOTPHandler(c *fiber.Ctx) error {
var req dto.VerifyOTPRequest // var req dto.VerifyOTPRequest
if err := c.BodyParser(&req); err != nil { // if err := c.BodyParser(&req); err != nil {
return utils.ErrorResponse(c, "Invalid request body") // return utils.ErrorResponse(c, "Invalid request body")
} // }
if req.OTP == "" { // if req.OTP == "" {
return utils.ErrorResponse(c, "OTP is required") // return utils.ErrorResponse(c, "OTP is required")
} // }
response, err := h.authService.VerifyOTP(&req) // response, err := h.authService.VerifyOTP(&req)
if err != nil { // if err != nil {
return utils.ErrorResponse(c, err.Error()) // return utils.ErrorResponse(c, err.Error())
} // }
return utils.SuccessResponse(c, response, "Registration/Login successful") // return utils.SuccessResponse(c, response, "Registration/Login successful")
} // }
func (h *AuthHandler) LogoutHandler(c *fiber.Ctx) error { // func (h *AuthHandler) LogoutHandler(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(string) // userID, ok := c.Locals("userID").(string)
if !ok || userID == "" { // if !ok || userID == "" {
return utils.ErrorResponse(c, "User is not logged in or invalid session") // return utils.ErrorResponse(c, "User is not logged in or invalid session")
} // }
phoneKey := "user_phone:" + userID // phoneKey := "user_phone:" + userID
phone, err := utils.GetStringData(phoneKey) // phone, err := utils.GetStringData(phoneKey)
if err != nil || phone == "" { // if err != nil || phone == "" {
log.Printf("Error retrieving phone from Redis for user %s: %v", userID, err) // log.Printf("Error retrieving phone from Redis for user %s: %v", userID, err)
return utils.ErrorResponse(c, "Phone number is missing or invalid session data") // return utils.ErrorResponse(c, "Phone number is missing or invalid session data")
} // }
err = h.authService.Logout(userID, phone) // err = h.authService.Logout(userID, phone)
if err != nil { // if err != nil {
log.Printf("Error during logout process for user %s: %v", userID, err) // log.Printf("Error during logout process for user %s: %v", userID, err)
return utils.ErrorResponse(c, err.Error()) // return utils.ErrorResponse(c, err.Error())
} // }
return utils.SuccessResponse(c, nil, "Logged out successfully") // return utils.SuccessResponse(c, nil, "Logged out successfully")
} // }

View File

@ -0,0 +1,93 @@
package repositories
import (
"rijig/model"
"gorm.io/gorm"
)
type AuthAdminRepository interface {
FindByEmail(email string) (*model.User, error)
FindAdminByEmailandRoleid(email, roleId string) (*model.User, error)
FindAdminByPhoneandRoleid(phone, roleId string) (*model.User, error)
FindByPhone(phone string) (*model.User, error)
FindByEmailOrPhone(identifier string) (*model.User, error)
FindRoleByName(roleName string) (*model.Role, error)
CreateUser(user *model.User) (*model.User, error)
}
type authAdminRepository struct {
DB *gorm.DB
}
func NewAuthAdminRepository(db *gorm.DB) AuthAdminRepository {
return &authAdminRepository{DB: db}
}
func (r *authAdminRepository) FindByEmail(email string) (*model.User, error) {
var user model.User
err := r.DB.Preload("Role").Where("email = ?", email).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *authAdminRepository) FindAdminByEmailandRoleid(email, roleId string) (*model.User, error) {
var user model.User
err := r.DB.Where("email = ? AND role_id = ?", email, roleId).First(&user).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &user, nil
}
func (r *authAdminRepository) FindAdminByPhoneandRoleid(phone, roleId string) (*model.User, error) {
var user model.User
err := r.DB.Where("phone = ? AND role_id = ?", phone, roleId).First(&user).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &user, nil
}
func (r *authAdminRepository) FindByPhone(phone string) (*model.User, error) {
var user model.User
err := r.DB.Where("phone = ?", phone).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *authAdminRepository) FindByEmailOrPhone(identifier string) (*model.User, error) {
var user model.User
err := r.DB.Where("email = ? OR phone = ?", identifier, identifier).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *authAdminRepository) CreateUser(user *model.User) (*model.User, error) {
err := r.DB.Create(user).Error
if err != nil {
return nil, err
}
return user, nil
}
func (r *authAdminRepository) FindRoleByName(roleName string) (*model.Role, error) {
var role model.Role
err := r.DB.Where("role_name = ?", roleName).First(&role).Error
if err != nil {
return nil, err
}
return &role, nil
}

View File

@ -0,0 +1,48 @@
package repositories
import (
"rijig/model"
"gorm.io/gorm"
)
type AuthMasyarakatRepository interface {
CreateUser(user *model.User) (*model.User, error)
GetUserByPhone(phone string) (*model.User, error)
GetUserByPhoneAndRole(phone string, roleId string) (*model.User, error)
}
type authMasyarakatRepository struct {
db *gorm.DB
}
func NewAuthMasyarakatRepositories(db *gorm.DB) AuthMasyarakatRepository {
return &authMasyarakatRepository{db}
}
func (r *authMasyarakatRepository) CreateUser(user *model.User) (*model.User, error) {
if err := r.db.Create(user).Error; err != nil {
return nil, err
}
return user, nil
}
func (r *authMasyarakatRepository) GetUserByPhone(phone string) (*model.User, error) {
var user model.User
if err := r.db.Where("phone = ?", phone).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
}
func (r *authMasyarakatRepository) GetUserByPhoneAndRole(phone string, roleId string) (*model.User, error) {
var user model.User
err := r.db.Where("phone = ? AND role_id = ?", phone, roleId).First(&user).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &user, nil
}

View File

@ -0,0 +1,48 @@
package repositories
import (
"rijig/model"
"gorm.io/gorm"
)
type AuthPengelolaRepository interface {
CreateUser(user *model.User) (*model.User, error)
GetUserByPhone(phone string) (*model.User, error)
GetUserByPhoneAndRole(phone string, roleId string) (*model.User, error)
}
type authPengelolaRepository struct {
db *gorm.DB
}
func NewAuthPengelolaRepositories(db *gorm.DB) AuthPengelolaRepository {
return &authPengelolaRepository{db}
}
func (r *authPengelolaRepository) CreateUser(user *model.User) (*model.User, error) {
if err := r.db.Create(user).Error; err != nil {
return nil, err
}
return user, nil
}
func (r *authPengelolaRepository) GetUserByPhone(phone string) (*model.User, error) {
var user model.User
if err := r.db.Where("phone = ?", phone).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
}
func (r *authPengelolaRepository) GetUserByPhoneAndRole(phone string, roleId string) (*model.User, error) {
var user model.User
err := r.db.Where("phone = ? AND role_id = ?", phone, roleId).First(&user).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &user, nil
}

View File

@ -0,0 +1,48 @@
package repositories
import (
"rijig/model"
"gorm.io/gorm"
)
type AuthPengepulRepository interface {
CreateUser(user *model.User) (*model.User, error)
GetUserByPhone(phone string) (*model.User, error)
GetUserByPhoneAndRole(phone string, roleId string) (*model.User, error)
}
type authPengepulRepository struct {
db *gorm.DB
}
func NewAuthPengepulRepositories(db *gorm.DB) AuthPengepulRepository {
return &authPengepulRepository{db}
}
func (r *authPengepulRepository) CreateUser(user *model.User) (*model.User, error) {
if err := r.db.Create(user).Error; err != nil {
return nil, err
}
return user, nil
}
func (r *authPengepulRepository) GetUserByPhone(phone string) (*model.User, error) {
var user model.User
if err := r.db.Where("phone = ?", phone).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
}
func (r *authPengepulRepository) GetUserByPhoneAndRole(phone string, roleId string) (*model.User, error) {
var user model.User
err := r.db.Where("phone = ? AND role_id = ?", phone, roleId).First(&user).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return &user, nil
}

View File

@ -8,6 +8,7 @@ import (
type RoleRepository interface { type RoleRepository interface {
FindByID(id string) (*model.Role, error) FindByID(id string) (*model.Role, error)
FindRoleByName(roleName string) (*model.Role, error)
FindAll() ([]model.Role, error) FindAll() ([]model.Role, error)
} }
@ -36,3 +37,12 @@ func (r *roleRepository) FindAll() ([]model.Role, error) {
} }
return roles, nil return roles, nil
} }
func (r *roleRepository) FindRoleByName(roleName string) (*model.Role, error) {
var role model.Role
err := r.DB.Where("role_name = ?", roleName).First(&role).Error
if err != nil {
return nil, err
}
return &role, nil
}

View File

@ -0,0 +1,191 @@
package service
import (
"errors"
"fmt"
"log"
"rijig/config"
dto "rijig/dto/auth"
"rijig/internal/repositories"
repository "rijig/internal/repositories/auth"
"rijig/model"
"rijig/utils"
"time"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
)
const (
ErrEmailTaken = "email is already used"
ErrPhoneTaken = "phone number is already used"
ErrInvalidPassword = "password does not match"
ErrRoleNotFound = "role not found"
ErrFailedToGenerateToken = "failed to generate token"
ErrFailedToHashPassword = "failed to hash password"
ErrFailedToCreateUser = "failed to create user"
ErrIncorrectPassword = "incorrect password"
ErrAccountNotFound = "account not found"
)
type AuthAdminService interface {
RegisterAdmin(request *dto.RegisterAdminRequest) (*model.User, error)
LoginAdmin(req *dto.LoginAdminRequest) (*dto.LoginResponse, error)
LogoutAdmin(userID, deviceID string) error
}
type authAdminService struct {
UserRepo repository.AuthAdminRepository
RoleRepo repositories.RoleRepository
SecretKey string
}
func NewAuthAdminService(userRepo repository.AuthAdminRepository, roleRepo repositories.RoleRepository, secretKey string) AuthAdminService {
return &authAdminService{UserRepo: userRepo, RoleRepo: roleRepo, SecretKey: secretKey}
}
func (s *authAdminService) RegisterAdmin(request *dto.RegisterAdminRequest) (*model.User, error) {
if existingUser, _ := s.UserRepo.FindByEmail(request.Email); existingUser != nil {
return nil, errors.New(ErrEmailTaken)
}
if existingUser, _ := s.UserRepo.FindByPhone(request.Phone); existingUser != nil {
return nil, errors.New(ErrPhoneTaken)
}
role, err := s.UserRepo.FindRoleByName("administrator")
if err != nil {
return nil, errors.New(ErrRoleNotFound)
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(request.Password), bcrypt.DefaultCost)
if err != nil {
log.Println("Error hashing password:", err)
return nil, errors.New(ErrFailedToHashPassword)
}
user := &model.User{
Name: request.Name,
Email: request.Email,
Phone: request.Phone,
Password: string(hashedPassword),
RoleID: role.ID,
Role: role,
Dateofbirth: request.Dateofbirth,
Placeofbirth: request.Placeofbirth,
Gender: request.Gender,
RegistrationStatus: "completed",
}
createdUser, err := s.UserRepo.CreateUser(user)
if err != nil {
log.Println("Error creating user:", err)
return nil, fmt.Errorf("%s: %v", ErrFailedToCreateUser, err)
}
return createdUser, nil
}
func (s *authAdminService) LoginAdmin(req *dto.LoginAdminRequest) (*dto.LoginResponse, error) {
user, err := s.UserRepo.FindByEmail(req.Email)
if err != nil {
log.Println("User not found:", err)
return nil, errors.New(ErrAccountNotFound)
}
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
log.Println("Incorrect password:", err)
return nil, errors.New(ErrIncorrectPassword)
}
existingUser, err := s.UserRepo.FindAdminByEmailandRoleid(req.Email, "42bdecce-f2ad-44ae-b3d6-883c1fbddaf7")
if err != nil {
return nil, fmt.Errorf("failed to check existing user: %w", err)
}
var adminUser *model.User
if existingUser != nil {
adminUser = existingUser
} else {
adminUser = &model.User{
Email: req.Email,
RoleID: "42bdecce-f2ad-44ae-b3d6-883c1fbddaf7",
}
createdUser, err := s.UserRepo.CreateUser(adminUser)
if err != nil {
return nil, err
}
adminUser = createdUser
}
token, err := s.generateJWTToken(adminUser.ID, req.Deviceid)
if err != nil {
return nil, err
}
role, err := s.RoleRepo.FindByID(user.RoleID)
if err != nil {
return nil, fmt.Errorf("failed to get role: %w", err)
}
deviceID := req.Deviceid
if err := s.saveSessionAdminData(user.ID, deviceID, user.RoleID, role.RoleName, token); err != nil {
return nil, err
}
return &dto.LoginResponse{
UserID: user.ID,
Role: user.Role.RoleName,
Token: token,
}, nil
}
func (s *authAdminService) saveSessionAdminData(userID string, deviceID string, roleID string, roleName string, token string) error {
sessionKey := fmt.Sprintf("session:%s:%s", userID, deviceID)
sessionData := map[string]interface{}{
"userID": userID,
"roleID": roleID,
"roleName": roleName,
}
if err := utils.SetJSONData(sessionKey, sessionData, 24*time.Hour); err != nil {
return fmt.Errorf("failed to set session data: %w", err)
}
if err := utils.SetStringData("session_token:"+userID+":"+deviceID, token, 24*time.Hour); err != nil {
return fmt.Errorf("failed to set session token: %w", err)
}
return nil
}
func (s *authAdminService) generateJWTToken(userID string, deviceID string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := jwt.MapClaims{
"sub": userID,
"exp": expirationTime.Unix(),
"device_id": deviceID,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
secretKey := config.GetSecretKey()
return token.SignedString([]byte(secretKey))
}
func (s *authAdminService) LogoutAdmin(userID, deviceID string) error {
err := utils.DeleteSessionData(userID, deviceID)
if err != nil {
return fmt.Errorf("failed to delete session from Redis: %w", err)
}
return nil
}

View File

@ -0,0 +1,170 @@
package service
import (
"errors"
"fmt"
"rijig/config"
"rijig/dto"
"rijig/internal/repositories"
repository "rijig/internal/repositories/auth"
"rijig/model"
"rijig/utils"
"time"
"github.com/golang-jwt/jwt/v5"
)
type AuthMasyarakatService interface {
RegisterOrLogin(req *dto.RegisterRequest) error
VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error)
Logout(userID, deviceID string) error
}
type authMasyarakatService struct {
userRepo repository.AuthPengelolaRepository
roleRepo repositories.RoleRepository
}
func NewAuthMasyarakatService(userRepo repositories.UserRepository, roleRepo repositories.RoleRepository) AuthMasyarakatService {
return &authMasyarakatService{userRepo, roleRepo}
}
func (s *authMasyarakatService) generateJWTToken(userID string, deviceID string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := jwt.MapClaims{
"sub": userID,
"exp": expirationTime.Unix(),
"device_id": deviceID,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
secretKey := config.GetSecretKey()
return token.SignedString([]byte(secretKey))
}
func (s *authMasyarakatService) RegisterOrLogin(req *dto.RegisterRequest) error {
if err := s.checkOTPRequestCooldown(req.Phone); err != nil {
return err
}
return s.sendOTP(req.Phone)
}
func (s *authMasyarakatService) checkOTPRequestCooldown(phone string) error {
otpSentTime, err := utils.GetStringData("otp_sent:" + phone)
if err != nil || otpSentTime == "" {
return nil
}
lastSent, _ := time.Parse(time.RFC3339, otpSentTime)
if time.Since(lastSent) < otpCooldown {
return errors.New("please wait before requesting a new OTP")
}
return nil
}
func (s *authMasyarakatService) sendOTP(phone string) error {
otp := generateOTP()
if err := config.SendWhatsAppMessage(phone, fmt.Sprintf("Your OTP is: %s", otp)); err != nil {
return err
}
if err := utils.SetStringData("otp:"+phone, otp, 10*time.Minute); err != nil {
return err
}
return utils.SetStringData("otp_sent:"+phone, time.Now().Format(time.RFC3339), 10*time.Minute)
}
func (s *authMasyarakatService) VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error) {
storedOTP, err := utils.GetStringData("otp:" + req.Phone)
if err != nil || storedOTP == "" {
return nil, errors.New("OTP expired or not found")
}
if storedOTP != req.OTP {
return nil, errors.New("invalid OTP")
}
if err := utils.DeleteData("otp:" + req.Phone); err != nil {
return nil, fmt.Errorf("failed to remove OTP from Redis: %w", err)
}
if err := utils.DeleteData("otp_sent:" + req.Phone); err != nil {
return nil, fmt.Errorf("failed to remove otp_sent from Redis: %w", err)
}
existingUser, err := s.userRepo.GetUserByPhoneAndRole(req.Phone, "0e5684e4-b214-4bd0-972f-3be80c4649a0")
if err != nil {
return nil, fmt.Errorf("failed to check existing user: %w", err)
}
var user *model.User
if existingUser != nil {
user = existingUser
} else {
user = &model.User{
Phone: req.Phone,
RoleID: "0e5684e4-b214-4bd0-972f-3be80c4649a0",
PhoneVerified: true,
RegistrationStatus: "completed",
}
createdUser, err := s.userRepo.CreateUser(user)
if err != nil {
return nil, err
}
user = createdUser
}
token, err := s.generateJWTToken(user.ID, req.DeviceID)
if err != nil {
return nil, err
}
role, err := s.roleRepo.FindByID(user.RoleID)
if err != nil {
return nil, fmt.Errorf("failed to get role: %w", err)
}
deviceID := req.DeviceID
if err := s.saveSessionData(user.ID, deviceID, user.RoleID, role.RoleName, token); err != nil {
return nil, err
}
return &dto.UserDataResponse{
UserID: user.ID,
UserRole: role.RoleName,
Token: token,
}, nil
}
func (s *authMasyarakatService) saveSessionData(userID string, deviceID string, roleID string, roleName string, token string) error {
sessionKey := fmt.Sprintf("session:%s:%s", userID, deviceID)
sessionData := map[string]interface{}{
"userID": userID,
"roleID": roleID,
"roleName": roleName,
}
if err := utils.SetJSONData(sessionKey, sessionData, 24*time.Hour); err != nil {
return fmt.Errorf("failed to set session data: %w", err)
}
if err := utils.SetStringData("session_token:"+userID+":"+deviceID, token, 24*time.Hour); err != nil {
return fmt.Errorf("failed to set session token: %w", err)
}
return nil
}
func (s *authMasyarakatService) Logout(userID, deviceID string) error {
err := utils.DeleteSessionData(userID, deviceID)
if err != nil {
return fmt.Errorf("failed to delete session from Redis: %w", err)
}
return nil
}

View File

@ -0,0 +1,170 @@
package service
import (
"errors"
"fmt"
"rijig/config"
"rijig/dto"
"rijig/internal/repositories"
repository "rijig/internal/repositories/auth"
"rijig/model"
"rijig/utils"
"time"
"github.com/golang-jwt/jwt/v5"
)
type AuthPengelolaService interface {
RegisterOrLogin(req *dto.RegisterRequest) error
VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error)
Logout(userID, deviceID string) error
}
type authPengelolaService struct {
userRepo repository.AuthPengelolaRepository
roleRepo repositories.RoleRepository
}
func NewAuthPengelolaService(userRepo repositories.UserRepository, roleRepo repositories.RoleRepository) AuthPengelolaService {
return &authPengelolaService{userRepo, roleRepo}
}
func (s *authPengelolaService) generateJWTToken(userID string, deviceID string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := jwt.MapClaims{
"sub": userID,
"exp": expirationTime.Unix(),
"device_id": deviceID,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
secretKey := config.GetSecretKey()
return token.SignedString([]byte(secretKey))
}
func (s *authPengelolaService) RegisterOrLogin(req *dto.RegisterRequest) error {
if err := s.checkOTPRequestCooldown(req.Phone); err != nil {
return err
}
return s.sendOTP(req.Phone)
}
func (s *authPengelolaService) checkOTPRequestCooldown(phone string) error {
otpSentTime, err := utils.GetStringData("otp_sent:" + phone)
if err != nil || otpSentTime == "" {
return nil
}
lastSent, _ := time.Parse(time.RFC3339, otpSentTime)
if time.Since(lastSent) < otpCooldown {
return errors.New("please wait before requesting a new OTP")
}
return nil
}
func (s *authPengelolaService) sendOTP(phone string) error {
otp := generateOTP()
if err := config.SendWhatsAppMessage(phone, fmt.Sprintf("Your OTP is: %s", otp)); err != nil {
return err
}
if err := utils.SetStringData("otp:"+phone, otp, 10*time.Minute); err != nil {
return err
}
return utils.SetStringData("otp_sent:"+phone, time.Now().Format(time.RFC3339), 10*time.Minute)
}
func (s *authPengelolaService) VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error) {
storedOTP, err := utils.GetStringData("otp:" + req.Phone)
if err != nil || storedOTP == "" {
return nil, errors.New("OTP expired or not found")
}
if storedOTP != req.OTP {
return nil, errors.New("invalid OTP")
}
if err := utils.DeleteData("otp:" + req.Phone); err != nil {
return nil, fmt.Errorf("failed to remove OTP from Redis: %w", err)
}
if err := utils.DeleteData("otp_sent:" + req.Phone); err != nil {
return nil, fmt.Errorf("failed to remove otp_sent from Redis: %w", err)
}
existingUser, err := s.userRepo.GetUserByPhoneAndRole(req.Phone, "0bf86966-7042-410a-a88c-d01f70832348")
if err != nil {
return nil, fmt.Errorf("failed to check existing user: %w", err)
}
var user *model.User
if existingUser != nil {
user = existingUser
} else {
user = &model.User{
Phone: req.Phone,
RoleID: "0bf86966-7042-410a-a88c-d01f70832348",
PhoneVerified: true,
RegistrationStatus: "uncompleted",
}
createdUser, err := s.userRepo.CreateUser(user)
if err != nil {
return nil, err
}
user = createdUser
}
token, err := s.generateJWTToken(user.ID, req.DeviceID)
if err != nil {
return nil, err
}
role, err := s.roleRepo.FindByID(user.RoleID)
if err != nil {
return nil, fmt.Errorf("failed to get role: %w", err)
}
deviceID := req.DeviceID
if err := s.saveSessionData(user.ID, deviceID, user.RoleID, role.RoleName, token); err != nil {
return nil, err
}
return &dto.UserDataResponse{
UserID: user.ID,
UserRole: role.RoleName,
Token: token,
}, nil
}
func (s *authPengelolaService) saveSessionData(userID string, deviceID string, roleID string, roleName string, token string) error {
sessionKey := fmt.Sprintf("session:%s:%s", userID, deviceID)
sessionData := map[string]interface{}{
"userID": userID,
"roleID": roleID,
"roleName": roleName,
}
if err := utils.SetJSONData(sessionKey, sessionData, 24*time.Hour); err != nil {
return fmt.Errorf("failed to set session data: %w", err)
}
if err := utils.SetStringData("session_token:"+userID+":"+deviceID, token, 24*time.Hour); err != nil {
return fmt.Errorf("failed to set session token: %w", err)
}
return nil
}
func (s *authPengelolaService) Logout(userID, deviceID string) error {
err := utils.DeleteSessionData(userID, deviceID)
if err != nil {
return fmt.Errorf("failed to delete session from Redis: %w", err)
}
return nil
}

View File

@ -0,0 +1,170 @@
package service
import (
"errors"
"fmt"
"rijig/config"
"rijig/dto"
"rijig/internal/repositories"
repository "rijig/internal/repositories/auth"
"rijig/model"
"rijig/utils"
"time"
"github.com/golang-jwt/jwt/v5"
)
type AuthPengepulService interface {
RegisterOrLogin(req *dto.RegisterRequest) error
VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error)
Logout(userID, deviceID string) error
}
type authPengepulService struct {
userRepo repository.AuthPengelolaRepository
roleRepo repositories.RoleRepository
}
func NewAuthPengepulService(userRepo repositories.UserRepository, roleRepo repositories.RoleRepository) AuthPengepulService {
return &authPengepulService{userRepo, roleRepo}
}
func (s *authPengepulService) generateJWTToken(userID string, deviceID string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := jwt.MapClaims{
"sub": userID,
"exp": expirationTime.Unix(),
"device_id": deviceID,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
secretKey := config.GetSecretKey()
return token.SignedString([]byte(secretKey))
}
func (s *authPengepulService) RegisterOrLogin(req *dto.RegisterRequest) error {
if err := s.checkOTPRequestCooldown(req.Phone); err != nil {
return err
}
return s.sendOTP(req.Phone)
}
func (s *authPengepulService) checkOTPRequestCooldown(phone string) error {
otpSentTime, err := utils.GetStringData("otp_sent:" + phone)
if err != nil || otpSentTime == "" {
return nil
}
lastSent, _ := time.Parse(time.RFC3339, otpSentTime)
if time.Since(lastSent) < otpCooldown {
return errors.New("please wait before requesting a new OTP")
}
return nil
}
func (s *authPengepulService) sendOTP(phone string) error {
otp := generateOTP()
if err := config.SendWhatsAppMessage(phone, fmt.Sprintf("Your OTP is: %s", otp)); err != nil {
return err
}
if err := utils.SetStringData("otp:"+phone, otp, 10*time.Minute); err != nil {
return err
}
return utils.SetStringData("otp_sent:"+phone, time.Now().Format(time.RFC3339), 10*time.Minute)
}
func (s *authPengepulService) VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error) {
storedOTP, err := utils.GetStringData("otp:" + req.Phone)
if err != nil || storedOTP == "" {
return nil, errors.New("OTP expired or not found")
}
if storedOTP != req.OTP {
return nil, errors.New("invalid OTP")
}
if err := utils.DeleteData("otp:" + req.Phone); err != nil {
return nil, fmt.Errorf("failed to remove OTP from Redis: %w", err)
}
if err := utils.DeleteData("otp_sent:" + req.Phone); err != nil {
return nil, fmt.Errorf("failed to remove otp_sent from Redis: %w", err)
}
existingUser, err := s.userRepo.GetUserByPhoneAndRole(req.Phone, "d7245535-0e9e-4d35-ab39-baece5c10b3c")
if err != nil {
return nil, fmt.Errorf("failed to check existing user: %w", err)
}
var user *model.User
if existingUser != nil {
user = existingUser
} else {
user = &model.User{
Phone: req.Phone,
RoleID: "d7245535-0e9e-4d35-ab39-baece5c10b3c",
PhoneVerified: true,
RegistrationStatus: "uncompleted",
}
createdUser, err := s.userRepo.CreateUser(user)
if err != nil {
return nil, err
}
user = createdUser
}
token, err := s.generateJWTToken(user.ID, req.DeviceID)
if err != nil {
return nil, err
}
role, err := s.roleRepo.FindByID(user.RoleID)
if err != nil {
return nil, fmt.Errorf("failed to get role: %w", err)
}
deviceID := req.DeviceID
if err := s.saveSessionData(user.ID, deviceID, user.RoleID, role.RoleName, token); err != nil {
return nil, err
}
return &dto.UserDataResponse{
UserID: user.ID,
UserRole: role.RoleName,
Token: token,
}, nil
}
func (s *authPengepulService) saveSessionData(userID string, deviceID string, roleID string, roleName string, token string) error {
sessionKey := fmt.Sprintf("session:%s:%s", userID, deviceID)
sessionData := map[string]interface{}{
"userID": userID,
"roleID": roleID,
"roleName": roleName,
}
if err := utils.SetJSONData(sessionKey, sessionData, 24*time.Hour); err != nil {
return fmt.Errorf("failed to set session data: %w", err)
}
if err := utils.SetStringData("session_token:"+userID+":"+deviceID, token, 24*time.Hour); err != nil {
return fmt.Errorf("failed to set session token: %w", err)
}
return nil
}
func (s *authPengepulService) Logout(userID, deviceID string) error {
err := utils.DeleteSessionData(userID, deviceID)
if err != nil {
return fmt.Errorf("failed to delete session from Redis: %w", err)
}
return nil
}

View File

@ -0,0 +1,14 @@
package service
import (
"fmt"
"math/rand"
"time"
)
func generateOTP() string {
randGenerator := rand.New(rand.NewSource(time.Now().UnixNano()))
return fmt.Sprintf("%04d", randGenerator.Intn(10000))
}
const otpCooldown = 50 * time.Second

View File

@ -1,213 +1,213 @@
package services package services
import ( // import (
"errors" // "errors"
"fmt" // "fmt"
"math/rand" // "math/rand"
"rijig/config" // "rijig/config"
"rijig/dto" // "rijig/dto"
"rijig/internal/repositories" // "rijig/internal/repositories"
"rijig/model" // "rijig/model"
"rijig/utils" // "rijig/utils"
"time" // "time"
"github.com/golang-jwt/jwt/v5" // "github.com/golang-jwt/jwt/v5"
) // )
const otpCooldown = 30 * time.Second // const otpCooldown = 30 * time.Second
type AuthService interface { // type AuthService interface {
RegisterOrLogin(req *dto.RegisterRequest) error // RegisterOrLogin(req *dto.RegisterRequest) error
VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error) // VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error)
Logout(userID, phone string) error // Logout(userID, phone string) error
} // }
type authService struct { // type authService struct {
userRepo repositories.UserRepository // userRepo repositories.UserRepository
roleRepo repositories.RoleRepository // roleRepo repositories.RoleRepository
} // }
func NewAuthService(userRepo repositories.UserRepository, roleRepo repositories.RoleRepository) AuthService { // func NewAuthService(userRepo repositories.UserRepository, roleRepo repositories.RoleRepository) AuthService {
return &authService{userRepo, roleRepo} // return &authService{userRepo, roleRepo}
} // }
func (s *authService) RegisterOrLogin(req *dto.RegisterRequest) error { // func (s *authService) RegisterOrLogin(req *dto.RegisterRequest) error {
if err := s.checkOTPRequestCooldown(req.Phone); err != nil { // if err := s.checkOTPRequestCooldown(req.Phone); err != nil {
return err // return err
} // }
user, err := s.userRepo.GetUserByPhoneAndRole(req.Phone, req.RoleID) // user, err := s.userRepo.GetUserByPhoneAndRole(req.Phone, req.RoleID)
if err != nil { // if err != nil {
return fmt.Errorf("failed to check existing user: %w", err) // return fmt.Errorf("failed to check existing user: %w", err)
} // }
if user != nil { // if user != nil {
return s.sendOTP(req.Phone) // return s.sendOTP(req.Phone)
} // }
user = &model.User{ // user = &model.User{
Phone: req.Phone, // Phone: req.Phone,
RoleID: req.RoleID, // RoleID: req.RoleID,
} // }
createdUser, err := s.userRepo.CreateUser(user) // createdUser, err := s.userRepo.CreateUser(user)
if err != nil { // if err != nil {
return fmt.Errorf("failed to create new user: %w", err) // return fmt.Errorf("failed to create new user: %w", err)
} // }
if err := s.saveUserToRedis(createdUser.ID, createdUser, req.Phone); err != nil { // if err := s.saveUserToRedis(createdUser.ID, createdUser, req.Phone); err != nil {
return err // return err
} // }
return s.sendOTP(req.Phone) // return s.sendOTP(req.Phone)
} // }
func (s *authService) checkOTPRequestCooldown(phone string) error { // func (s *authService) checkOTPRequestCooldown(phone string) error {
otpSentTime, err := utils.GetStringData("otp_sent:" + phone) // otpSentTime, err := utils.GetStringData("otp_sent:" + phone)
if err != nil || otpSentTime == "" { // if err != nil || otpSentTime == "" {
return nil // return nil
} // }
lastSent, _ := time.Parse(time.RFC3339, otpSentTime) // lastSent, _ := time.Parse(time.RFC3339, otpSentTime)
if time.Since(lastSent) < otpCooldown { // if time.Since(lastSent) < otpCooldown {
return errors.New("please wait before requesting a new OTP") // return errors.New("please wait before requesting a new OTP")
} // }
return nil // return nil
} // }
func (s *authService) sendOTP(phone string) error { // func (s *authService) sendOTP(phone string) error {
otp := generateOTP() // otp := generateOTP()
if err := config.SendWhatsAppMessage(phone, fmt.Sprintf("Your OTP is: %s", otp)); err != nil { // if err := config.SendWhatsAppMessage(phone, fmt.Sprintf("Your OTP is: %s", otp)); err != nil {
return err // return err
} // }
if err := utils.SetStringData("otp:"+phone, otp, 10*time.Minute); err != nil { // if err := utils.SetStringData("otp:"+phone, otp, 10*time.Minute); err != nil {
return err // return err
} // }
return utils.SetStringData("otp_sent:"+phone, time.Now().Format(time.RFC3339), 10*time.Minute) // return utils.SetStringData("otp_sent:"+phone, time.Now().Format(time.RFC3339), 10*time.Minute)
} // }
func (s *authService) VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error) { // func (s *authService) VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error) {
storedOTP, err := utils.GetStringData("otp:" + req.Phone) // storedOTP, err := utils.GetStringData("otp:" + req.Phone)
if err != nil || storedOTP == "" { // if err != nil || storedOTP == "" {
return nil, errors.New("OTP expired or not found") // return nil, errors.New("OTP expired or not found")
} // }
if storedOTP != req.OTP { // if storedOTP != req.OTP {
return nil, errors.New("invalid OTP") // return nil, errors.New("invalid OTP")
} // }
if err := utils.DeleteData("otp:" + req.Phone); err != nil { // if err := utils.DeleteData("otp:" + req.Phone); err != nil {
return nil, fmt.Errorf("failed to remove OTP from Redis: %w", err) // return nil, fmt.Errorf("failed to remove OTP from Redis: %w", err)
} // }
existingUser, err := s.userRepo.GetUserByPhoneAndRole(req.Phone, req.RoleID) // existingUser, err := s.userRepo.GetUserByPhoneAndRole(req.Phone, req.RoleID)
if err != nil { // if err != nil {
return nil, fmt.Errorf("failed to check existing user: %w", err) // return nil, fmt.Errorf("failed to check existing user: %w", err)
} // }
var user *model.User // var user *model.User
if existingUser != nil { // if existingUser != nil {
user = existingUser // user = existingUser
} else { // } else {
user = &model.User{ // user = &model.User{
Phone: req.Phone, // Phone: req.Phone,
RoleID: req.RoleID, // RoleID: req.RoleID,
} // }
createdUser, err := s.userRepo.CreateUser(user) // createdUser, err := s.userRepo.CreateUser(user)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
user = createdUser // user = createdUser
} // }
token, err := s.generateJWTToken(user.ID) // token, err := s.generateJWTToken(user.ID)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
role, err := s.roleRepo.FindByID(user.RoleID) // role, err := s.roleRepo.FindByID(user.RoleID)
if err != nil { // if err != nil {
return nil, fmt.Errorf("failed to get role: %w", err) // return nil, fmt.Errorf("failed to get role: %w", err)
} // }
if err := s.saveSessionData(user.ID, user.RoleID, role.RoleName, token); err != nil { // if err := s.saveSessionData(user.ID, user.RoleID, role.RoleName, token); err != nil {
return nil, err // return nil, err
} // }
return &dto.UserDataResponse{ // return &dto.UserDataResponse{
UserID: user.ID, // UserID: user.ID,
UserRole: role.RoleName, // UserRole: role.RoleName,
Token: token, // Token: token,
}, nil // }, nil
} // }
func (s *authService) saveUserToRedis(userID string, user *model.User, phone string) error { // func (s *authService) saveUserToRedis(userID string, user *model.User, phone string) error {
if err := utils.SetJSONData("user:"+userID, user, 10*time.Minute); err != nil { // if err := utils.SetJSONData("user:"+userID, user, 10*time.Minute); err != nil {
return fmt.Errorf("failed to store user data in Redis: %w", err) // return fmt.Errorf("failed to store user data in Redis: %w", err)
} // }
if err := utils.SetStringData("user_phone:"+userID, phone, 10*time.Minute); err != nil { // if err := utils.SetStringData("user_phone:"+userID, phone, 10*time.Minute); err != nil {
return fmt.Errorf("failed to store user phone in Redis: %w", err) // return fmt.Errorf("failed to store user phone in Redis: %w", err)
} // }
return nil // return nil
} // }
func (s *authService) generateJWTToken(userID string) (string, error) { // func (s *authService) generateJWTToken(userID string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour) // expirationTime := time.Now().Add(24 * time.Hour)
claims := &jwt.RegisteredClaims{ // claims := &jwt.RegisteredClaims{
Subject: userID, // Subject: userID,
ExpiresAt: jwt.NewNumericDate(expirationTime), // ExpiresAt: jwt.NewNumericDate(expirationTime),
} // }
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
secretKey := config.GetSecretKey() // secretKey := config.GetSecretKey()
return token.SignedString([]byte(secretKey)) // return token.SignedString([]byte(secretKey))
} // }
func (s *authService) saveSessionData(userID string, roleID string, roleName string, token string) error { // func (s *authService) saveSessionData(userID string, roleID string, roleName string, token string) error {
sessionKey := fmt.Sprintf("session:%s", userID) // sessionKey := fmt.Sprintf("session:%s", userID)
sessionData := map[string]interface{}{ // sessionData := map[string]interface{}{
"userID": userID, // "userID": userID,
"roleID": roleID, // "roleID": roleID,
"roleName": roleName, // "roleName": roleName,
} // }
if err := utils.SetJSONData(sessionKey, sessionData, 24*time.Hour); err != nil { // if err := utils.SetJSONData(sessionKey, sessionData, 24*time.Hour); err != nil {
return fmt.Errorf("failed to set session data: %w", err) // return fmt.Errorf("failed to set session data: %w", err)
} // }
if err := utils.SetStringData("session_token:"+userID, token, 24*time.Hour); err != nil { // if err := utils.SetStringData("session_token:"+userID, token, 24*time.Hour); err != nil {
return fmt.Errorf("failed to set session token: %w", err) // return fmt.Errorf("failed to set session token: %w", err)
} // }
return nil // return nil
} // }
func (s *authService) Logout(userID, phone string) error { // func (s *authService) Logout(userID, phone string) error {
keys := []string{ // keys := []string{
"session:" + userID, // "session:" + userID,
"session_token:" + userID, // "session_token:" + userID,
"user_logged_in:" + userID, // "user_logged_in:" + userID,
"user:" + userID, // "user:" + userID,
"user_phone:" + userID, // "user_phone:" + userID,
"otp_sent:" + phone, // "otp_sent:" + phone,
} // }
for _, key := range keys { // for _, key := range keys {
if err := utils.DeleteData(key); err != nil { // if err := utils.DeleteData(key); err != nil {
return fmt.Errorf("failed to delete key %s from Redis: %w", key, err) // return fmt.Errorf("failed to delete key %s from Redis: %w", key, err)
} // }
} // }
return nil // return nil
} // }
func generateOTP() string { // func generateOTP() string {
randGenerator := rand.New(rand.NewSource(time.Now().UnixNano())) // randGenerator := rand.New(rand.NewSource(time.Now().UnixNano()))
return fmt.Sprintf("%04d", randGenerator.Intn(10000)) // return fmt.Sprintf("%04d", randGenerator.Intn(10000))
} // }

View File

@ -45,12 +45,12 @@ func (s *userProfileService) prepareUserResponse(user *model.User) *dto.UserResp
return &dto.UserResponseDTO{ return &dto.UserResponseDTO{
ID: user.ID, ID: user.ID,
Username: user.Username, // Username: user.Username,
Avatar: user.Avatar, Avatar: user.Avatar,
Name: user.Name, Name: user.Name,
Phone: user.Phone, Phone: user.Phone,
Email: user.Email, Email: user.Email,
EmailVerified: user.EmailVerified, // EmailVerified: user.EmailVerified,
RoleName: user.Role.RoleName, RoleName: user.Role.RoleName,
CreatedAt: createdAt, CreatedAt: createdAt,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
@ -100,12 +100,12 @@ func (s *userProfileService) GetAllUsers() ([]dto.UserResponseDTO, error) {
for _, user := range users { for _, user := range users {
response = append(response, dto.UserResponseDTO{ response = append(response, dto.UserResponseDTO{
ID: user.ID, ID: user.ID,
Username: user.Username, // Username: user.Username,
Avatar: user.Avatar, Avatar: user.Avatar,
Name: user.Name, Name: user.Name,
Phone: user.Phone, Phone: user.Phone,
Email: user.Email, // Email: user.Email,
EmailVerified: user.EmailVerified, // EmailVerified: user.EmailVerified,
RoleName: user.Role.RoleName, RoleName: user.Role.RoleName,
CreatedAt: user.CreatedAt.Format(time.RFC3339), CreatedAt: user.CreatedAt.Format(time.RFC3339),
UpdatedAt: user.UpdatedAt.Format(time.RFC3339), UpdatedAt: user.UpdatedAt.Format(time.RFC3339),
@ -125,12 +125,12 @@ func (s *userProfileService) GetUsersByRoleID(roleID string) ([]dto.UserResponse
for _, user := range users { for _, user := range users {
response = append(response, dto.UserResponseDTO{ response = append(response, dto.UserResponseDTO{
ID: user.ID, ID: user.ID,
Username: user.Username, // Username: user.Username,
Avatar: user.Avatar, Avatar: user.Avatar,
Name: user.Name, Name: user.Name,
Phone: user.Phone, Phone: user.Phone,
Email: user.Email, // Email: user.Email,
EmailVerified: user.EmailVerified, // EmailVerified: user.EmailVerified,
RoleName: user.Role.RoleName, RoleName: user.Role.RoleName,
CreatedAt: user.CreatedAt.Format(time.RFC3339), CreatedAt: user.CreatedAt.Format(time.RFC3339),
UpdatedAt: user.UpdatedAt.Format(time.RFC3339), UpdatedAt: user.UpdatedAt.Format(time.RFC3339),

View File

@ -1,8 +1,9 @@
package middleware package middleware
import ( import (
"fmt"
"log"
"os" "os"
"rijig/utils" "rijig/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@ -22,35 +23,39 @@ func AuthMiddleware(c *fiber.Ctx) error {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("SECRET_KEY")), nil return []byte(os.Getenv("SECRET_KEY")), nil
}) })
if err != nil || !token.Valid { if err != nil || !token.Valid {
log.Printf("Error parsing token: %v", err)
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid token") return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid token")
} }
claims, ok := token.Claims.(jwt.MapClaims) claims, ok := token.Claims.(jwt.MapClaims)
if !ok || claims["sub"] == nil { if !ok || claims["sub"] == nil || claims["device_id"] == nil {
log.Println("Invalid token claims")
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid token claims") return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid token claims")
} }
userID := claims["sub"].(string) userID := claims["sub"].(string)
if userID == "" { deviceID := claims["device_id"].(string)
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid user session")
}
sessionKey := "session:" + userID sessionKey := fmt.Sprintf("session:%s:%s", userID, deviceID)
sessionData, err := utils.GetJSONData(sessionKey) sessionData, err := utils.GetJSONData(sessionKey)
if err != nil || sessionData == nil { if err != nil || sessionData == nil {
log.Printf("Session expired or invalid for userID: %s, deviceID: %s", userID, deviceID)
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Session expired or invalid") return utils.GenericResponse(c, fiber.StatusUnauthorized, "Session expired or invalid")
} }
roleID, roleOK := sessionData["roleID"].(string) roleID, roleOK := sessionData["roleID"].(string)
roleName, roleNameOK := sessionData["roleName"].(string) roleName, roleNameOK := sessionData["roleName"].(string)
if !roleOK || !roleNameOK { if !roleOK || !roleNameOK {
log.Println("Invalid session data for userID:", userID)
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid session data") return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid session data")
} }
c.Locals("userID", userID) c.Locals("userID", userID)
c.Locals("roleID", roleID) c.Locals("roleID", roleID)
c.Locals("roleName", roleName) c.Locals("roleName", roleName)
c.Locals("device_id", deviceID)
return c.Next() return c.Next()
} }

View File

@ -0,0 +1,24 @@
package model
import (
"time"
)
type CompanyProfile 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"`
CompanyName string `gorm:"not null" json:"company_name"`
CompanyAddress string `gorm:"not null" json:"company_address"`
CompanyPhone string `gorm:"not null" json:"company_phone"`
CompanyEmail string `gorm:"not null" json:"company_email"`
CompanyLogo string `gorm:"not null" json:"company_logo"`
CompanyWebsite string `json:"company_website"`
TaxID string `json:"tax_id"`
FoundedDate time.Time `json:"founded_date"`
CompanyType string `gorm:"not null" json:"company_type"`
CompanyDescription string `gorm:"type:text" json:"company_description"`
CompanyStatus string `gorm:"not null" json:"company_status"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
}

View File

@ -0,0 +1,25 @@
package model
import "time"
type IdentityCard 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"`
Identificationumber string `gorm:"not null" json:"identificationumber"`
Placeofbirth string `gorm:"not null" json:"placeofbirth"`
Dateofbirth string `gorm:"not null" json:"dateofbirth"`
Gender string `gorm:"not null" json:"gender"`
BloodType string `gorm:"not null" json:"bloodtype"`
District string `gorm:"not null" json:"district"`
Village string `gorm:"not null" json:"village"`
Neighbourhood string `gorm:"not null" json:"neighbourhood"`
Religion string `gorm:"not null" json:"religion"`
Maritalstatus string `gorm:"not null" json:"maritalstatus"`
Job string `gorm:"not null" json:"job"`
Citizenship string `gorm:"not null" json:"citizenship"`
Validuntil string `gorm:"not null" json:"validuntil"`
Cardphoto string `gorm:"not null" json:"cardphoto"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
}

View File

@ -5,7 +5,7 @@ import "time"
type Role struct { type Role struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
RoleName string `gorm:"unique;not null" json:"roleName"` RoleName string `gorm:"unique;not null" json:"roleName"`
Users []User `gorm:"foreignKey:RoleID" json:"users"` // Users []User `gorm:"foreignKey:RoleID" json:"users"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"` CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"` UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
} }

View File

@ -3,16 +3,19 @@ package model
import "time" import "time"
type User struct { type User struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
Avatar *string `json:"avatar,omitempty"` Avatar *string `json:"avatar,omitempty"`
Username string `gorm:"not null" json:"username"` Name string `gorm:"not null" json:"name"`
Name string `gorm:"not null" json:"name"` Gender string `gorm:"not null" json:"gender"`
Phone string `gorm:"not null" json:"phone"` Dateofbirth string `gorm:"not null" json:"dateofbirth"`
Email string `gorm:"not null" json:"email"` Placeofbirth string `gorm:"not null" json:"placeofbirth"`
EmailVerified bool `gorm:"default:false" json:"emailVerified"` Phone string `gorm:"not null" json:"phone"`
Password string `gorm:"not null" json:"password"` Email string `json:"email,omitempty"`
RoleID string `gorm:"not null" json:"roleId"` PhoneVerified bool `gorm:"default:false" json:"phoneVerified"`
Role *Role `gorm:"foreignKey:RoleID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"role"` Password string `json:"password,omitempty"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"` RoleID string `gorm:"not null" json:"roleId"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"` Role *Role `gorm:"foreignKey:RoleID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"role"`
RegistrationStatus string `gorm:"default:uncompleted" json:"registrationstatus"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
} }

View File

@ -0,0 +1,35 @@
package presentation
import (
"log"
"os"
"rijig/config"
handler "rijig/internal/handler/auth"
"rijig/internal/repositories"
repository "rijig/internal/repositories/auth"
services "rijig/internal/services/auth"
"rijig/middleware"
"github.com/gofiber/fiber/v2"
)
func AdminAuthRouter(api fiber.Router) {
secretKey := os.Getenv("SECRET_KEY")
if secretKey == "" {
log.Fatal("SECRET_KEY is not set in the environment variables")
os.Exit(1)
}
adminAuthRepo := repository.NewAuthAdminRepository(config.DB)
roleRepo := repositories.NewRoleRepository(config.DB)
adminAuthService := services.NewAuthAdminService(adminAuthRepo, roleRepo, secretKey)
adminAuthHandler := handler.NewAuthAdminHandler(adminAuthService)
adminAuthAPI := api.Group("/admin-auth")
adminAuthAPI.Post("/register", adminAuthHandler.RegisterAdmin)
adminAuthAPI.Post("/login", adminAuthHandler.LoginAdmin)
adminAuthAPI.Post("/logout", middleware.AuthMiddleware, adminAuthHandler.LogoutAdmin)
}

View File

@ -0,0 +1,26 @@
package presentation
import (
"rijig/config"
handler "rijig/internal/handler/auth"
"rijig/internal/repositories"
repository "rijig/internal/repositories/auth"
services "rijig/internal/services/auth"
"rijig/middleware"
"github.com/gofiber/fiber/v2"
)
func AuthMasyarakatRouter(api fiber.Router) {
authMasyarakatRepo := repository.NewAuthMasyarakatRepositories(config.DB)
roleRepo := repositories.NewRoleRepository(config.DB)
authMasyarakatService := services.NewAuthMasyarakatService(authMasyarakatRepo, roleRepo)
authHandler := handler.NewAuthMasyarakatHandler(authMasyarakatService)
authMasyarakat := api.Group("/authmasyarakat")
authMasyarakat.Post("/auth", authHandler.RegisterOrLoginHandler)
authMasyarakat.Post("/logout", middleware.AuthMiddleware, authHandler.LogoutHandler)
authMasyarakat.Post("/verify-otp", authHandler.VerifyOTPHandler)
}

View File

@ -0,0 +1,26 @@
package presentation
import (
"rijig/config"
handler "rijig/internal/handler/auth"
"rijig/internal/repositories"
repository "rijig/internal/repositories/auth"
services "rijig/internal/services/auth"
"rijig/middleware"
"github.com/gofiber/fiber/v2"
)
func AuthPengelolaRouter(api fiber.Router) {
authPengelolaRepo := repository.NewAuthPengelolaRepositories(config.DB)
roleRepo := repositories.NewRoleRepository(config.DB)
authPengelolaService := services.NewAuthPengelolaService(authPengelolaRepo, roleRepo)
authHandler := handler.NewAuthPengelolaHandler(authPengelolaService)
authPengelola := api.Group("/authpengelola")
authPengelola.Post("/auth", authHandler.RegisterOrLoginHandler)
authPengelola.Post("/logout", middleware.AuthMiddleware, authHandler.LogoutHandler)
authPengelola.Post("/verify-otp", authHandler.VerifyOTPHandler)
}

View File

@ -0,0 +1,26 @@
package presentation
import (
"rijig/config"
handler "rijig/internal/handler/auth"
"rijig/internal/repositories"
repository "rijig/internal/repositories/auth"
services "rijig/internal/services/auth"
"rijig/middleware"
"github.com/gofiber/fiber/v2"
)
func AuthPengepulRouter(api fiber.Router) {
authPengepulRepo := repository.NewAuthPengepulRepositories(config.DB)
roleRepo := repositories.NewRoleRepository(config.DB)
authPengepulService := services.NewAuthPengepulService(authPengepulRepo, roleRepo)
authHandler := handler.NewAuthPengepulHandler(authPengepulService)
authPengepul := api.Group("/authpengepul")
authPengepul.Post("/auth", authHandler.RegisterOrLoginHandler)
authPengepul.Post("/logout", middleware.AuthMiddleware, authHandler.LogoutHandler)
authPengepul.Post("/verify-otp", authHandler.VerifyOTPHandler)
}

View File

@ -1,23 +1,23 @@
package presentation package presentation
import ( // import (
"rijig/config" // "rijig/config"
"rijig/internal/handler" // "rijig/internal/handler"
"rijig/internal/repositories" // "rijig/internal/repositories"
"rijig/internal/services" // "rijig/internal/services"
"rijig/middleware" // "rijig/middleware"
"github.com/gofiber/fiber/v2" // "github.com/gofiber/fiber/v2"
) // )
func AuthRouter(api fiber.Router) { // func AuthRouter(api fiber.Router) {
userRepo := repositories.NewUserRepository(config.DB) // userRepo := repositories.NewUserRepository(config.DB)
roleRepo := repositories.NewRoleRepository(config.DB) // roleRepo := repositories.NewRoleRepository(config.DB)
authService := services.NewAuthService(userRepo, roleRepo) // authService := services.NewAuthService(userRepo, roleRepo)
authHandler := handler.NewAuthHandler(authService) // authHandler := handler.NewAuthHandler(authService)
api.Post("/auth", authHandler.RegisterOrLoginHandler) // api.Post("/auth", authHandler.RegisterOrLoginHandler)
api.Post("/logout", middleware.AuthMiddleware, authHandler.LogoutHandler) // api.Post("/logout", middleware.AuthMiddleware, authHandler.LogoutHandler)
api.Post("/verify-otp", authHandler.VerifyOTPHandler) // api.Post("/verify-otp", authHandler.VerifyOTPHandler)
} // }

View File

@ -11,15 +11,14 @@ import (
) )
func UserPinRouter(api fiber.Router) { func UserPinRouter(api fiber.Router) {
userPinRepo := repositories.NewUserPinRepository(config.DB) userPinRepo := repositories.NewUserPinRepository(config.DB)
userPinService := services.NewUserPinService(userPinRepo) userPinService := services.NewUserPinService(userPinRepo)
userPinHandler := handler.NewUserPinHandler(userPinService) userPinHandler := handler.NewUserPinHandler(userPinService)
api.Post("/user/set-pin", middleware.AuthMiddleware, userPinHandler.CreateUserPin) api.Post("/set-pin", middleware.AuthMiddleware, userPinHandler.CreateUserPin)
api.Post("/user/verif-pin", middleware.AuthMiddleware, userPinHandler.VerifyUserPin) api.Post("/verif-pin", middleware.AuthMiddleware, userPinHandler.VerifyUserPin)
api.Get("/user/cek-pin-status", middleware.AuthMiddleware, userPinHandler.CheckPinStatus) api.Get("/cek-pin-status", middleware.AuthMiddleware, userPinHandler.CheckPinStatus)
api.Patch("/user/update-pin", middleware.AuthMiddleware, userPinHandler.UpdateUserPin) api.Patch("/update-pin", middleware.AuthMiddleware, userPinHandler.UpdateUserPin)
} }

View File

@ -5,6 +5,7 @@ import (
"rijig/middleware" "rijig/middleware"
"rijig/presentation" "rijig/presentation"
presentationn "rijig/presentation/auth"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@ -15,7 +16,13 @@ func SetupRoutes(app *fiber.App) {
api := app.Group(os.Getenv("BASE_URL")) api := app.Group(os.Getenv("BASE_URL"))
api.Use(middleware.APIKeyMiddleware) api.Use(middleware.APIKeyMiddleware)
presentation.AuthRouter(api) // || auth router || //
// presentation.AuthRouter(api)
presentationn.AdminAuthRouter(api)
presentationn.AuthPengelolaRouter(api)
presentationn.AuthPengepulRouter(api)
presentationn.AuthMasyarakatRouter(api)
// || auth router || //
presentation.UserProfileRouter(api) presentation.UserProfileRouter(api)
presentation.UserPinRouter(api) presentation.UserPinRouter(api)
presentation.RoleRouter(api) presentation.RoleRouter(api)

View File

@ -17,7 +17,6 @@ var ctx = context.Background()
const defaultExpiration = 1 * time.Hour const defaultExpiration = 1 * time.Hour
func SetData[T any](key string, value T, expiration time.Duration) error { func SetData[T any](key string, value T, expiration time.Duration) error {
if expiration == 0 { if expiration == 0 {
expiration = defaultExpiration expiration = defaultExpiration
} }
@ -36,6 +35,27 @@ func SetData[T any](key string, value T, expiration time.Duration) error {
return nil return nil
} }
func SaveSessionTokenToRedis(userID string, deviceID string, token string) error {
sessionKey := "session:" + userID + ":" + deviceID
err := config.RedisClient.Set(ctx, sessionKey, token, 24*time.Hour).Err()
if err != nil {
return err
}
log.Printf("Session token saved to Redis with key: %s", sessionKey)
return nil
}
func GetSessionTokenFromRedis(userID string, deviceID string) (string, error) {
sessionKey := "session:" + userID + ":" + deviceID
token, err := config.RedisClient.Get(ctx, sessionKey).Result()
if err != nil {
return "", err
}
return token, nil
}
func GetData(key string) (string, error) { func GetData(key string) (string, error) {
val, err := config.RedisClient.Get(ctx, key).Result() val, err := config.RedisClient.Get(ctx, key).Result()
if err == redis.Nil { if err == redis.Nil {
@ -89,9 +109,23 @@ func GetJSONData(key string) (map[string]interface{}, error) {
return data, nil return data, nil
} }
func DeleteSessionData(userID string) error { func DeleteSessionData(userID string, deviceID string) error {
sessionKey := "session:" + userID sessionKey := "session:" + userID + ":" + deviceID
return DeleteData(sessionKey) sessionTokenKey := "session_token:" + userID + ":" + deviceID
log.Printf("Attempting to delete session data with keys: %s, %s", sessionKey, sessionTokenKey)
err := DeleteData(sessionKey)
if err != nil {
return fmt.Errorf("failed to delete session data: %w", err)
}
err = DeleteData(sessionTokenKey)
if err != nil {
return fmt.Errorf("failed to delete session token: %w", err)
}
log.Printf("Successfully deleted session data for userID: %s, deviceID: %s", userID, deviceID)
return nil
} }
func logAndReturnError(message string, err error) error { func logAndReturnError(message string, err error) error {
@ -124,43 +158,11 @@ func GetStringData(key string) (string, error) {
return val, nil return val, nil
} }
// func SetStringData(key, value string, expiration time.Duration) error { func CheckSessionExists(userID string, deviceID string) (bool, error) {
// if expiration == 0 { sessionKey := "session:" + userID + ":" + deviceID
// expiration = defaultExpiration val, err := config.RedisClient.Exists(ctx, sessionKey).Result()
// } if err != nil {
return false, err
// err := config.RedisClient.Set(ctx, key, value, expiration).Err() }
// if err != nil { return val > 0, nil
// return fmt.Errorf("Error setting string data in Redis with key: %s: %v", key, err) }
// }
// log.Printf("String data stored in Redis with key: %s", key)
// return nil
// }
// GetStringData retrieves a string value from Redis by key
// func GetStringData(key string) (string, error) {
// val, err := config.RedisClient.Get(ctx, key).Result()
// if err == redis.Nil {
// return "", nil
// } else if err != nil {
// return "", fmt.Errorf("Error retrieving string data from Redis with key: %s: %v", key, err)
// }
// return val, nil
// }
// func StoreOTPInRedis(phone, otpCode string, expirationTime time.Duration) error {
// err := config.RedisClient.Set(config.Ctx, phone, otpCode, expirationTime).Err()
// if err != nil {
// return fmt.Errorf("failed to store OTP in Redis: %v", err)
// }
// return nil
// }
// func GetOTPFromRedis(phone string) (string, error) {
// otpCode, err := config.RedisClient.Get(config.Ctx, phone).Result()
// if err != nil {
// return "", fmt.Errorf("failed to get OTP from Redis: %v", err)
// }
// return otpCode, nil
// }

44
utils/regexp_formatter.go Normal file
View File

@ -0,0 +1,44 @@
package utils
import (
"regexp"
"strings"
)
func IsValidPhoneNumber(phone string) bool {
re := regexp.MustCompile(`^62\d{9,13}$`)
return re.MatchString(phone)
}
func IsValidEmail(email string) bool {
re := regexp.MustCompile(`^[a-z0-9]+@[a-z0-9]+\.[a-z]{2,}$`)
return re.MatchString(email)
}
func IsValidPassword(password string) bool {
if len(password) < 6 {
return false
}
hasUpper := false
hasDigit := false
hasSpecial := false
for _, char := range password {
if char >= 'A' && char <= 'Z' {
hasUpper = true
} else if char >= '0' && char <= '9' {
hasDigit = true
} else if isSpecialCharacter(char) {
hasSpecial = true
}
}
return hasUpper && hasDigit && hasSpecial
}
func isSpecialCharacter(char rune) bool {
specialChars := "!@#$%^&*()-_=+[]{}|;:'\",.<>?/`~"
return strings.ContainsRune(specialChars, char)
}

View File

@ -1,8 +1,8 @@
package utils package utils
const ( const (
RoleAdministrator = "46f75bb9-7f64-44b7-b378-091a67b3e229" RoleAdministrator = "42bdecce-f2ad-44ae-b3d6-883c1fbddaf7"
RoleMasyarakat = "6cfa867b-536c-448d-ba11-fe060b5af971" RolePengelola = "0bf86966-7042-410a-a88c-d01f70832348"
RolePengepul = "8171883c-ea9e-4d17-9f28-a7896d88380f" RolePengepul = "d7245535-0e9e-4d35-ab39-baece5c10b3c"
RolePengelola = "84d72ddb-68a8-430c-9b79-5d71f90cb1be" RoleMasyarakat = "60e5684e4-b214-4bd0-972f-3be80c4649a0"
) )