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.Use(cors.New(cors.Config{
AllowOrigins: "http://localhost:3000",
AllowMethods: "GET,POST,PUT,DELETE,OPTIONS",
AllowHeaders: "Origin, Content-Type, Accept, Authorization, x-api-key",
AllowCredentials: true,
AllowOrigins: "*",
AllowMethods: "GET,POST,PUT,PATCH,DELETE",
AllowHeaders: "Content-Type,x-api-key",
}))
app.Use(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.Next()
})
// app.Use(cors.New(cors.Config{
// AllowOrigins: "http://localhost:3000",
// AllowMethods: "GET,POST,PUT,DELETE,OPTIONS",
// AllowHeaders: "Origin, Content-Type, Accept, Authorization, x-api-key",
// AllowCredentials: true,
// }))
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)
})
// app.Use(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.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)

View File

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

View File

@ -5,6 +5,7 @@ import (
"fmt"
"log"
"os"
"strconv"
"github.com/go-redis/redis/v8"
)
@ -13,13 +14,21 @@ var RedisClient *redis.Client
var Ctx = context.Background()
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{
Addr: fmt.Sprintf("%s:%s", os.Getenv("REDIS_HOST"), os.Getenv("REDIS_PORT")),
Password: os.Getenv("REDIS_PASSWORD"),
DB: 0,
DB: redisDB,
})
_, err := RedisClient.Ping(Ctx).Result()
_, err = RedisClient.Ping(Ctx).Result()
if err != nil {
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)
log.Printf("Server is running on http://%s", address)
log.Printf("server berjalan di http://%s", address)
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 {
RoleID string `json:"role_id"`
Phone string `json:"phone"`
Phone string `json:"phone"`
}
type VerifyOTPRequest struct {
RoleID string `json:"role_id"`
Phone string `json:"phone"`
Phone string `json:"phone"`
OTP string `json:"otp"`
DeviceID string `json:"device_id"`
}
type UserDataResponse struct {
@ -25,10 +24,6 @@ type UserDataResponse struct {
func (r *RegisterRequest) Validate() (map[string][]string, bool) {
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) == "" {
errors["phone"] = append(errors["phone"], "Phone is required")
} 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")
}
// 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 {
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
import (
"log"
"rijig/dto"
"rijig/internal/services"
"rijig/utils"
// import (
// "log"
// "rijig/dto"
// "rijig/internal/services"
// "rijig/utils"
"github.com/gofiber/fiber/v2"
)
// "github.com/gofiber/fiber/v2"
// )
type AuthHandler struct {
authService services.AuthService
}
// type AuthHandler struct {
// authService services.AuthService
// }
func NewAuthHandler(authService services.AuthService) *AuthHandler {
return &AuthHandler{authService}
}
// func NewAuthHandler(authService services.AuthService) *AuthHandler {
// return &AuthHandler{authService}
// }
func (h *AuthHandler) RegisterOrLoginHandler(c *fiber.Ctx) error {
var req dto.RegisterRequest
// func (h *AuthHandler) RegisterOrLoginHandler(c *fiber.Ctx) error {
// var req dto.RegisterRequest
if err := c.BodyParser(&req); err != nil {
return utils.ErrorResponse(c, "Invalid request body")
}
// if err := c.BodyParser(&req); err != nil {
// return utils.ErrorResponse(c, "Invalid request body")
// }
if req.Phone == "" || req.RoleID == "" {
return utils.ErrorResponse(c, "Phone number and role ID are required")
}
// if req.Phone == "" || req.RoleID == "" {
// return utils.ErrorResponse(c, "Phone number and role ID are required")
// }
if err := h.authService.RegisterOrLogin(&req); err != nil {
return utils.ErrorResponse(c, err.Error())
}
// if err := h.authService.RegisterOrLogin(&req); err != nil {
// 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 {
var req dto.VerifyOTPRequest
// func (h *AuthHandler) VerifyOTPHandler(c *fiber.Ctx) error {
// var req dto.VerifyOTPRequest
if err := c.BodyParser(&req); err != nil {
return utils.ErrorResponse(c, "Invalid request body")
}
// 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.OTP == "" {
// return utils.ErrorResponse(c, "OTP is required")
// }
response, err := h.authService.VerifyOTP(&req)
if err != nil {
return utils.ErrorResponse(c, err.Error())
}
// response, err := h.authService.VerifyOTP(&req)
// if err != nil {
// 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)
if !ok || userID == "" {
return utils.ErrorResponse(c, "User is not logged in or invalid session")
}
// userID, ok := c.Locals("userID").(string)
// if !ok || userID == "" {
// return utils.ErrorResponse(c, "User is not logged in or invalid session")
// }
phoneKey := "user_phone:" + userID
phone, err := utils.GetStringData(phoneKey)
if err != nil || phone == "" {
// phoneKey := "user_phone:" + userID
// phone, err := utils.GetStringData(phoneKey)
// if err != nil || phone == "" {
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")
}
// 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")
// }
err = h.authService.Logout(userID, phone)
if err != nil {
// err = h.authService.Logout(userID, phone)
// if err != nil {
log.Printf("Error during logout process for user %s: %v", userID, err)
return utils.ErrorResponse(c, err.Error())
}
// 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")
}
// 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 {
FindByID(id string) (*model.Role, error)
FindRoleByName(roleName string) (*model.Role, error)
FindAll() ([]model.Role, error)
}
@ -36,3 +37,12 @@ func (r *roleRepository) FindAll() ([]model.Role, error) {
}
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
import (
"errors"
"fmt"
"math/rand"
"rijig/config"
"rijig/dto"
"rijig/internal/repositories"
"rijig/model"
"rijig/utils"
"time"
// import (
// "errors"
// "fmt"
// "math/rand"
// "rijig/config"
// "rijig/dto"
// "rijig/internal/repositories"
// "rijig/model"
// "rijig/utils"
// "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 {
RegisterOrLogin(req *dto.RegisterRequest) error
VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error)
Logout(userID, phone string) error
}
// type AuthService interface {
// RegisterOrLogin(req *dto.RegisterRequest) error
// VerifyOTP(req *dto.VerifyOTPRequest) (*dto.UserDataResponse, error)
// Logout(userID, phone string) error
// }
type authService struct {
userRepo repositories.UserRepository
roleRepo repositories.RoleRepository
}
// type authService struct {
// userRepo repositories.UserRepository
// roleRepo repositories.RoleRepository
// }
func NewAuthService(userRepo repositories.UserRepository, roleRepo repositories.RoleRepository) AuthService {
return &authService{userRepo, roleRepo}
}
// func NewAuthService(userRepo repositories.UserRepository, roleRepo repositories.RoleRepository) AuthService {
// 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 {
return err
}
// if err := s.checkOTPRequestCooldown(req.Phone); err != nil {
// return err
// }
user, err := s.userRepo.GetUserByPhoneAndRole(req.Phone, req.RoleID)
if err != nil {
return fmt.Errorf("failed to check existing user: %w", err)
}
// user, err := s.userRepo.GetUserByPhoneAndRole(req.Phone, req.RoleID)
// if err != nil {
// return fmt.Errorf("failed to check existing user: %w", err)
// }
if user != nil {
return s.sendOTP(req.Phone)
}
// if user != nil {
// return s.sendOTP(req.Phone)
// }
user = &model.User{
Phone: req.Phone,
RoleID: req.RoleID,
}
// user = &model.User{
// Phone: req.Phone,
// RoleID: req.RoleID,
// }
createdUser, err := s.userRepo.CreateUser(user)
if err != nil {
return fmt.Errorf("failed to create new user: %w", err)
}
// createdUser, err := s.userRepo.CreateUser(user)
// if err != nil {
// return fmt.Errorf("failed to create new user: %w", err)
// }
if err := s.saveUserToRedis(createdUser.ID, createdUser, req.Phone); err != nil {
return err
}
// if err := s.saveUserToRedis(createdUser.ID, createdUser, req.Phone); err != nil {
// return err
// }
return s.sendOTP(req.Phone)
}
// return s.sendOTP(req.Phone)
// }
func (s *authService) 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 *authService) 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 *authService) sendOTP(phone string) error {
otp := generateOTP()
if err := config.SendWhatsAppMessage(phone, fmt.Sprintf("Your OTP is: %s", otp)); err != nil {
return err
}
// func (s *authService) 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)
}
// 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 *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)
if err != nil || storedOTP == "" {
return nil, errors.New("OTP expired or not found")
}
// 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 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:" + req.Phone); err != nil {
// return nil, fmt.Errorf("failed to remove OTP from Redis: %w", err)
// }
existingUser, err := s.userRepo.GetUserByPhoneAndRole(req.Phone, req.RoleID)
if err != nil {
return nil, fmt.Errorf("failed to check existing user: %w", err)
}
// existingUser, err := s.userRepo.GetUserByPhoneAndRole(req.Phone, req.RoleID)
// if err != nil {
// return nil, fmt.Errorf("failed to check existing user: %w", err)
// }
var user *model.User
if existingUser != nil {
user = existingUser
} else {
// var user *model.User
// if existingUser != nil {
// user = existingUser
// } else {
user = &model.User{
Phone: req.Phone,
RoleID: req.RoleID,
}
createdUser, err := s.userRepo.CreateUser(user)
if err != nil {
return nil, err
}
user = createdUser
}
// user = &model.User{
// Phone: req.Phone,
// RoleID: req.RoleID,
// }
// createdUser, err := s.userRepo.CreateUser(user)
// if err != nil {
// return nil, err
// }
// user = createdUser
// }
token, err := s.generateJWTToken(user.ID)
if err != nil {
return nil, err
}
// token, err := s.generateJWTToken(user.ID)
// 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)
}
// role, err := s.roleRepo.FindByID(user.RoleID)
// if err != nil {
// return nil, fmt.Errorf("failed to get role: %w", err)
// }
if err := s.saveSessionData(user.ID, user.RoleID, role.RoleName, token); err != nil {
return nil, err
}
// if err := s.saveSessionData(user.ID, user.RoleID, role.RoleName, token); err != nil {
// return nil, err
// }
return &dto.UserDataResponse{
UserID: user.ID,
UserRole: role.RoleName,
Token: token,
}, nil
}
// return &dto.UserDataResponse{
// UserID: user.ID,
// UserRole: role.RoleName,
// Token: token,
// }, nil
// }
func (s *authService) saveUserToRedis(userID string, user *model.User, phone string) error {
if err := utils.SetJSONData("user:"+userID, user, 10*time.Minute); err != nil {
return fmt.Errorf("failed to store user data in Redis: %w", err)
}
// func (s *authService) saveUserToRedis(userID string, user *model.User, phone string) error {
// if err := utils.SetJSONData("user:"+userID, user, 10*time.Minute); err != nil {
// 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 {
return fmt.Errorf("failed to store user phone in Redis: %w", err)
}
// 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 nil
}
// return nil
// }
func (s *authService) generateJWTToken(userID string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := &jwt.RegisteredClaims{
Subject: userID,
ExpiresAt: jwt.NewNumericDate(expirationTime),
}
// func (s *authService) generateJWTToken(userID string) (string, error) {
// expirationTime := time.Now().Add(24 * time.Hour)
// claims := &jwt.RegisteredClaims{
// Subject: userID,
// ExpiresAt: jwt.NewNumericDate(expirationTime),
// }
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
secretKey := config.GetSecretKey()
// token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 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 {
sessionKey := fmt.Sprintf("session:%s", userID)
sessionData := map[string]interface{}{
"userID": userID,
"roleID": roleID,
"roleName": roleName,
}
// func (s *authService) saveSessionData(userID string, roleID string, roleName string, token string) error {
// sessionKey := fmt.Sprintf("session:%s", userID)
// 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.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, token, 24*time.Hour); err != nil {
return fmt.Errorf("failed to set session token: %w", err)
}
// if err := utils.SetStringData("session_token:"+userID, token, 24*time.Hour); err != nil {
// return fmt.Errorf("failed to set session token: %w", err)
// }
return nil
}
// return nil
// }
func (s *authService) Logout(userID, phone string) error {
keys := []string{
"session:" + userID,
"session_token:" + userID,
"user_logged_in:" + userID,
"user:" + userID,
"user_phone:" + userID,
"otp_sent:" + phone,
}
// func (s *authService) Logout(userID, phone string) error {
// keys := []string{
// "session:" + userID,
// "session_token:" + userID,
// "user_logged_in:" + userID,
// "user:" + userID,
// "user_phone:" + userID,
// "otp_sent:" + phone,
// }
for _, key := range keys {
if err := utils.DeleteData(key); err != nil {
return fmt.Errorf("failed to delete key %s from Redis: %w", key, err)
}
}
// for _, key := range keys {
// if err := utils.DeleteData(key); err != nil {
// return fmt.Errorf("failed to delete key %s from Redis: %w", key, err)
// }
// }
return nil
}
// return nil
// }
func generateOTP() string {
randGenerator := rand.New(rand.NewSource(time.Now().UnixNano()))
return fmt.Sprintf("%04d", randGenerator.Intn(10000))
}
// func generateOTP() string {
// randGenerator := rand.New(rand.NewSource(time.Now().UnixNano()))
// 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{
ID: user.ID,
Username: user.Username,
// Username: user.Username,
Avatar: user.Avatar,
Name: user.Name,
Phone: user.Phone,
Email: user.Email,
EmailVerified: user.EmailVerified,
// EmailVerified: user.EmailVerified,
RoleName: user.Role.RoleName,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
@ -100,12 +100,12 @@ func (s *userProfileService) GetAllUsers() ([]dto.UserResponseDTO, error) {
for _, user := range users {
response = append(response, dto.UserResponseDTO{
ID: user.ID,
Username: user.Username,
// Username: user.Username,
Avatar: user.Avatar,
Name: user.Name,
Phone: user.Phone,
Email: user.Email,
EmailVerified: user.EmailVerified,
// Email: user.Email,
// EmailVerified: user.EmailVerified,
RoleName: user.Role.RoleName,
CreatedAt: user.CreatedAt.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 {
response = append(response, dto.UserResponseDTO{
ID: user.ID,
Username: user.Username,
// Username: user.Username,
Avatar: user.Avatar,
Name: user.Name,
Phone: user.Phone,
Email: user.Email,
EmailVerified: user.EmailVerified,
// Email: user.Email,
// EmailVerified: user.EmailVerified,
RoleName: user.Role.RoleName,
CreatedAt: user.CreatedAt.Format(time.RFC3339),
UpdatedAt: user.UpdatedAt.Format(time.RFC3339),

View File

@ -1,8 +1,9 @@
package middleware
import (
"fmt"
"log"
"os"
"rijig/utils"
"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) {
return []byte(os.Getenv("SECRET_KEY")), nil
})
if err != nil || !token.Valid {
log.Printf("Error parsing token: %v", err)
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid token")
}
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")
}
userID := claims["sub"].(string)
if userID == "" {
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid user session")
}
deviceID := claims["device_id"].(string)
sessionKey := "session:" + userID
sessionKey := fmt.Sprintf("session:%s:%s", userID, deviceID)
sessionData, err := utils.GetJSONData(sessionKey)
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")
}
roleID, roleOK := sessionData["roleID"].(string)
roleName, roleNameOK := sessionData["roleName"].(string)
if !roleOK || !roleNameOK {
log.Println("Invalid session data for userID:", userID)
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: Invalid session data")
}
c.Locals("userID", userID)
c.Locals("roleID", roleID)
c.Locals("roleName", roleName)
c.Locals("device_id", deviceID)
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 {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
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"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
}

View File

@ -3,16 +3,19 @@ package model
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:"not null" json:"username"`
Name string `gorm:"not null" json:"name"`
Phone string `gorm:"not null" json:"phone"`
Email string `gorm:"not null" json:"email"`
EmailVerified bool `gorm:"default:false" json:"emailVerified"`
Password string `gorm:"not null" json:"password"`
RoleID string `gorm:"not null" json:"roleId"`
Role *Role `gorm:"foreignKey:RoleID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"role"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
Avatar *string `json:"avatar,omitempty"`
Name string `gorm:"not null" json:"name"`
Gender string `gorm:"not null" json:"gender"`
Dateofbirth string `gorm:"not null" json:"dateofbirth"`
Placeofbirth string `gorm:"not null" json:"placeofbirth"`
Phone string `gorm:"not null" json:"phone"`
Email string `json:"email,omitempty"`
PhoneVerified bool `gorm:"default:false" json:"phoneVerified"`
Password string `json:"password,omitempty"`
RoleID string `gorm:"not null" json:"roleId"`
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
import (
"rijig/config"
"rijig/internal/handler"
"rijig/internal/repositories"
"rijig/internal/services"
"rijig/middleware"
// import (
// "rijig/config"
// "rijig/internal/handler"
// "rijig/internal/repositories"
// "rijig/internal/services"
// "rijig/middleware"
"github.com/gofiber/fiber/v2"
)
// "github.com/gofiber/fiber/v2"
// )
func AuthRouter(api fiber.Router) {
userRepo := repositories.NewUserRepository(config.DB)
roleRepo := repositories.NewRoleRepository(config.DB)
authService := services.NewAuthService(userRepo, roleRepo)
// func AuthRouter(api fiber.Router) {
// userRepo := repositories.NewUserRepository(config.DB)
// roleRepo := repositories.NewRoleRepository(config.DB)
// authService := services.NewAuthService(userRepo, roleRepo)
authHandler := handler.NewAuthHandler(authService)
// authHandler := handler.NewAuthHandler(authService)
api.Post("/auth", authHandler.RegisterOrLoginHandler)
api.Post("/logout", middleware.AuthMiddleware, authHandler.LogoutHandler)
api.Post("/verify-otp", authHandler.VerifyOTPHandler)
}
// api.Post("/auth", authHandler.RegisterOrLoginHandler)
// api.Post("/logout", middleware.AuthMiddleware, authHandler.LogoutHandler)
// api.Post("/verify-otp", authHandler.VerifyOTPHandler)
// }

View File

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

View File

@ -5,6 +5,7 @@ import (
"rijig/middleware"
"rijig/presentation"
presentationn "rijig/presentation/auth"
"github.com/gofiber/fiber/v2"
)
@ -15,7 +16,13 @@ func SetupRoutes(app *fiber.App) {
api := app.Group(os.Getenv("BASE_URL"))
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.UserPinRouter(api)
presentation.RoleRouter(api)

View File

@ -17,7 +17,6 @@ var ctx = context.Background()
const defaultExpiration = 1 * time.Hour
func SetData[T any](key string, value T, expiration time.Duration) error {
if expiration == 0 {
expiration = defaultExpiration
}
@ -36,6 +35,27 @@ func SetData[T any](key string, value T, expiration time.Duration) error {
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) {
val, err := config.RedisClient.Get(ctx, key).Result()
if err == redis.Nil {
@ -89,9 +109,23 @@ func GetJSONData(key string) (map[string]interface{}, error) {
return data, nil
}
func DeleteSessionData(userID string) error {
sessionKey := "session:" + userID
return DeleteData(sessionKey)
func DeleteSessionData(userID string, deviceID string) error {
sessionKey := "session:" + userID + ":" + deviceID
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 {
@ -124,43 +158,11 @@ func GetStringData(key string) (string, error) {
return val, nil
}
// func SetStringData(key, value string, expiration time.Duration) error {
// if expiration == 0 {
// expiration = defaultExpiration
// }
// err := config.RedisClient.Set(ctx, key, value, expiration).Err()
// if err != 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
// }
func CheckSessionExists(userID string, deviceID string) (bool, error) {
sessionKey := "session:" + userID + ":" + deviceID
val, err := config.RedisClient.Exists(ctx, sessionKey).Result()
if err != nil {
return false, err
}
return val > 0, 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
const (
RoleAdministrator = "46f75bb9-7f64-44b7-b378-091a67b3e229"
RoleMasyarakat = "6cfa867b-536c-448d-ba11-fe060b5af971"
RolePengepul = "8171883c-ea9e-4d17-9f28-a7896d88380f"
RolePengelola = "84d72ddb-68a8-430c-9b79-5d71f90cb1be"
RoleAdministrator = "42bdecce-f2ad-44ae-b3d6-883c1fbddaf7"
RolePengelola = "0bf86966-7042-410a-a88c-d01f70832348"
RolePengepul = "d7245535-0e9e-4d35-ab39-baece5c10b3c"
RoleMasyarakat = "60e5684e4-b214-4bd0-972f-3be80c4649a0"
)