Files
cardverse-be/internal/pkg/response/response.go

131 lines
3.8 KiB
Go
Raw Normal View History

2026-07-30 09:51:25 +07:00
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,
})
}