420 lines
13 KiB
Go
420 lines
13 KiB
Go
// 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
|
|
} |