05e7b50ec4
- Updated user-related API handlers to return user entities directly instead of using the ToResponse method, simplifying response structures. - Modified service methods to return UserResponse types instead of User, enhancing consistency in response formats. - Improved pagination handling in the response package by setting default values for sorting parameters, ensuring more predictable behavior in API responses.
223 lines
5.2 KiB
Go
223 lines
5.2 KiB
Go
package response
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/asaskevich/govalidator"
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
// APIResponse represents a standard API response structure
|
|
type APIResponse struct {
|
|
Success bool `json:"success"`
|
|
Message string `json:"message,omitempty"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
Error *APIError `json:"error,omitempty"`
|
|
Meta *Meta `json:"meta,omitempty"`
|
|
}
|
|
|
|
// APIError represents error details in API response
|
|
type APIError struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
Details string `json:"details,omitempty"`
|
|
}
|
|
|
|
// Meta represents metadata for paginated responses
|
|
type Meta struct {
|
|
Total int `json:"total"`
|
|
Limit int `json:"limit"`
|
|
Offset int `json:"offset"`
|
|
Page int `json:"page"`
|
|
Pages int `json:"pages"`
|
|
}
|
|
|
|
type Pagination struct {
|
|
Limit int `query:"limit" valid:"optional,range(1|100)" default:"20"`
|
|
Offset int `query:"offset" valid:"optional,min(0)" default:"0"`
|
|
SortBy string `query:"sort_by" valid:"optional"`
|
|
SortOrder string `query:"sort_order" valid:"optional,in(asc|desc)"`
|
|
}
|
|
|
|
// Success returns a successful response
|
|
func Success(c echo.Context, data interface{}, message string) error {
|
|
return c.JSON(http.StatusOK, APIResponse{
|
|
Success: true,
|
|
Message: message,
|
|
Data: data,
|
|
})
|
|
}
|
|
|
|
// SuccessWithMeta returns a successful response with metadata
|
|
func SuccessWithMeta(c echo.Context, data interface{}, meta *Meta, message string) error {
|
|
return c.JSON(http.StatusOK, APIResponse{
|
|
Success: true,
|
|
Message: message,
|
|
Data: data,
|
|
Meta: meta,
|
|
})
|
|
}
|
|
|
|
// Created returns a 201 Created response
|
|
func Created(c echo.Context, data interface{}, message string) error {
|
|
return c.JSON(http.StatusCreated, APIResponse{
|
|
Success: true,
|
|
Message: message,
|
|
Data: data,
|
|
})
|
|
}
|
|
|
|
// BadRequest returns a 400 Bad Request response
|
|
func BadRequest(c echo.Context, message, details string) error {
|
|
return c.JSON(http.StatusBadRequest, APIResponse{
|
|
Success: false,
|
|
Message: "Bad Request",
|
|
Error: &APIError{
|
|
Code: "BAD_REQUEST",
|
|
Message: message,
|
|
Details: details,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Unauthorized returns a 401 Unauthorized response
|
|
func Unauthorized(c echo.Context, message string) error {
|
|
return c.JSON(http.StatusUnauthorized, APIResponse{
|
|
Success: false,
|
|
Message: "Unauthorized",
|
|
Error: &APIError{
|
|
Code: "UNAUTHORIZED",
|
|
Message: message,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Forbidden returns a 403 Forbidden response
|
|
func Forbidden(c echo.Context, message string) error {
|
|
return c.JSON(http.StatusForbidden, APIResponse{
|
|
Success: false,
|
|
Message: "Forbidden",
|
|
Error: &APIError{
|
|
Code: "FORBIDDEN",
|
|
Message: message,
|
|
},
|
|
})
|
|
}
|
|
|
|
// NotFound returns a 404 Not Found response
|
|
func NotFound(c echo.Context, message string) error {
|
|
return c.JSON(http.StatusNotFound, APIResponse{
|
|
Success: false,
|
|
Message: "Not Found",
|
|
Error: &APIError{
|
|
Code: "NOT_FOUND",
|
|
Message: message,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Conflict returns a 409 Conflict response
|
|
func Conflict(c echo.Context, message string) error {
|
|
return c.JSON(http.StatusConflict, APIResponse{
|
|
Success: false,
|
|
Message: "Conflict",
|
|
Error: &APIError{
|
|
Code: "CONFLICT",
|
|
Message: message,
|
|
},
|
|
})
|
|
}
|
|
|
|
// ValidationError returns a 422 Unprocessable Entity response
|
|
func ValidationError(c echo.Context, message, details string) error {
|
|
return c.JSON(http.StatusUnprocessableEntity, APIResponse{
|
|
Success: false,
|
|
Message: "Validation Error",
|
|
Error: &APIError{
|
|
Code: "VALIDATION_ERROR",
|
|
Message: message,
|
|
Details: details,
|
|
},
|
|
})
|
|
}
|
|
|
|
// InternalServerError returns a 500 Internal Server Error response
|
|
func InternalServerError(c echo.Context, message string) error {
|
|
return c.JSON(http.StatusInternalServerError, APIResponse{
|
|
Success: false,
|
|
Message: "Internal Server Error",
|
|
Error: &APIError{
|
|
Code: "INTERNAL_SERVER_ERROR",
|
|
Message: message,
|
|
},
|
|
})
|
|
}
|
|
|
|
// ServiceUnavailable returns a 503 Service Unavailable response
|
|
func ServiceUnavailable(c echo.Context, message string) error {
|
|
return c.JSON(http.StatusServiceUnavailable, APIResponse{
|
|
Success: false,
|
|
Message: "Service Unavailable",
|
|
Error: &APIError{
|
|
Code: "SERVICE_UNAVAILABLE",
|
|
Message: message,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Parse parses the form and validates it
|
|
func Parse[T any](c echo.Context) (*T, error) {
|
|
var form T
|
|
if err := c.Bind(&form); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &form, nil
|
|
}
|
|
|
|
func NewPagination(c echo.Context) *Pagination {
|
|
limit, err := strconv.Atoi(c.QueryParam("limit"))
|
|
if err != nil {
|
|
limit = 20
|
|
}
|
|
|
|
offset, err := strconv.Atoi(c.QueryParam("offset"))
|
|
if err != nil {
|
|
offset = 0
|
|
}
|
|
|
|
sortBy := c.QueryParam("sort_by")
|
|
if sortBy == "" {
|
|
sortBy = "created_at"
|
|
}
|
|
|
|
sortOrder := c.QueryParam("sort_order")
|
|
if sortOrder == "" {
|
|
sortOrder = "desc"
|
|
}
|
|
|
|
return &Pagination{
|
|
Limit: limit,
|
|
Offset: offset,
|
|
SortBy: sortBy,
|
|
SortOrder: sortOrder,
|
|
}
|
|
}
|
|
|
|
// Pagination calculates pagination metadata
|
|
func (p *Pagination) Response(total int64) *Meta {
|
|
pages := (total + int64(p.Limit) - 1) / int64(p.Limit)
|
|
return &Meta{
|
|
Total: int(total),
|
|
Limit: p.Limit,
|
|
Offset: p.Offset,
|
|
Page: int(p.Offset/p.Limit) + 1,
|
|
Pages: int(pages),
|
|
}
|
|
}
|