// internal/controllers/claim_controller.go package controllers import ( "lost-and-found/internal/models" "lost-and-found/internal/services" "lost-and-found/internal/utils" "net/http" "strconv" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type ClaimController struct { claimService *services.ClaimService verificationService *services.VerificationService } func NewClaimController(db *gorm.DB) *ClaimController { return &ClaimController{ claimService: services.NewClaimService(db), verificationService: services.NewVerificationService(db), } } // GetAllClaims gets all claims // GET /api/claims func (c *ClaimController) GetAllClaims(ctx *gin.Context) { page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10")) status := ctx.Query("status") var itemID, userID *uint if itemIDStr := ctx.Query("item_id"); itemIDStr != "" { id, _ := strconv.ParseUint(itemIDStr, 10, 32) itemID = new(uint) *itemID = uint(id) } // If regular user, only show their claims if userObj, exists := ctx.Get("user"); exists { user := userObj.(*models.User) if user.IsUser() { userID = &user.ID } } claims, total, err := c.claimService.GetAllClaims(page, limit, status, itemID, userID) if err != nil { utils.ErrorResponse(ctx, http.StatusInternalServerError, "Failed to get claims", err.Error()) return } // Pastikan claims selalu berupa array, bukan null if claims == nil { claims = []models.ClaimResponse{} } utils.SendPaginatedResponse(ctx, http.StatusOK, "Claims retrieved", claims, total, page, limit) } // POST /api/user/claims/:id/respond func (c *ClaimController) UserApproveClaim(ctx *gin.Context) { userObj, exists := ctx.Get("user") if !exists { utils.ErrorResponse(ctx, http.StatusUnauthorized, "Unauthorized", "User not found") return } user := userObj.(*models.User) claimID, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid claim ID", err.Error()) return } var req struct { Action string `json:"action" binding:"required"` // 'approve' or 'reject' } if err := ctx.ShouldBindJSON(&req); err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid request body", err.Error()) return } // Validasi Action if req.Action != "approve" && req.Action != "reject" { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid action", "Action must be 'approve' or 'reject'") return } // Logic approve/reject khusus user err = c.claimService.ProcessUserDecision(user.ID, uint(claimID), req.Action) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Gagal memproses keputusan", err.Error()) return } message := "Klaim berhasil disetujui" if req.Action == "reject" { message = "Klaim berhasil ditolak" } utils.SuccessResponse(ctx, http.StatusOK, message, nil) } // GetClaimByID gets claim by ID // GET /api/claims/:id func (c *ClaimController) GetClaimByID(ctx *gin.Context) { id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid claim ID", err.Error()) return } isManager := false if userObj, exists := ctx.Get("user"); exists { user := userObj.(*models.User) isManager = user.IsManager() || user.IsAdmin() } // Jika Manager, paksa hitung similarity dulu sebelum ambil data if isManager { _, err := c.verificationService.VerifyClaimDescription(uint(id)) if err != nil { // Log error tapi jangan stop flow } } claim, err := c.claimService.GetClaimByID(uint(id), isManager) if err != nil { utils.ErrorResponse(ctx, http.StatusNotFound, "Claim not found", err.Error()) return } utils.SuccessResponse(ctx, http.StatusOK, "Claim retrieved", claim) } func (c *ClaimController) UserConfirmCompletion(ctx *gin.Context) { userObj, _ := ctx.Get("user") user := userObj.(*models.User) claimID, _ := strconv.ParseUint(ctx.Param("id"), 10, 32) err := c.claimService.UserConfirmCompletion(user.ID, uint(claimID)) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Gagal menyelesaikan kasus", err.Error()) return } utils.SuccessResponse(ctx, http.StatusOK, "Kasus selesai. Barang telah diterima.", nil) } // CreateClaim creates a new claim // POST /api/claims func (c *ClaimController) CreateClaim(ctx *gin.Context) { userObj, _ := ctx.Get("user") user := userObj.(*models.User) var req services.CreateClaimRequest if err := ctx.ShouldBindJSON(&req); err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid request data", err.Error()) return } ipAddress := ctx.ClientIP() userAgent := ctx.Request.UserAgent() claim, err := c.claimService.CreateClaim(user.ID, req, ipAddress, userAgent) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Failed to create claim", err.Error()) return } utils.SuccessResponse(ctx, http.StatusCreated, "Claim created", claim.ToResponse()) } // VerifyClaim verifies a claim (manager only) // POST /api/claims/:id/verify func (c *ClaimController) VerifyClaim(ctx *gin.Context) { managerObj, _ := ctx.Get("user") manager := managerObj.(*models.User) claimID, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid claim ID", err.Error()) return } var req services.VerifyClaimRequest if err := ctx.ShouldBindJSON(&req); err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid request data", err.Error()) return } // Auto-verify description similarity verification, err := c.verificationService.VerifyClaimDescription(uint(claimID)) if err != nil { utils.ErrorResponse(ctx, http.StatusInternalServerError, "Verification failed", err.Error()) return } ipAddress := ctx.ClientIP() userAgent := ctx.Request.UserAgent() // Verify the claim if err := c.claimService.VerifyClaim( manager.ID, uint(claimID), req, verification.SimilarityScore, stringSliceToString(verification.MatchedKeywords), ipAddress, userAgent, ); err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Failed to verify claim", err.Error()) return } utils.SuccessResponse(ctx, http.StatusOK, "Claim verified", gin.H{ "verification": verification, }) } // GetClaimVerification gets verification data for a claim // GET /api/claims/:id/verification func (c *ClaimController) GetClaimVerification(ctx *gin.Context) { claimID, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid claim ID", err.Error()) return } verification, err := c.verificationService.VerifyClaimDescription(uint(claimID)) if err != nil { utils.ErrorResponse(ctx, http.StatusInternalServerError, "Verification failed", err.Error()) return } utils.SuccessResponse(ctx, http.StatusOK, "Verification retrieved", verification) } // CancelClaimApproval handles cancelling an approved claim // POST /api/claims/:id/cancel-approval // ✅ RENAMED: CancelApproval -> CancelClaimApproval (Matches routes.go) func (c *ClaimController) CancelClaimApproval(ctx *gin.Context) { managerObj, _ := ctx.Get("user") manager := managerObj.(*models.User) claimID, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid claim ID", err.Error()) return } if err := c.claimService.CancelClaimApproval(manager.ID, uint(claimID)); err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Failed to cancel approval", err.Error()) return } utils.SuccessResponse(ctx, http.StatusOK, "Approval cancelled successfully", nil) } // CloseCase handles case closure (Manager only) // POST /api/claims/:id/close // ✅ RENAMED: CloseClaim -> CloseCase (Matches routes.go) func (c *ClaimController) CloseCase(ctx *gin.Context) { // 1. Ambil User (Manager) dari context secara konsisten managerObj, exists := ctx.Get("user") if !exists { utils.ErrorResponse(ctx, http.StatusUnauthorized, "Unauthorized", "User not found") return } manager := managerObj.(*models.User) // 2. Ambil ID Klaim dari URL idStr := ctx.Param("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid ID format", err.Error()) return } // 3. Parse Body Request var req services.CloseCaseRequest if err := ctx.ShouldBindJSON(&req); err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid request data", err.Error()) return } // 4. Ambil Info Audit ipAddress := ctx.ClientIP() userAgent := ctx.Request.UserAgent() // 5. Panggil Service if err := c.claimService.CloseCase(manager.ID, uint(id), req, ipAddress, userAgent); err != nil { utils.ErrorResponse(ctx, http.StatusInternalServerError, "Failed to close case", err.Error()) return } utils.SuccessResponse(ctx, http.StatusOK, "Case closed successfully", nil) } // ReopenCase handles reopening a closed case // POST /api/claims/:id/reopen // ✅ RENAMED: ReopenClaim -> ReopenCase (Matches routes.go) func (c *ClaimController) ReopenCase(ctx *gin.Context) { managerObj, _ := ctx.Get("user") manager := managerObj.(*models.User) claimID, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid claim ID", err.Error()) return } var req services.ReopenCaseRequest if err := ctx.ShouldBindJSON(&req); err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid request data", err.Error()) return } ipAddress := ctx.ClientIP() userAgent := ctx.Request.UserAgent() if err := c.claimService.ReopenCase(manager.ID, uint(claimID), req, ipAddress, userAgent); err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Failed to reopen case", err.Error()) return } utils.SuccessResponse(ctx, http.StatusOK, "Case reopened successfully", nil) } // GetClaimsByUser gets claims by user // GET /api/user/claims func (c *ClaimController) GetClaimsByUser(ctx *gin.Context) { userObj, _ := ctx.Get("user") user := userObj.(*models.User) page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10")) claims, total, err := c.claimService.GetClaimsByUser(user.ID, page, limit) if err != nil { utils.ErrorResponse(ctx, http.StatusInternalServerError, "Failed to get claims", err.Error()) return } utils.SendPaginatedResponse(ctx, http.StatusOK, "Claims retrieved", claims, total, page, limit) } // UpdateClaim updates a claim // PUT /api/claims/:id func (c *ClaimController) UpdateClaim(ctx *gin.Context) { // Bisa Admin atau User pemilik klaim (dicek di middleware/service) userObj, _ := ctx.Get("user") user := userObj.(*models.User) claimID, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid claim ID", err.Error()) return } var req services.UpdateClaimRequest if err := ctx.ShouldBindJSON(&req); err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid request data", err.Error()) return } ipAddress := ctx.ClientIP() userAgent := ctx.Request.UserAgent() claim, err := c.claimService.UpdateClaim(user.ID, uint(claimID), req, ipAddress, userAgent) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Failed to update claim", err.Error()) return } utils.SuccessResponse(ctx, http.StatusOK, "Claim updated successfully", claim) } // DeleteClaim deletes a claim // DELETE /api/claims/:id func (c *ClaimController) DeleteClaim(ctx *gin.Context) { userObj, _ := ctx.Get("user") user := userObj.(*models.User) claimID, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Invalid claim ID", err.Error()) return } ipAddress := ctx.ClientIP() userAgent := ctx.Request.UserAgent() if err := c.claimService.DeleteClaim(user.ID, uint(claimID), ipAddress, userAgent); err != nil { utils.ErrorResponse(ctx, http.StatusBadRequest, "Failed to delete claim", err.Error()) return } utils.SuccessResponse(ctx, http.StatusOK, "Claim deleted", nil) } // Helper function to convert string slice to string func stringSliceToString(slice []string) string { if len(slice) == 0 { return "" } result := "" for i, s := range slice { if i > 0 { result += ", " } result += s } return result }