Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 38 additions & 42 deletions api/handlers/auth_handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers

import (
"api-shiners/api/handlers/dto"
"api-shiners/pkg/auth"
"api-shiners/pkg/utils"
"context"
Expand All @@ -10,7 +11,6 @@ import (
"github.com/gofiber/fiber/v2"
)

// AuthController handles authentication related endpoints
type AuthController struct {
authService auth.AuthService
}
Expand All @@ -19,17 +19,16 @@ func NewAuthController(authService auth.AuthService) AuthController {
return AuthController{authService: authService}
}

// ==================== REGISTER ====================

// Register godoc
// @Summary Register a new user
// @Description Create a new user account
// @Description Membuat akun user baru
// @Tags Auth
// @Accept json
// @Produce json
// @Param request body auth.RegisterRequest true "Register Request"
// @Success 201 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Param request body dto.RegisterRequest true "Register Request"
// @Success 201 {object} dto.RegisterResponse
// @Failure 400 {object} utils.ErrorResponse
// @Failure 500 {object} utils.ErrorResponse
// @Router /api/auth/register [post]
func (ctrl *AuthController) Register(c *fiber.Ctx) error {
var req auth.RegisterRequest
Expand All @@ -45,49 +44,53 @@ func (ctrl *AuthController) Register(c *fiber.Ctx) error {
return utils.Success(c, http.StatusCreated, "User registered successfully", createdUser, nil)
}

// ==================== LOGIN ====================

// Login godoc
// @Summary Login user
// @Description Authenticate user and return JWT token
// @Description Autentikasi user dan mendapatkan JWT token
// @Tags Auth
// @Accept json
// @Produce json
// @Param request body auth.LoginRequest true "Login Request"
// @Success 200 {object} map[string]interface{}
// @Failure 401 {object} map[string]interface{}
// @Param request body dto.LoginRequest true "Login Request"
// @Success 200 {object} dto.LoginResponse
// @Failure 400 {object} utils.ErrorResponse
// @Failure 401 {object} utils.ErrorResponse
// @Router /api/auth/login [post]
func (ctrl *AuthController) Login(c *fiber.Ctx) error {
var req auth.LoginRequest
if err := c.BodyParser(&req); err != nil {
return utils.Error(c, http.StatusBadRequest, "Invalid request body", "BadRequestException", nil)
}

token, exp, err := ctrl.authService.Login(context.Background(), req)
user, token, exp, permissions, err := ctrl.authService.LoginCore(context.Background(), req)
if err != nil {
return utils.Error(c, http.StatusUnauthorized, err.Error(), "UnauthorizedException", nil)
}

data := fiber.Map{
"token": token,
"expires_in": exp.Format(time.RFC3339),
"token_type": "Bearer",
"token": token,
"expires_in": exp.Format(time.RFC3339),
"token_type": "Bearer",
"user": fiber.Map{
"id": user.ID,
"name": user.Name,
"role": user.Roles,
"permissions": permissions,
},
}

return utils.Success(c, http.StatusOK, "Login successful", data, nil)
}

// ==================== LOGOUT ====================

// Logout godoc
// @Summary Logout user
// @Description Invalidate user token
// @Description Mengakhiri sesi dan menonaktifkan token
// @Tags Auth
// @Accept json
// @Produce json
// @Security BearerAuth
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Success 200 {object} dto.GenericResponse
// @Failure 400 {object} utils.ErrorResponse
// @Failure 401 {object} utils.ErrorResponse
// @Router /api/auth/logout [post]
func (ctrl *AuthController) Logout(c *fiber.Ctx) error {
token := c.Get("Authorization")
Expand All @@ -107,22 +110,19 @@ func (ctrl *AuthController) Logout(c *fiber.Ctx) error {
return utils.Success(c, http.StatusOK, "Logout successful", nil, nil)
}

// ==================== FORGOT PASSWORD ====================

// ForgotPassword godoc
// @Summary Request password reset
// @Description Generate reset token and send it to user's email
// @Description Generate reset token dan kirim ke email user
// @Tags Auth
// @Accept json
// @Produce json
// @Param request body map[string]string true "Email Request"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Param request body dto.ForgotPasswordRequest true "Forgot Password Request"
// @Success 200 {object} dto.GenericResponse
// @Failure 400 {object} utils.ErrorResponse
// @Failure 500 {object} utils.ErrorResponse
// @Router /api/auth/forgot-password [post]
func (ctrl *AuthController) ForgotPassword(c *fiber.Ctx) error {
var req struct {
Email string `json:"email"`
}
var req dto.ForgotPasswordRequest
if err := c.BodyParser(&req); err != nil || req.Email == "" {
return utils.Error(c, http.StatusBadRequest, "Email is required", "BadRequestException", nil)
}
Expand All @@ -134,27 +134,23 @@ func (ctrl *AuthController) ForgotPassword(c *fiber.Ctx) error {

return utils.Success(c, http.StatusOK, "Password reset token generated", fiber.Map{
"email": req.Email,
"token": token, // tampilkan untuk testing
"token": token, // ditampilkan untuk keperluan testing
}, nil)
}

// ==================== RESET PASSWORD ====================

// ResetPassword godoc
// @Summary Reset user password
// @Description Reset password using valid reset token
// @Description Reset password menggunakan reset token yang valid
// @Tags Auth
// @Accept json
// @Produce json
// @Param request body map[string]string true "Reset Password Request"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Param request body dto.ResetPasswordRequest true "Reset Password Request"
// @Success 200 {object} dto.GenericResponse
// @Failure 400 {object} utils.ErrorResponse
// @Failure 500 {object} utils.ErrorResponse
// @Router /api/auth/reset-password [post]
func (ctrl *AuthController) ResetPassword(c *fiber.Ctx) error {
var req struct {
Token string `json:"token"`
NewPassword string `json:"new_password"`
}
var req dto.ResetPasswordRequest
if err := c.BodyParser(&req); err != nil || req.Token == "" || req.NewPassword == "" {
return utils.Error(c, http.StatusBadRequest, "Token and new password required", "BadRequestException", nil)
}
Expand Down
45 changes: 45 additions & 0 deletions api/handlers/dto/auth_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dto

// RegisterRequest — payload untuk registrasi user
type RegisterRequest struct {
Name string `json:"name" example:"John Doe"`
Email string `json:"email" example:"[email protected]"`
Password string `json:"password" example:"strongpassword123"`
}

// RegisterResponse — response sukses registrasi
type RegisterResponse struct {
ID string `json:"id" example:"a3b2c1d4-56ef-7890-gh12-ijk345lmn678"`
Name string `json:"name" example:"John Doe"`
Email string `json:"email" example:"[email protected]"`
}

// LoginRequest — payload login user
type LoginRequest struct {
Email string `json:"email" example:"[email protected]"`
Password string `json:"password" example:"strongpassword123"`
}

// LoginResponse — response sukses login
type LoginResponse struct {
Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"`
ExpiresIn string `json:"expires_in" example:"2025-10-18T15:04:05Z"`
TokenType string `json:"token_type" example:"Bearer"`
User interface{} `json:"user"`
}

// ForgotPasswordRequest — payload untuk lupa password
type ForgotPasswordRequest struct {
Email string `json:"email" example:"[email protected]"`
}

// ResetPasswordRequest — payload untuk reset password
type ResetPasswordRequest struct {
Token string `json:"token" example:"123456"`
NewPassword string `json:"new_password" example:"newStrongPassword123"`
}

// GenericResponse — response umum (sukses tanpa data)
type GenericResponse struct {
Message string `json:"message" example:"Operation successful"`
}
10 changes: 10 additions & 0 deletions api/handlers/dto/feedback_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dto

type CreateQuestionRequest struct {
Question string `json:"question" example:"Apa pendapat Anda tentang pelatihan ini?"`
}

type SubmitAnswerRequest struct {
QuestionID string `json:"question_id" example:"b5a1c6c3-1234-4bcd-9123-a12b34cd56ef"`
Answer string `json:"answer" example:"Sangat bermanfaat dan jelas"`
}
55 changes: 55 additions & 0 deletions api/handlers/dto/user_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package dto

// ==================== REQUEST DTO ====================

type SetRoleRequest struct {
Role string `json:"role" example:"ADMIN"`
}

// ==================== RESPONSE DTO ====================

type UserResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
IsActive bool `json:"is_active"`
Role string `json:"role,omitempty"`
}

type UserRoleResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Role string `json:"role"`
}

type UserStatusResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
IsActive bool `json:"is_active"`
}

type UserProfileResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
IsActive bool `json:"is_active"`
Roles []string `json:"roles"`
}

// ==================== PAGINATION META ====================

// MetaResponse digunakan untuk metadata pagination di response
type MetaResponse struct {
Page int `json:"page" example:"1"`
PerPage int `json:"per_page" example:"10"`
Total int `json:"total" example:"100"`
}

// ==================== PAGINATED RESPONSE ====================

type PaginatedUsersResponse struct {
Data []UserResponse `json:"data"`
Meta MetaResponse `json:"meta"`
}
Loading