diff --git a/api/handlers/auth_handler.go b/api/handlers/auth_handler.go index 7786195..a4620c4 100644 --- a/api/handlers/auth_handler.go +++ b/api/handlers/auth_handler.go @@ -1,6 +1,7 @@ package handlers import ( + "api-shiners/api/handlers/dto" "api-shiners/pkg/auth" "api-shiners/pkg/utils" "context" @@ -10,7 +11,6 @@ import ( "github.com/gofiber/fiber/v2" ) -// AuthController handles authentication related endpoints type AuthController struct { authService auth.AuthService } @@ -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 @@ -45,17 +44,16 @@ 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 @@ -63,31 +61,36 @@ func (ctrl *AuthController) Login(c *fiber.Ctx) error { 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") @@ -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) } @@ -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) } diff --git a/api/handlers/dto/auth_request.go b/api/handlers/dto/auth_request.go new file mode 100644 index 0000000..d9a8cc0 --- /dev/null +++ b/api/handlers/dto/auth_request.go @@ -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:"john@example.com"` + 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:"john@example.com"` +} + +// LoginRequest — payload login user +type LoginRequest struct { + Email string `json:"email" example:"john@example.com"` + 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:"john@example.com"` +} + +// 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"` +} diff --git a/api/handlers/dto/feedback_request.go b/api/handlers/dto/feedback_request.go new file mode 100644 index 0000000..8766c6f --- /dev/null +++ b/api/handlers/dto/feedback_request.go @@ -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"` +} \ No newline at end of file diff --git a/api/handlers/dto/user_request.go b/api/handlers/dto/user_request.go new file mode 100644 index 0000000..2a4c150 --- /dev/null +++ b/api/handlers/dto/user_request.go @@ -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"` +} diff --git a/api/handlers/feedback_handler.go b/api/handlers/feedback_handler.go new file mode 100644 index 0000000..de98190 --- /dev/null +++ b/api/handlers/feedback_handler.go @@ -0,0 +1,147 @@ +package handlers + +import ( + "api-shiners/api/handlers/dto" + "api-shiners/pkg/feedback" + "api-shiners/pkg/utils" + "context" + "net/http" + + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" +) + +type FeedbackController struct { + service feedback.FeedbackService +} + +func NewFeedbackController(service feedback.FeedbackService) *FeedbackController { + return &FeedbackController{service: service} +} + + +// @Summary Create new feedback question +// @Description Admin membuat pertanyaan feedback baru +// @Tags Feedback +// @Accept json +// @Produce json +// @Param request body dto.CreateQuestionRequest true "Question payload" +// @Success 201 {object} entities.FeedbackQuestion +// @Failure 400 {object} utils.ErrorResponse +// @Failure 401 {object} utils.ErrorResponse +// @Failure 500 {object} utils.ErrorResponse +// @Router /api/feedback/questions [post] +func (h *FeedbackController) CreateQuestion(c *fiber.Ctx) error { + var req dto.CreateQuestionRequest + if err := c.BodyParser(&req); err != nil { + return utils.Error(c, http.StatusBadRequest, "Invalid request body", "BadRequest", nil) + } + + if req.Question == "" { + return utils.Error(c, http.StatusBadRequest, "Question is required", "ValidationError", nil) + } + + userIDStr := c.Locals("user_id") + if userIDStr == nil { + return utils.Error(c, http.StatusUnauthorized, "Unauthorized", "Unauthorized", nil) + } + + userID, err := uuid.Parse(userIDStr.(string)) + if err != nil { + return utils.Error(c, http.StatusUnauthorized, "Invalid user ID", "Unauthorized", nil) + } + + ctx := context.Background() + + question, err := h.service.CreateQuestion(ctx, req.Question, userID) + if err != nil { + return utils.Error(c, http.StatusInternalServerError, "Failed to create question", "InternalServerError", nil) + } + + return utils.Success(c, http.StatusCreated, "Feedback question created successfully", question, nil) +} + + +// @Summary Get all feedback questions +// @Description Mendapatkan semua pertanyaan feedback +// @Tags Feedback +// @Produce json +// @Success 200 {array} entities.FeedbackQuestion +// @Failure 500 {object} utils.ErrorResponse +// @Router /api/feedback/questions [get] +func (h *FeedbackController) GetAllQuestions(c *fiber.Ctx) error { + ctx := context.Background() + questions, err := h.service.GetAllQuestions(ctx) + if err != nil { + return utils.Error(c, http.StatusInternalServerError, "Failed to get questions", "InternalServerError", nil) + } + + return utils.Success(c, http.StatusOK, "Get all feedback questions successfully", questions, nil) +} + + +// @Summary Submit feedback answer +// @Description Mahasiswa mengirimkan jawaban feedback +// @Tags Feedback +// @Accept json +// @Produce json +// @Param request body dto.SubmitAnswerRequest true "Answer payload" +// @Success 201 {object} utils.SuccessResponse +// @Failure 400 {object} utils.ErrorResponse +// @Failure 401 {object} utils.ErrorResponse +// @Failure 500 {object} utils.ErrorResponse +// @Router /api/feedback/answers [post] +func (h *FeedbackController) SubmitAnswer(c *fiber.Ctx) error { + var req dto.SubmitAnswerRequest + if err := c.BodyParser(&req); err != nil { + return utils.Error(c, http.StatusBadRequest, "Invalid request body", "BadRequest", nil) + } + + if req.QuestionID == "" || req.Answer == "" { + return utils.Error(c, http.StatusBadRequest, "QuestionID and Answer are required", "ValidationError", nil) + } + + userIDStr := c.Locals("user_id") + if userIDStr == nil { + return utils.Error(c, http.StatusUnauthorized, "Unauthorized", "Unauthorized", nil) + } + + studentID, err := uuid.Parse(userIDStr.(string)) + if err != nil { + return utils.Error(c, http.StatusUnauthorized, "Invalid user ID", "Unauthorized", nil) + } + + questionID, err := uuid.Parse(req.QuestionID) + if err != nil { + return utils.Error(c, http.StatusBadRequest, "Invalid question ID", "BadRequest", nil) + } + + ctx := context.Background() + if err := h.service.SubmitAnswer(ctx, questionID, studentID, req.Answer); err != nil { + return utils.Error(c, http.StatusInternalServerError, "Failed to submit answer", "InternalServerError", nil) + } + + return utils.Success(c, http.StatusCreated, "Answer submitted successfully", nil, nil) +} + + + +func (h *FeedbackController) GetQuestionsWithAnswersByTeacher(c *fiber.Ctx) error { + userIDStr := c.Locals("user_id") + if userIDStr == nil { + return utils.Error(c, http.StatusUnauthorized, "Unauthorized", "Unauthorized", nil) + } + + teacherID, err := uuid.Parse(userIDStr.(string)) + if err != nil { + return utils.Error(c, http.StatusUnauthorized, "Invalid teacher ID", "Unauthorized", nil) + } + + ctx := context.Background() + questions, err := h.service.GetQuestionsWithAnswersByTeacher(ctx, teacherID) + if err != nil { + return utils.Error(c, http.StatusInternalServerError, "Failed to get feedback data", "InternalServerError", nil) + } + + return utils.Success(c, http.StatusOK, "Get feedback with answers successfully", questions, nil) +} diff --git a/api/handlers/health_handler.go b/api/handlers/health_handler.go index a148e10..8d38af0 100644 --- a/api/handlers/health_handler.go +++ b/api/handlers/health_handler.go @@ -10,10 +10,8 @@ import ( "github.com/gofiber/fiber/v2" ) -// HealthController untuk handle health check API type HealthController struct{} -// NewHealthController membuat instance baru HealthController func NewHealthController() HealthController { return HealthController{} } @@ -25,13 +23,12 @@ func NewHealthController() HealthController { // @Produce json // @Success 200 {object} map[string]interface{} // @Failure 503 {object} map[string]interface{} -// @Router /api/health [get] +// @Router /api/health/database [get] func (ctrl *HealthController) HealthCheckDatabase(c *fiber.Ctx) error { status := fiber.Map{ "status": "ok", } - // ✅ Cek koneksi database PostgreSQL sqlDB, err := config.DB.DB() if err != nil { status["database"] = "error: cannot access sql.DB" @@ -50,28 +47,33 @@ func (ctrl *HealthController) HealthCheckDatabase(c *fiber.Ctx) error { } + +// HealthCheck godoc +// @Summary Check service health +// @Description Check database connection status +// @Tags Health +// @Produce json +// @Success 200 {object} map[string]interface{} +// @Failure 503 {object} map[string]interface{} +// @Router /api/health/redis [get] func (ctrl *HealthController) HealthCheckRedis(c *fiber.Ctx) error { status := fiber.Map{ "status": "ok", } - // 🔹 Gunakan context lokal ctx := context.Background() - // 🔹 Cek apakah RedisClient tersedia if config.RedisClient == nil { status["redis"] = "not connected" return utils.Success(c, http.StatusOK, "Redis not initialized (dev mode or disabled)", status, nil) } - // 🔹 Coba ping ke Redis _, err := config.RedisClient.Ping(ctx).Result() if err != nil { status["redis"] = "not connected" return utils.Success(c, http.StatusOK, fmt.Sprintf("Redis not connected: %v", err.Error()), status, nil) } - // 🔹 Jika Redis sehat status["redis"] = "connected" return utils.Success(c, http.StatusOK, "Redis connected successfully", status, nil) } diff --git a/api/handlers/user_handler.go b/api/handlers/user_handler.go index f4adf70..e606f88 100644 --- a/api/handlers/user_handler.go +++ b/api/handlers/user_handler.go @@ -1,10 +1,11 @@ package handlers import ( - "net/http" - + "api-shiners/api/handlers/dto" "api-shiners/pkg/user" "api-shiners/pkg/utils" + "context" + "net/http" "github.com/gofiber/fiber/v2" "github.com/google/uuid" @@ -15,30 +16,269 @@ type UserController struct { } func NewUserController(userService user.UserService) *UserController { - return &UserController{userService} + return &UserController{userService: userService} } + +// GetAllUsers godoc +// @Summary Get all users +// @Description Retrieve a paginated list of all users +// @Tags Users +// @Accept json +// @Produce json +// @Param page query int false "Page number" +// @Param per_page query int false "Items per page" +// @Success 200 {object} dto.PaginatedUsersResponse +// @Failure 500 {object} utils.ErrorResponse +// @Router /api/users [get] func (ctrl *UserController) GetAllUsers(c *fiber.Ctx) error { - users, err := ctrl.userService.GetAllUsers() + page := c.QueryInt("page", 1) + perPage := c.QueryInt("per_page", 10) + + if page < 1 { + page = 1 + } + if perPage < 1 { + perPage = 10 + } + + users, total, err := ctrl.userService.GetAllUsers(page, perPage) if err != nil { return utils.Error(c, http.StatusInternalServerError, err.Error(), "InternalServerError", nil) } - return utils.Success(c, http.StatusOK, "Get all users successfully", users, nil) + // mapping ke DTO + var userDTOs []dto.UserResponse + for _, u := range users { + userDTOs = append(userDTOs, dto.UserResponse{ + ID: u.ID.String(), + Name: u.Name, + Email: u.Email, + IsActive: u.IsActive, + }) + } + + meta := dto.MetaResponse{ + Page: page, + PerPage: perPage, + Total: int(total), + } + + response := dto.PaginatedUsersResponse{ + Data: userDTOs, + Meta: meta, + } + + return utils.Success(c, http.StatusOK, "Get all users successfully", response, nil) } +// GetUserByID godoc +// @Summary Get user by ID +// @Description Retrieve user details by user ID +// @Tags Users +// @Accept json +// @Produce json +// @Param id path string true "User ID" +// @Success 200 {object} dto.UserResponse +// @Failure 400 {object} utils.ErrorResponse +// @Failure 404 {object} utils.ErrorResponse +// @Router /api/users/{id} [get] func (ctrl *UserController) GetUserByID(c *fiber.Ctx) error { - idParam := c.Params("id") - userID, err := uuid.Parse(idParam) + userID, err := uuid.Parse(c.Params("id")) if err != nil { return utils.Error(c, http.StatusBadRequest, "Invalid user ID format", "InvalidUUID", nil) } - user, err := ctrl.userService.GetUserByID(userID) + u, err := ctrl.userService.GetUserByID(userID) if err != nil { return utils.Error(c, http.StatusNotFound, "User not found", "UserNotFound", nil) } - return utils.Success(c, http.StatusOK, "Get user by ID successfully", user, nil) + resp := dto.UserResponse{ + ID: u.ID.String(), + Name: u.Name, + Email: u.Email, + IsActive: u.IsActive, + } + + return utils.Success(c, http.StatusOK, "Get user by ID successfully", resp, nil) +} + + +// SetUserRole godoc +// @Summary Set user role +// @Description Assign or update a user's role +// @Tags Users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "User ID" +// @Param request body dto.SetRoleRequest true "Set Role Request" +// @Success 200 {object} dto.UserRoleResponse +// @Failure 400 {object} utils.ErrorResponse +// @Router /api/users/{id}/role [put] +func (ctrl *UserController) SetUserRole(c *fiber.Ctx) error { + var req dto.SetRoleRequest + if err := c.BodyParser(&req); err != nil { + return utils.Error(c, http.StatusBadRequest, "Invalid request body", "BadRequestException", nil) + } + + if req.Role == "" { + return utils.Error(c, http.StatusBadRequest, "Role is required", "BadRequestException", nil) + } + + userID, err := uuid.Parse(c.Params("id")) + if err != nil { + return utils.Error(c, http.StatusBadRequest, "Invalid user ID format", "InvalidUUID", nil) + } + + updatedUser, err := ctrl.userService.SetUserRole(context.Background(), userID, req.Role) + if err != nil { + return utils.Error(c, http.StatusBadRequest, err.Error(), "SetRoleException", nil) + } + + resp := dto.UserRoleResponse{ + ID: updatedUser.ID.String(), + Name: updatedUser.Name, + Email: updatedUser.Email, + Role: req.Role, + } + + return utils.Success(c, http.StatusOK, "User role assigned successfully", resp, nil) +} + + +// DeactivateUser godoc +// @Summary Deactivate user +// @Description Deactivate a user's account by ID +// @Tags Users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "User ID" +// @Success 200 {object} dto.UserStatusResponse +// @Failure 400 {object} utils.ErrorResponse +// @Router /api/users/{id}/deactivate [put] +func (ctrl *UserController) DeactivateUser(c *fiber.Ctx) error { + userID, err := uuid.Parse(c.Params("id")) + if err != nil { + return utils.Error(c, http.StatusBadRequest, "Invalid user ID format", "InvalidUUID", nil) + } + + deactivatedUser, err := ctrl.userService.DeactivateUser(context.Background(), userID) + if err != nil { + return utils.Error(c, http.StatusBadRequest, err.Error(), "DeactivateUserException", nil) + } + + resp := dto.UserStatusResponse{ + ID: deactivatedUser.ID.String(), + Name: deactivatedUser.Name, + Email: deactivatedUser.Email, + IsActive: deactivatedUser.IsActive, + } + + return utils.Success(c, http.StatusOK, "User deactivated successfully", resp, nil) +} + + +// ActivateUser godoc +// @Summary Activate user +// @Description Activate a previously deactivated user account +// @Tags Users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path string true "User ID" +// @Success 200 {object} dto.UserStatusResponse +// @Failure 400 {object} utils.ErrorResponse +// @Router /api/users/{id}/activate [put] +func (ctrl *UserController) ActivateUser(c *fiber.Ctx) error { + userID, err := uuid.Parse(c.Params("id")) + if err != nil { + return utils.Error(c, http.StatusBadRequest, "Invalid user ID format", "InvalidUUID", nil) + } + + activatedUser, err := ctrl.userService.ActivateUser(context.Background(), userID) + if err != nil { + return utils.Error(c, http.StatusBadRequest, err.Error(), "ActivateUserException", nil) + } + + resp := dto.UserStatusResponse{ + ID: activatedUser.ID.String(), + Name: activatedUser.Name, + Email: activatedUser.Email, + IsActive: activatedUser.IsActive, + } + + return utils.Success(c, http.StatusOK, "User activated successfully", resp, nil) +} + + +// Profile godoc +// @Summary Get user profile +// @Description Retrieve authenticated user's profile +// @Tags Users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} dto.UserProfileResponse +// @Failure 401 {object} utils.ErrorResponse +// @Failure 404 {object} utils.ErrorResponse +// @Router /api/users/profile [get] +func (ctrl *UserController) Profile(c *fiber.Ctx) error { + userID := c.Locals("user_id") + if userID == nil { + return utils.Error(c, http.StatusUnauthorized, "Unauthorized", "UnauthorizedException", nil) + } + + profile, err := ctrl.userService.GetProfile(context.Background(), userID.(string)) + if err != nil { + return utils.Error(c, http.StatusNotFound, "User not found", "NotFoundException", nil) + } + + var roles []string + for _, r := range profile.Roles { + roles = append(roles, string(r.Name)) + } + + resp := dto.UserProfileResponse{ + ID: profile.ID.String(), + Name: profile.Name, + Email: profile.Email, + IsActive: profile.IsActive, + Roles: roles, + } + + return utils.Success(c, http.StatusOK, "Profile fetched successfully", resp, nil) +} + +type UpdateProfileRequest struct { + Name string `json:"name"` + Email string `json:"email"` +} + +func (h *UserController) UpdateProfile(c *fiber.Ctx) error { + userIDStr := c.Locals("user_id") + if userIDStr == nil { + return utils.Error(c, http.StatusUnauthorized, "Unauthorized", "Unauthorized", nil) + } + + userID, err := uuid.Parse(userIDStr.(string)) + if err != nil { + return utils.Error(c, http.StatusUnauthorized, "Invalid user ID", "Unauthorized", nil) + } + + var req UpdateProfileRequest + if err := c.BodyParser(&req); err != nil { + return utils.Error(c, http.StatusBadRequest, "Invalid request body", "BadRequest", nil) + } + + ctx := context.Background() + updatedUser, err := h.userService.UpdateProfile(ctx, userID, req.Name, req.Email) + if err != nil { + return utils.Error(c, http.StatusInternalServerError, err.Error(), "InternalServerError", nil) + } + + return utils.Success(c, http.StatusOK, "Profile updated successfully", updatedUser, nil) } diff --git a/api/routes/feedback_routes.go b/api/routes/feedback_routes.go new file mode 100644 index 0000000..208df40 --- /dev/null +++ b/api/routes/feedback_routes.go @@ -0,0 +1,21 @@ +package routes + +import ( + "api-shiners/api/handlers" + "api-shiners/pkg/middleware" + + "github.com/gofiber/fiber/v2" +) + +func FeedbackRoutes(app *fiber.App, feedbackController *handlers.FeedbackController) { + api := app.Group("/api") + + api.Post("/feedback/questions",middleware.TeacherMiddleware, feedbackController.CreateQuestion) + api.Get("/feedback/questions", middleware.AuthMiddleware, feedbackController.GetAllQuestions) + + api.Post("/feedback/answers", middleware.AuthMiddleware, feedbackController.SubmitAnswer) + + api.Get("/feedback/teacher", middleware.TeacherMiddleware, feedbackController.GetQuestionsWithAnswersByTeacher) +} + + diff --git a/api/routes/user_routes.go b/api/routes/user_routes.go index 431ad9f..e7def26 100644 --- a/api/routes/user_routes.go +++ b/api/routes/user_routes.go @@ -11,4 +11,12 @@ func UserRoutes(app *fiber.App, userController *handlers.UserController) { api := app.Group("/api") api.Get("/users", middleware.AdminMiddleware, userController.GetAllUsers) api.Get("/users/:id", middleware.AuthMiddleware, userController.GetUserByID) + + api.Post("/users/:id/role", middleware.AdminMiddleware, userController.SetUserRole) + + api.Post("/users/:id/deactivate", middleware.AdminMiddleware, userController.DeactivateUser) + api.Post("/users/:id/activate", middleware.AdminMiddleware, userController.ActivateUser) + + api.Get("/profile", middleware.AuthMiddleware, userController.Profile) + api.Put("/profile", middleware.AuthMiddleware, userController.UpdateProfile) } \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 633dc49..618c6ab 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -17,7 +17,7 @@ const docTemplate = `{ "paths": { "/api/auth/forgot-password": { "post": { - "description": "Generate reset token and send it to user's email", + "description": "Generate reset token dan kirim ke email user", "consumes": [ "application/json" ], @@ -30,15 +30,12 @@ const docTemplate = `{ "summary": "Request password reset", "parameters": [ { - "description": "Email Request", + "description": "Forgot Password Request", "name": "request", "in": "body", "required": true, "schema": { - "type": "object", - "additionalProperties": { - "type": "string" - } + "$ref": "#/definitions/dto.ForgotPasswordRequest" } } ], @@ -46,15 +43,19 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/dto.GenericResponse" } }, "400": { "description": "Bad Request", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" } } } @@ -62,7 +63,7 @@ const docTemplate = `{ }, "/api/auth/login": { "post": { - "description": "Authenticate user and return JWT token", + "description": "Autentikasi user dan mendapatkan JWT token", "consumes": [ "application/json" ], @@ -80,7 +81,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/auth.LoginRequest" + "$ref": "#/definitions/dto.LoginRequest" } } ], @@ -88,15 +89,19 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/dto.LoginResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" } }, "401": { "description": "Unauthorized", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/utils.ErrorResponse" } } } @@ -109,7 +114,7 @@ const docTemplate = `{ "BearerAuth": [] } ], - "description": "Invalidate user token", + "description": "Mengakhiri sesi dan menonaktifkan token", "consumes": [ "application/json" ], @@ -124,15 +129,19 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/dto.GenericResponse" } }, "400": { "description": "Bad Request", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" } } } @@ -140,7 +149,7 @@ const docTemplate = `{ }, "/api/auth/register": { "post": { - "description": "Create a new user account", + "description": "Membuat akun user baru", "consumes": [ "application/json" ], @@ -158,7 +167,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/auth.RegisterRequest" + "$ref": "#/definitions/dto.RegisterRequest" } } ], @@ -166,15 +175,19 @@ const docTemplate = `{ "201": { "description": "Created", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/dto.RegisterResponse" } }, "400": { "description": "Bad Request", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" } } } @@ -182,7 +195,7 @@ const docTemplate = `{ }, "/api/auth/reset-password": { "post": { - "description": "Reset password using valid reset token", + "description": "Reset password menggunakan reset token yang valid", "consumes": [ "application/json" ], @@ -200,13 +213,202 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "type": "object", - "additionalProperties": { - "type": "string" + "$ref": "#/definitions/dto.ResetPasswordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.GenericResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/feedback/answers": { + "post": { + "description": "Mahasiswa mengirimkan jawaban feedback", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Feedback" + ], + "summary": "Submit feedback answer", + "parameters": [ + { + "description": "Answer payload", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SubmitAnswerRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/utils.SuccessResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/feedback/questions": { + "get": { + "description": "Mendapatkan semua pertanyaan feedback", + "produces": [ + "application/json" + ], + "tags": [ + "Feedback" + ], + "summary": "Get all feedback questions", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.FeedbackQuestion" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + }, + "post": { + "description": "Admin membuat pertanyaan feedback baru", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Feedback" + ], + "summary": "Create new feedback question", + "parameters": [ + { + "description": "Question payload", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateQuestionRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/entities.FeedbackQuestion" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/feedback/questions/answers": { + "get": { + "description": "Mendapatkan semua pertanyaan beserta jawaban mahasiswa", + "produces": [ + "application/json" + ], + "tags": [ + "Feedback" + ], + "summary": "Get all questions with answers", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.FeedbackQuestion" } } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } } + } + } + }, + "/api/health/database": { + "get": { + "description": "Check database connection status", + "produces": [ + "application/json" ], + "tags": [ + "Health" + ], + "summary": "Check service health", "responses": { "200": { "description": "OK", @@ -215,8 +417,8 @@ const docTemplate = `{ "additionalProperties": true } }, - "400": { - "description": "Bad Request", + "503": { + "description": "Service Unavailable", "schema": { "type": "object", "additionalProperties": true @@ -224,30 +426,725 @@ const docTemplate = `{ } } } - } - }, - "definitions": { - "auth.LoginRequest": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "password": { - "type": "string" + }, + "/api/health/redis": { + "get": { + "description": "Check database connection status", + "produces": [ + "application/json" + ], + "tags": [ + "Health" + ], + "summary": "Check service health", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "type": "object", + "additionalProperties": true + } + } } } }, - "auth.RegisterRequest": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "name": { - "type": "string" - }, - "password": { + "/api/users": { + "get": { + "description": "Retrieve a paginated list of all users", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get all users", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Items per page", + "name": "per_page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PaginatedUsersResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/users/profile": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve authenticated user's profile", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get user profile", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserProfileResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/users/{id}": { + "get": { + "description": "Retrieve user details by user ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get user by ID", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/users/{id}/activate": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Activate a previously deactivated user account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Activate user", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserStatusResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/users/{id}/deactivate": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Deactivate a user's account by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Deactivate user", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserStatusResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/users/{id}/role": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Assign or update a user's role", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Set user role", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Set Role Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SetRoleRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserRoleResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "dto.CreateQuestionRequest": { + "type": "object", + "properties": { + "question": { + "type": "string", + "example": "Apa pendapat Anda tentang pelatihan ini?" + } + } + }, + "dto.ForgotPasswordRequest": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "john@example.com" + } + } + }, + "dto.GenericResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Operation successful" + } + } + }, + "dto.LoginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "john@example.com" + }, + "password": { + "type": "string", + "example": "strongpassword123" + } + } + }, + "dto.LoginResponse": { + "type": "object", + "properties": { + "expires_in": { + "type": "string", + "example": "2025-10-18T15:04:05Z" + }, + "token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + }, + "token_type": { + "type": "string", + "example": "Bearer" + }, + "user": {} + } + }, + "dto.MetaResponse": { + "type": "object", + "properties": { + "page": { + "type": "integer", + "example": 1 + }, + "per_page": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 100 + } + } + }, + "dto.PaginatedUsersResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.UserResponse" + } + }, + "meta": { + "$ref": "#/definitions/dto.MetaResponse" + } + } + }, + "dto.RegisterRequest": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "john@example.com" + }, + "name": { + "type": "string", + "example": "John Doe" + }, + "password": { + "type": "string", + "example": "strongpassword123" + } + } + }, + "dto.RegisterResponse": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "john@example.com" + }, + "id": { + "type": "string", + "example": "a3b2c1d4-56ef-7890-gh12-ijk345lmn678" + }, + "name": { + "type": "string", + "example": "John Doe" + } + } + }, + "dto.ResetPasswordRequest": { + "type": "object", + "properties": { + "new_password": { + "type": "string", + "example": "newStrongPassword123" + }, + "token": { + "type": "string", + "example": "123456" + } + } + }, + "dto.SetRoleRequest": { + "type": "object", + "properties": { + "role": { + "type": "string", + "example": "ADMIN" + } + } + }, + "dto.SubmitAnswerRequest": { + "type": "object", + "properties": { + "answer": { + "type": "string", + "example": "Sangat bermanfaat dan jelas" + }, + "question_id": { + "type": "string", + "example": "b5a1c6c3-1234-4bcd-9123-a12b34cd56ef" + } + } + }, + "dto.UserProfileResponse": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "dto.UserResponse": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "role": { + "type": "string" + } + } + }, + "dto.UserRoleResponse": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "role": { + "type": "string" + } + } + }, + "dto.UserStatusResponse": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + } + } + }, + "entities.FeedbackAnswer": { + "type": "object", + "properties": { + "answer": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "question_id": { + "type": "string" + }, + "student": { + "$ref": "#/definitions/entities.User" + }, + "student_id": { + "type": "string" + } + } + }, + "entities.FeedbackQuestion": { + "type": "object", + "properties": { + "answers": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.FeedbackAnswer" + } + }, + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "id": { + "type": "string" + }, + "question": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "entities.Role": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "$ref": "#/definitions/entities.RoleName" + }, + "updated_at": { + "type": "string" + } + } + }, + "entities.RoleName": { + "type": "string", + "enum": [ + "ADMIN", + "TEACHER", + "STUDENT" + ], + "x-enum-varnames": [ + "ADMIN", + "TEACHER", + "STUDENT" + ] + }, + "entities.User": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.Role" + } + }, + "updated_at": { + "type": "string" + } + } + }, + "utils.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/utils.FieldError" + } + }, + "message": { + "type": "string" + }, + "path": { + "type": "string" + }, + "statusCode": { + "type": "integer" + }, + "success": { + "type": "boolean" + }, + "timestamp": { + "type": "string" + } + } + }, + "utils.FieldError": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + }, + "messages": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "utils.Meta": { + "type": "object", + "properties": { + "page": { + "type": "integer" + }, + "per_page": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "utils.SuccessResponse": { + "type": "object", + "properties": { + "data": {}, + "message": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/utils.Meta" + }, + "path": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "timestamp": { "type": "string" } } diff --git a/docs/swagger.json b/docs/swagger.json index 9a11c87..3a410e3 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -14,7 +14,7 @@ "paths": { "/api/auth/forgot-password": { "post": { - "description": "Generate reset token and send it to user's email", + "description": "Generate reset token dan kirim ke email user", "consumes": [ "application/json" ], @@ -27,15 +27,12 @@ "summary": "Request password reset", "parameters": [ { - "description": "Email Request", + "description": "Forgot Password Request", "name": "request", "in": "body", "required": true, "schema": { - "type": "object", - "additionalProperties": { - "type": "string" - } + "$ref": "#/definitions/dto.ForgotPasswordRequest" } } ], @@ -43,15 +40,19 @@ "200": { "description": "OK", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/dto.GenericResponse" } }, "400": { "description": "Bad Request", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" } } } @@ -59,7 +60,7 @@ }, "/api/auth/login": { "post": { - "description": "Authenticate user and return JWT token", + "description": "Autentikasi user dan mendapatkan JWT token", "consumes": [ "application/json" ], @@ -77,7 +78,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/auth.LoginRequest" + "$ref": "#/definitions/dto.LoginRequest" } } ], @@ -85,15 +86,19 @@ "200": { "description": "OK", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/dto.LoginResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" } }, "401": { "description": "Unauthorized", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/utils.ErrorResponse" } } } @@ -106,7 +111,7 @@ "BearerAuth": [] } ], - "description": "Invalidate user token", + "description": "Mengakhiri sesi dan menonaktifkan token", "consumes": [ "application/json" ], @@ -121,15 +126,19 @@ "200": { "description": "OK", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/dto.GenericResponse" } }, "400": { "description": "Bad Request", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" } } } @@ -137,7 +146,7 @@ }, "/api/auth/register": { "post": { - "description": "Create a new user account", + "description": "Membuat akun user baru", "consumes": [ "application/json" ], @@ -155,7 +164,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/auth.RegisterRequest" + "$ref": "#/definitions/dto.RegisterRequest" } } ], @@ -163,15 +172,19 @@ "201": { "description": "Created", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/dto.RegisterResponse" } }, "400": { "description": "Bad Request", "schema": { - "type": "object", - "additionalProperties": true + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" } } } @@ -179,7 +192,7 @@ }, "/api/auth/reset-password": { "post": { - "description": "Reset password using valid reset token", + "description": "Reset password menggunakan reset token yang valid", "consumes": [ "application/json" ], @@ -197,13 +210,202 @@ "in": "body", "required": true, "schema": { - "type": "object", - "additionalProperties": { - "type": "string" + "$ref": "#/definitions/dto.ResetPasswordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.GenericResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/feedback/answers": { + "post": { + "description": "Mahasiswa mengirimkan jawaban feedback", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Feedback" + ], + "summary": "Submit feedback answer", + "parameters": [ + { + "description": "Answer payload", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SubmitAnswerRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/utils.SuccessResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/feedback/questions": { + "get": { + "description": "Mendapatkan semua pertanyaan feedback", + "produces": [ + "application/json" + ], + "tags": [ + "Feedback" + ], + "summary": "Get all feedback questions", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.FeedbackQuestion" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + }, + "post": { + "description": "Admin membuat pertanyaan feedback baru", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Feedback" + ], + "summary": "Create new feedback question", + "parameters": [ + { + "description": "Question payload", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CreateQuestionRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/entities.FeedbackQuestion" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/feedback/questions/answers": { + "get": { + "description": "Mendapatkan semua pertanyaan beserta jawaban mahasiswa", + "produces": [ + "application/json" + ], + "tags": [ + "Feedback" + ], + "summary": "Get all questions with answers", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.FeedbackQuestion" } } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } } + } + } + }, + "/api/health/database": { + "get": { + "description": "Check database connection status", + "produces": [ + "application/json" ], + "tags": [ + "Health" + ], + "summary": "Check service health", "responses": { "200": { "description": "OK", @@ -212,8 +414,8 @@ "additionalProperties": true } }, - "400": { - "description": "Bad Request", + "503": { + "description": "Service Unavailable", "schema": { "type": "object", "additionalProperties": true @@ -221,30 +423,725 @@ } } } - } - }, - "definitions": { - "auth.LoginRequest": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "password": { - "type": "string" + }, + "/api/health/redis": { + "get": { + "description": "Check database connection status", + "produces": [ + "application/json" + ], + "tags": [ + "Health" + ], + "summary": "Check service health", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "type": "object", + "additionalProperties": true + } + } } } }, - "auth.RegisterRequest": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "name": { - "type": "string" - }, - "password": { + "/api/users": { + "get": { + "description": "Retrieve a paginated list of all users", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get all users", + "parameters": [ + { + "type": "integer", + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "Items per page", + "name": "per_page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PaginatedUsersResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/users/profile": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Retrieve authenticated user's profile", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get user profile", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserProfileResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/users/{id}": { + "get": { + "description": "Retrieve user details by user ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get user by ID", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/users/{id}/activate": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Activate a previously deactivated user account", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Activate user", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserStatusResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/users/{id}/deactivate": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Deactivate a user's account by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Deactivate user", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserStatusResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + }, + "/api/users/{id}/role": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Assign or update a user's role", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Set user role", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Set Role Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SetRoleRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserRoleResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/utils.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "dto.CreateQuestionRequest": { + "type": "object", + "properties": { + "question": { + "type": "string", + "example": "Apa pendapat Anda tentang pelatihan ini?" + } + } + }, + "dto.ForgotPasswordRequest": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "john@example.com" + } + } + }, + "dto.GenericResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Operation successful" + } + } + }, + "dto.LoginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "john@example.com" + }, + "password": { + "type": "string", + "example": "strongpassword123" + } + } + }, + "dto.LoginResponse": { + "type": "object", + "properties": { + "expires_in": { + "type": "string", + "example": "2025-10-18T15:04:05Z" + }, + "token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" + }, + "token_type": { + "type": "string", + "example": "Bearer" + }, + "user": {} + } + }, + "dto.MetaResponse": { + "type": "object", + "properties": { + "page": { + "type": "integer", + "example": 1 + }, + "per_page": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 100 + } + } + }, + "dto.PaginatedUsersResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.UserResponse" + } + }, + "meta": { + "$ref": "#/definitions/dto.MetaResponse" + } + } + }, + "dto.RegisterRequest": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "john@example.com" + }, + "name": { + "type": "string", + "example": "John Doe" + }, + "password": { + "type": "string", + "example": "strongpassword123" + } + } + }, + "dto.RegisterResponse": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "john@example.com" + }, + "id": { + "type": "string", + "example": "a3b2c1d4-56ef-7890-gh12-ijk345lmn678" + }, + "name": { + "type": "string", + "example": "John Doe" + } + } + }, + "dto.ResetPasswordRequest": { + "type": "object", + "properties": { + "new_password": { + "type": "string", + "example": "newStrongPassword123" + }, + "token": { + "type": "string", + "example": "123456" + } + } + }, + "dto.SetRoleRequest": { + "type": "object", + "properties": { + "role": { + "type": "string", + "example": "ADMIN" + } + } + }, + "dto.SubmitAnswerRequest": { + "type": "object", + "properties": { + "answer": { + "type": "string", + "example": "Sangat bermanfaat dan jelas" + }, + "question_id": { + "type": "string", + "example": "b5a1c6c3-1234-4bcd-9123-a12b34cd56ef" + } + } + }, + "dto.UserProfileResponse": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "dto.UserResponse": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "role": { + "type": "string" + } + } + }, + "dto.UserRoleResponse": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "role": { + "type": "string" + } + } + }, + "dto.UserStatusResponse": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + } + } + }, + "entities.FeedbackAnswer": { + "type": "object", + "properties": { + "answer": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "question_id": { + "type": "string" + }, + "student": { + "$ref": "#/definitions/entities.User" + }, + "student_id": { + "type": "string" + } + } + }, + "entities.FeedbackQuestion": { + "type": "object", + "properties": { + "answers": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.FeedbackAnswer" + } + }, + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "id": { + "type": "string" + }, + "question": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "entities.Role": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "$ref": "#/definitions/entities.RoleName" + }, + "updated_at": { + "type": "string" + } + } + }, + "entities.RoleName": { + "type": "string", + "enum": [ + "ADMIN", + "TEACHER", + "STUDENT" + ], + "x-enum-varnames": [ + "ADMIN", + "TEACHER", + "STUDENT" + ] + }, + "entities.User": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.Role" + } + }, + "updated_at": { + "type": "string" + } + } + }, + "utils.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/utils.FieldError" + } + }, + "message": { + "type": "string" + }, + "path": { + "type": "string" + }, + "statusCode": { + "type": "integer" + }, + "success": { + "type": "boolean" + }, + "timestamp": { + "type": "string" + } + } + }, + "utils.FieldError": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + }, + "messages": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "utils.Meta": { + "type": "object", + "properties": { + "page": { + "type": "integer" + }, + "per_page": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "utils.SuccessResponse": { + "type": "object", + "properties": { + "data": {}, + "message": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/utils.Meta" + }, + "path": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "timestamp": { "type": "string" } } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 6739312..5244e77 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,19 +1,289 @@ basePath: / definitions: - auth.LoginRequest: + dto.CreateQuestionRequest: + properties: + question: + example: Apa pendapat Anda tentang pelatihan ini? + type: string + type: object + dto.ForgotPasswordRequest: properties: email: + example: john@example.com + type: string + type: object + dto.GenericResponse: + properties: + message: + example: Operation successful + type: string + type: object + dto.LoginRequest: + properties: + email: + example: john@example.com type: string password: + example: strongpassword123 type: string type: object - auth.RegisterRequest: + dto.LoginResponse: + properties: + expires_in: + example: "2025-10-18T15:04:05Z" + type: string + token: + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 + type: string + token_type: + example: Bearer + type: string + user: {} + type: object + dto.MetaResponse: + properties: + page: + example: 1 + type: integer + per_page: + example: 10 + type: integer + total: + example: 100 + type: integer + type: object + dto.PaginatedUsersResponse: + properties: + data: + items: + $ref: '#/definitions/dto.UserResponse' + type: array + meta: + $ref: '#/definitions/dto.MetaResponse' + type: object + dto.RegisterRequest: properties: email: + example: john@example.com type: string name: + example: John Doe type: string password: + example: strongpassword123 + type: string + type: object + dto.RegisterResponse: + properties: + email: + example: john@example.com + type: string + id: + example: a3b2c1d4-56ef-7890-gh12-ijk345lmn678 + type: string + name: + example: John Doe + type: string + type: object + dto.ResetPasswordRequest: + properties: + new_password: + example: newStrongPassword123 + type: string + token: + example: "123456" + type: string + type: object + dto.SetRoleRequest: + properties: + role: + example: ADMIN + type: string + type: object + dto.SubmitAnswerRequest: + properties: + answer: + example: Sangat bermanfaat dan jelas + type: string + question_id: + example: b5a1c6c3-1234-4bcd-9123-a12b34cd56ef + type: string + type: object + dto.UserProfileResponse: + properties: + email: + type: string + id: + type: string + is_active: + type: boolean + name: + type: string + roles: + items: + type: string + type: array + type: object + dto.UserResponse: + properties: + email: + type: string + id: + type: string + is_active: + type: boolean + name: + type: string + role: + type: string + type: object + dto.UserRoleResponse: + properties: + email: + type: string + id: + type: string + name: + type: string + role: + type: string + type: object + dto.UserStatusResponse: + properties: + email: + type: string + id: + type: string + is_active: + type: boolean + name: + type: string + type: object + entities.FeedbackAnswer: + properties: + answer: + type: string + created_at: + type: string + id: + type: string + question_id: + type: string + student: + $ref: '#/definitions/entities.User' + student_id: + type: string + type: object + entities.FeedbackQuestion: + properties: + answers: + items: + $ref: '#/definitions/entities.FeedbackAnswer' + type: array + created_at: + type: string + created_by: + type: string + id: + type: string + question: + type: string + updated_at: + type: string + type: object + entities.Role: + properties: + created_at: + type: string + description: + type: string + id: + type: string + name: + $ref: '#/definitions/entities.RoleName' + updated_at: + type: string + type: object + entities.RoleName: + enum: + - ADMIN + - TEACHER + - STUDENT + type: string + x-enum-varnames: + - ADMIN + - TEACHER + - STUDENT + entities.User: + properties: + created_at: + type: string + email: + type: string + id: + type: string + is_active: + type: boolean + name: + type: string + roles: + items: + $ref: '#/definitions/entities.Role' + type: array + updated_at: + type: string + type: object + utils.ErrorResponse: + properties: + error: + type: string + errors: + items: + $ref: '#/definitions/utils.FieldError' + type: array + message: + type: string + path: + type: string + statusCode: + type: integer + success: + type: boolean + timestamp: + type: string + type: object + utils.FieldError: + properties: + field: + type: string + message: + type: string + messages: + items: + type: string + type: array + type: object + utils.Meta: + properties: + page: + type: integer + per_page: + type: integer + total: + type: integer + type: object + utils.SuccessResponse: + properties: + data: {} + message: + type: string + meta: + $ref: '#/definitions/utils.Meta' + path: + type: string + status: + type: integer + timestamp: type: string type: object host: localhost:3000 @@ -27,29 +297,29 @@ paths: post: consumes: - application/json - description: Generate reset token and send it to user's email + description: Generate reset token dan kirim ke email user parameters: - - description: Email Request + - description: Forgot Password Request in: body name: request required: true schema: - additionalProperties: - type: string - type: object + $ref: '#/definitions/dto.ForgotPasswordRequest' produces: - application/json responses: "200": description: OK schema: - additionalProperties: true - type: object + $ref: '#/definitions/dto.GenericResponse' "400": description: Bad Request schema: - additionalProperties: true - type: object + $ref: '#/definitions/utils.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.ErrorResponse' summary: Request password reset tags: - Auth @@ -57,27 +327,29 @@ paths: post: consumes: - application/json - description: Authenticate user and return JWT token + description: Autentikasi user dan mendapatkan JWT token parameters: - description: Login Request in: body name: request required: true schema: - $ref: '#/definitions/auth.LoginRequest' + $ref: '#/definitions/dto.LoginRequest' produces: - application/json responses: "200": description: OK schema: - additionalProperties: true - type: object + $ref: '#/definitions/dto.LoginResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.ErrorResponse' "401": description: Unauthorized schema: - additionalProperties: true - type: object + $ref: '#/definitions/utils.ErrorResponse' summary: Login user tags: - Auth @@ -85,20 +357,22 @@ paths: post: consumes: - application/json - description: Invalidate user token + description: Mengakhiri sesi dan menonaktifkan token produces: - application/json responses: "200": description: OK schema: - additionalProperties: true - type: object + $ref: '#/definitions/dto.GenericResponse' "400": description: Bad Request schema: - additionalProperties: true - type: object + $ref: '#/definitions/utils.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/utils.ErrorResponse' security: - BearerAuth: [] summary: Logout user @@ -108,27 +382,29 @@ paths: post: consumes: - application/json - description: Create a new user account + description: Membuat akun user baru parameters: - description: Register Request in: body name: request required: true schema: - $ref: '#/definitions/auth.RegisterRequest' + $ref: '#/definitions/dto.RegisterRequest' produces: - application/json responses: "201": description: Created schema: - additionalProperties: true - type: object + $ref: '#/definitions/dto.RegisterResponse' "400": description: Bad Request schema: - additionalProperties: true - type: object + $ref: '#/definitions/utils.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.ErrorResponse' summary: Register a new user tags: - Auth @@ -136,32 +412,344 @@ paths: post: consumes: - application/json - description: Reset password using valid reset token + description: Reset password menggunakan reset token yang valid parameters: - description: Reset Password Request in: body name: request required: true schema: - additionalProperties: - type: string - type: object + $ref: '#/definitions/dto.ResetPasswordRequest' produces: - application/json responses: "200": description: OK schema: - additionalProperties: true - type: object + $ref: '#/definitions/dto.GenericResponse' "400": description: Bad Request schema: - additionalProperties: true - type: object + $ref: '#/definitions/utils.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.ErrorResponse' summary: Reset user password tags: - Auth + /api/feedback/answers: + post: + consumes: + - application/json + description: Mahasiswa mengirimkan jawaban feedback + parameters: + - description: Answer payload + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.SubmitAnswerRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/utils.SuccessResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/utils.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.ErrorResponse' + summary: Submit feedback answer + tags: + - Feedback + /api/feedback/questions: + get: + description: Mendapatkan semua pertanyaan feedback + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/entities.FeedbackQuestion' + type: array + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.ErrorResponse' + summary: Get all feedback questions + tags: + - Feedback + post: + consumes: + - application/json + description: Admin membuat pertanyaan feedback baru + parameters: + - description: Question payload + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.CreateQuestionRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/entities.FeedbackQuestion' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.ErrorResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/utils.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.ErrorResponse' + summary: Create new feedback question + tags: + - Feedback + /api/feedback/questions/answers: + get: + description: Mendapatkan semua pertanyaan beserta jawaban mahasiswa + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/entities.FeedbackQuestion' + type: array + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.ErrorResponse' + summary: Get all questions with answers + tags: + - Feedback + /api/health/database: + get: + description: Check database connection status + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + "503": + description: Service Unavailable + schema: + additionalProperties: true + type: object + summary: Check service health + tags: + - Health + /api/health/redis: + get: + description: Check database connection status + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + "503": + description: Service Unavailable + schema: + additionalProperties: true + type: object + summary: Check service health + tags: + - Health + /api/users: + get: + consumes: + - application/json + description: Retrieve a paginated list of all users + parameters: + - description: Page number + in: query + name: page + type: integer + - description: Items per page + in: query + name: per_page + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.PaginatedUsersResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/utils.ErrorResponse' + summary: Get all users + tags: + - Users + /api/users/{id}: + get: + consumes: + - application/json + description: Retrieve user details by user ID + parameters: + - description: User ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.UserResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.ErrorResponse' + summary: Get user by ID + tags: + - Users + /api/users/{id}/activate: + put: + consumes: + - application/json + description: Activate a previously deactivated user account + parameters: + - description: User ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.UserStatusResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.ErrorResponse' + security: + - BearerAuth: [] + summary: Activate user + tags: + - Users + /api/users/{id}/deactivate: + put: + consumes: + - application/json + description: Deactivate a user's account by ID + parameters: + - description: User ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.UserStatusResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.ErrorResponse' + security: + - BearerAuth: [] + summary: Deactivate user + tags: + - Users + /api/users/{id}/role: + put: + consumes: + - application/json + description: Assign or update a user's role + parameters: + - description: User ID + in: path + name: id + required: true + type: string + - description: Set Role Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.SetRoleRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.UserRoleResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/utils.ErrorResponse' + security: + - BearerAuth: [] + summary: Set user role + tags: + - Users + /api/users/profile: + get: + consumes: + - application/json + description: Retrieve authenticated user's profile + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.UserProfileResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/utils.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/utils.ErrorResponse' + security: + - BearerAuth: [] + summary: Get user profile + tags: + - Users schemes: - http securityDefinitions: diff --git a/index.html b/index.html new file mode 100644 index 0000000..d6aed36 --- /dev/null +++ b/index.html @@ -0,0 +1,61 @@ + + +
+ + +