131 lines
3.8 KiB
Go
131 lines
3.8 KiB
Go
|
|
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,
|
||
|
|
})
|
||
|
|
}
|