package response import ( "math" "net/http" "cardverse/internal/pkg/logger" "github.com/gin-gonic/gin" ) // Response adalah struktur baku untuk seluruh response API type Response struct { Message string `json:"message"` Data interface{} `json:"data,omitempty"` Meta interface{} `json:"meta,omitempty"` Error interface{} `json:"error,omitempty"` } // Pagination berisi metadata pagination yang dikirim di field "meta" type Pagination struct { Page int `json:"page"` Limit int `json:"limit"` TotalData int64 `json:"total_data"` TotalPages int `json:"total_pages"` HasNext bool `json:"has_next"` HasPrev bool `json:"has_prev"` } // NewPagination menghitung metadata pagination dari page, limit, dan total data. // Dipakai di service/handler setelah query COUNT(*) ke database. func NewPagination(page, limit int, totalData int64) Pagination { if page < 1 { page = 1 } if limit < 1 { limit = 10 } totalPages := int(math.Ceil(float64(totalData) / float64(limit))) if totalPages < 1 { totalPages = 1 } return Pagination{ Page: page, Limit: limit, TotalData: totalData, TotalPages: totalPages, HasNext: page < totalPages, HasPrev: page > 1, } } func Success(c *gin.Context, statusCode int, message string, data interface{}) { c.JSON(statusCode, Response{ Message: message, Data: data, }) } func SuccessWithPagination(c *gin.Context, statusCode int, message string, data interface{}, pagination Pagination) { c.JSON(statusCode, Response{ Message: message, Data: data, Meta: pagination, }) } var defaultErrorMessages = map[int]string{ http.StatusUnauthorized: "Anda tidak memiliki akses, silakan login kembali", http.StatusForbidden: "Anda tidak memiliki izin untuk melakukan aksi ini", http.StatusNotFound: "Data yang diminta tidak ditemukan", http.StatusConflict: "Data sudah ada atau bertentangan dengan data yang tersimpan", http.StatusUnprocessableEntity: "Data yang dikirim tidak dapat diproses", http.StatusTooManyRequests: "Terlalu banyak request, silakan coba lagi nanti", http.StatusInternalServerError: "Terjadi kesalahan pada server, silakan coba lagi nanti", http.StatusBadGateway: "Layanan sedang tidak tersedia, silakan coba lagi nanti", http.StatusServiceUnavailable: "Layanan sedang tidak tersedia, silakan coba lagi nanti", } const fallbackErrorMessage = "Terjadi kesalahan, silakan coba lagi nanti" // Error mengirim response gagal ke client. // // - Untuk 400 Bad Request: message & err (data) dikirim apa adanya ke client — biasanya // cuma detail validasi input, aman untuk membantu client memperbaiki request-nya. // - Untuk status lain (401, 403, 404, 409, 500, dst): client HANYA menerima pesan generik // dari defaultErrorMessages, TIDAK menerima err/message asli sama sekali. Detail asli // (message & err yang dikirim caller) dicatat lewat logger supaya tetap bisa di-debug // dari log server tanpa membocorkannya ke response API. func Error(c *gin.Context, statusCode int, message string, err interface{}) { if statusCode == http.StatusBadRequest { c.JSON(statusCode, Response{ Message: message, Error: err, }) return } logFields := logger.Fields{ "method": c.Request.Method, "path": c.Request.URL.Path, "status": statusCode, "ip": c.ClientIP(), "detail": message, } if err != nil { logFields["error"] = err } if statusCode >= http.StatusInternalServerError { logger.Error("request gagal diproses", logFields) } else { logger.Warn("request ditolak", logFields) } clientMessage := message if clientMessage == "" { if msg, ok := defaultErrorMessages[statusCode]; ok { clientMessage = msg } else { clientMessage = fallbackErrorMessage } } c.JSON(statusCode, Response{ Message: clientMessage, }) }