refactor: remove repeating code etc
This commit is contained in:
@@ -3,8 +3,11 @@ package utils
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/timetracker/backend/internal/api/responses"
|
||||
"github.com/timetracker/backend/internal/types"
|
||||
)
|
||||
|
||||
@@ -43,14 +46,14 @@ func HandleGetAll[M any, D any](
|
||||
// Get entities from the database
|
||||
entities, err := getAllFn(c.Request.Context())
|
||||
if err != nil {
|
||||
InternalErrorResponse(c, fmt.Sprintf("Error retrieving %s: %s", entityName, err.Error()))
|
||||
responses.InternalErrorResponse(c, fmt.Sprintf("Error retrieving %s: %s", entityName, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to DTOs
|
||||
dtos := ConvertToDTO(entities, convertFn)
|
||||
|
||||
SuccessResponse(c, 200, dtos)
|
||||
responses.SuccessResponse(c, 200, dtos)
|
||||
}
|
||||
|
||||
// HandleGetByID is a generic function to handle GET entity by ID endpoints
|
||||
@@ -63,26 +66,26 @@ func HandleGetByID[M any, D any](
|
||||
// Parse ID from URL
|
||||
id, err := ParseID(c, "id")
|
||||
if err != nil {
|
||||
BadRequestResponse(c, fmt.Sprintf("Invalid %s ID format", entityName))
|
||||
responses.BadRequestResponse(c, fmt.Sprintf("Invalid %s ID format", entityName))
|
||||
return
|
||||
}
|
||||
|
||||
// Get entity from the database
|
||||
entity, err := getByIDFn(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
InternalErrorResponse(c, fmt.Sprintf("Error retrieving %s: %s", entityName, err.Error()))
|
||||
responses.InternalErrorResponse(c, fmt.Sprintf("Error retrieving %s: %s", entityName, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if entity == nil {
|
||||
NotFoundResponse(c, fmt.Sprintf("%s not found", entityName))
|
||||
responses.NotFoundResponse(c, fmt.Sprintf("%s not found", entityName))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to DTO
|
||||
dto := convertFn(entity)
|
||||
|
||||
SuccessResponse(c, 200, dto)
|
||||
responses.SuccessResponse(c, 200, dto)
|
||||
}
|
||||
|
||||
// HandleCreate is a generic function to handle POST entity endpoints
|
||||
@@ -95,21 +98,21 @@ func HandleCreate[C any, M any, D any](
|
||||
// Parse request body
|
||||
var createDTO C
|
||||
if err := BindJSON(c, &createDTO); err != nil {
|
||||
BadRequestResponse(c, err.Error())
|
||||
responses.BadRequestResponse(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Create entity in the database
|
||||
entity, err := createFn(c.Request.Context(), createDTO)
|
||||
if err != nil {
|
||||
InternalErrorResponse(c, fmt.Sprintf("Error creating %s: %s", entityName, err.Error()))
|
||||
responses.InternalErrorResponse(c, fmt.Sprintf("Error creating %s: %s", entityName, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to DTO
|
||||
dto := convertFn(entity)
|
||||
|
||||
SuccessResponse(c, 201, dto)
|
||||
responses.SuccessResponse(c, 201, dto)
|
||||
}
|
||||
|
||||
// HandleDelete is a generic function to handle DELETE entity endpoints
|
||||
@@ -121,16 +124,163 @@ func HandleDelete(
|
||||
// Parse ID from URL
|
||||
id, err := ParseID(c, "id")
|
||||
if err != nil {
|
||||
BadRequestResponse(c, fmt.Sprintf("Invalid %s ID format", entityName))
|
||||
responses.BadRequestResponse(c, fmt.Sprintf("Invalid %s ID format", entityName))
|
||||
return
|
||||
}
|
||||
|
||||
// Delete entity from the database
|
||||
err = deleteFn(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
InternalErrorResponse(c, fmt.Sprintf("Error deleting %s: %s", entityName, err.Error()))
|
||||
responses.InternalErrorResponse(c, fmt.Sprintf("Error deleting %s: %s", entityName, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
SuccessResponse(c, 204, nil)
|
||||
responses.SuccessResponse(c, 204, nil)
|
||||
}
|
||||
|
||||
// HandleUpdate is a generic function to handle PUT entity endpoints
|
||||
// It takes a prepareUpdateFn that handles parsing the ID, binding the JSON, and converting the DTO to a model update object
|
||||
func HandleUpdate[U any, M any, D any](
|
||||
c *gin.Context,
|
||||
updateFn func(ctx context.Context, update U) (*M, error),
|
||||
convertFn func(*M) D,
|
||||
prepareUpdateFn func(*gin.Context) (U, error),
|
||||
entityName string,
|
||||
) {
|
||||
// Prepare the update object (parse ID, bind JSON, convert DTO to model)
|
||||
update, err := prepareUpdateFn(c)
|
||||
if err != nil {
|
||||
// The prepareUpdateFn should handle setting the appropriate error response
|
||||
return
|
||||
}
|
||||
|
||||
// Update entity in the database
|
||||
entity, err := updateFn(c.Request.Context(), update)
|
||||
if err != nil {
|
||||
responses.InternalErrorResponse(c, fmt.Sprintf("Error updating %s: %s", entityName, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if entity == nil {
|
||||
responses.NotFoundResponse(c, fmt.Sprintf("%s not found", entityName))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to DTO
|
||||
dto := convertFn(entity)
|
||||
|
||||
responses.SuccessResponse(c, http.StatusOK, dto)
|
||||
}
|
||||
|
||||
// HandleGetByFilter is a generic function to handle GET entities by a filter parameter
|
||||
func HandleGetByFilter[M any, D any](
|
||||
c *gin.Context,
|
||||
getByFilterFn func(ctx context.Context, filterID types.ULID) ([]M, error),
|
||||
convertFn func(*M) D,
|
||||
entityName string,
|
||||
paramName string,
|
||||
) {
|
||||
// Parse filter ID from URL
|
||||
filterID, err := ParseID(c, paramName)
|
||||
if err != nil {
|
||||
responses.BadRequestResponse(c, fmt.Sprintf("Invalid %s ID format", paramName))
|
||||
return
|
||||
}
|
||||
|
||||
// Get entities from the database
|
||||
entities, err := getByFilterFn(c.Request.Context(), filterID)
|
||||
if err != nil {
|
||||
responses.InternalErrorResponse(c, fmt.Sprintf("Error retrieving %s: %s", entityName, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to DTOs
|
||||
dtos := ConvertToDTO(entities, convertFn)
|
||||
|
||||
responses.SuccessResponse(c, http.StatusOK, dtos)
|
||||
}
|
||||
|
||||
// HandleGetByUserID is a specialized function to handle GET entities by user ID
|
||||
func HandleGetByUserID[M any, D any](
|
||||
c *gin.Context,
|
||||
getByUserIDFn func(ctx context.Context, userID types.ULID) ([]M, error),
|
||||
convertFn func(*M) D,
|
||||
entityName string,
|
||||
) {
|
||||
// Get user ID from context (set by AuthMiddleware)
|
||||
userID, exists := c.Get("userID")
|
||||
if !exists {
|
||||
responses.UnauthorizedResponse(c, "User not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
userIDStr, ok := userID.(string)
|
||||
if !ok {
|
||||
responses.InternalErrorResponse(c, "Invalid user ID type in context")
|
||||
return
|
||||
}
|
||||
|
||||
parsedUserID, err := types.ULIDFromString(userIDStr)
|
||||
if err != nil {
|
||||
responses.InternalErrorResponse(c, fmt.Sprintf("Error parsing user ID: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Get entities from the database
|
||||
entities, err := getByUserIDFn(c.Request.Context(), parsedUserID)
|
||||
if err != nil {
|
||||
responses.InternalErrorResponse(c, fmt.Sprintf("Error retrieving %s: %s", entityName, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to DTOs
|
||||
dtos := ConvertToDTO(entities, convertFn)
|
||||
|
||||
responses.SuccessResponse(c, http.StatusOK, dtos)
|
||||
}
|
||||
|
||||
// HandleGetByDateRange is a specialized function to handle GET entities by date range
|
||||
func HandleGetByDateRange[M any, D any](
|
||||
c *gin.Context,
|
||||
getByDateRangeFn func(ctx context.Context, start, end time.Time) ([]M, error),
|
||||
convertFn func(*M) D,
|
||||
entityName string,
|
||||
) {
|
||||
// Parse date range from query parameters
|
||||
startStr := c.Query("start")
|
||||
endStr := c.Query("end")
|
||||
|
||||
if startStr == "" || endStr == "" {
|
||||
responses.BadRequestResponse(c, "Start and end dates are required")
|
||||
return
|
||||
}
|
||||
|
||||
start, err := time.Parse(time.RFC3339, startStr)
|
||||
if err != nil {
|
||||
responses.BadRequestResponse(c, "Invalid start date format. Use ISO 8601 format (e.g., 2023-01-01T00:00:00Z)")
|
||||
return
|
||||
}
|
||||
|
||||
end, err := time.Parse(time.RFC3339, endStr)
|
||||
if err != nil {
|
||||
responses.BadRequestResponse(c, "Invalid end date format. Use ISO 8601 format (e.g., 2023-01-01T00:00:00Z)")
|
||||
return
|
||||
}
|
||||
|
||||
if end.Before(start) {
|
||||
responses.BadRequestResponse(c, "End date cannot be before start date")
|
||||
return
|
||||
}
|
||||
|
||||
// Get entities from the database
|
||||
entities, err := getByDateRangeFn(c.Request.Context(), start, end)
|
||||
if err != nil {
|
||||
responses.InternalErrorResponse(c, fmt.Sprintf("Error retrieving %s: %s", entityName, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to DTOs
|
||||
dtos := ConvertToDTO(entities, convertFn)
|
||||
|
||||
responses.SuccessResponse(c, http.StatusOK, dtos)
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Response is a standardized API response structure
|
||||
type Response struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Error *ErrorInfo `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ErrorInfo contains detailed error information
|
||||
type ErrorInfo struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ErrorResponse codes
|
||||
const (
|
||||
ErrorCodeValidation = "VALIDATION_ERROR"
|
||||
ErrorCodeNotFound = "NOT_FOUND"
|
||||
ErrorCodeUnauthorized = "UNAUTHORIZED"
|
||||
ErrorCodeForbidden = "FORBIDDEN"
|
||||
ErrorCodeInternal = "INTERNAL_ERROR"
|
||||
ErrorCodeBadRequest = "BAD_REQUEST"
|
||||
ErrorCodeConflict = "CONFLICT"
|
||||
)
|
||||
|
||||
// SuccessResponse sends a successful response with data
|
||||
func SuccessResponse(c *gin.Context, statusCode int, data interface{}) {
|
||||
c.JSON(statusCode, Response{
|
||||
Success: true,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
// ErrorResponse sends an error response
|
||||
func ErrorResponse(c *gin.Context, statusCode int, errorCode string, message string) {
|
||||
c.JSON(statusCode, Response{
|
||||
Success: false,
|
||||
Error: &ErrorInfo{
|
||||
Code: errorCode,
|
||||
Message: message,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// BadRequestResponse sends a 400 Bad Request response
|
||||
func BadRequestResponse(c *gin.Context, message string) {
|
||||
ErrorResponse(c, http.StatusBadRequest, ErrorCodeBadRequest, message)
|
||||
}
|
||||
|
||||
// ValidationErrorResponse sends a 400 Bad Request response for validation errors
|
||||
func ValidationErrorResponse(c *gin.Context, message string) {
|
||||
ErrorResponse(c, http.StatusBadRequest, ErrorCodeValidation, message)
|
||||
}
|
||||
|
||||
// NotFoundResponse sends a 404 Not Found response
|
||||
func NotFoundResponse(c *gin.Context, message string) {
|
||||
ErrorResponse(c, http.StatusNotFound, ErrorCodeNotFound, message)
|
||||
}
|
||||
|
||||
// UnauthorizedResponse sends a 401 Unauthorized response
|
||||
func UnauthorizedResponse(c *gin.Context, message string) {
|
||||
ErrorResponse(c, http.StatusUnauthorized, ErrorCodeUnauthorized, message)
|
||||
}
|
||||
|
||||
// ForbiddenResponse sends a 403 Forbidden response
|
||||
func ForbiddenResponse(c *gin.Context, message string) {
|
||||
ErrorResponse(c, http.StatusForbidden, ErrorCodeForbidden, message)
|
||||
}
|
||||
|
||||
// InternalErrorResponse sends a 500 Internal Server Error response
|
||||
func InternalErrorResponse(c *gin.Context, message string) {
|
||||
ErrorResponse(c, http.StatusInternalServerError, ErrorCodeInternal, message)
|
||||
}
|
||||
|
||||
// ConflictResponse sends a 409 Conflict response
|
||||
func ConflictResponse(c *gin.Context, message string) {
|
||||
ErrorResponse(c, http.StatusConflict, ErrorCodeConflict, message)
|
||||
}
|
||||
Reference in New Issue
Block a user