265 lines
8.0 KiB
Go
265 lines
8.0 KiB
Go
package imagevariant
|
|
|
|
import (
|
|
"RenewCMS/api/pkg/apperr"
|
|
validator "RenewCMS/api/pkg/validator"
|
|
"RenewCMS/internal/domain/image"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
validatorlib "github.com/go-playground/validator/v10"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
type Handler struct {
|
|
list *useCases.ListVariantsUseCase
|
|
create *useCases.CreateVariantUseCase
|
|
update *useCases.UpdateVariantUseCase
|
|
setEnabled *useCases.SetVariantEnabledUseCase
|
|
remove *useCases.DeleteVariantUseCase
|
|
purge *useCases.PurgeVariantUseCase
|
|
}
|
|
|
|
func NewHandler(list *useCases.ListVariantsUseCase, create *useCases.CreateVariantUseCase, update *useCases.UpdateVariantUseCase, setEnabled *useCases.SetVariantEnabledUseCase, remove *useCases.DeleteVariantUseCase, purge *useCases.PurgeVariantUseCase) *Handler {
|
|
return &Handler{list, create, update, setEnabled, remove, purge}
|
|
}
|
|
|
|
type definitionRequest struct {
|
|
Width *int `json:"width" validate:"required_without=Height,omitempty,min=1,max=8000"`
|
|
Height *int `json:"height" validate:"required_without=Width,omitempty,min=1,max=8000"`
|
|
Fit string `json:"fit" validate:"omitempty,oneof=cover contain inside"`
|
|
Format string `json:"format" validate:"omitempty,oneof=original jpeg png webp"`
|
|
Quality *int `json:"quality" validate:"omitempty,min=1,max=100"`
|
|
}
|
|
|
|
type switchesRequest struct {
|
|
GenerateOnUpload *bool `json:"generate_on_upload"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
type createRequest struct {
|
|
Name string `json:"name" validate:"required,variantname"`
|
|
definitionRequest
|
|
switchesRequest
|
|
}
|
|
|
|
type updateRequest struct {
|
|
definitionRequest
|
|
switchesRequest
|
|
}
|
|
|
|
type enabledRequest struct {
|
|
Enabled *bool `json:"enabled" validate:"required"`
|
|
}
|
|
|
|
type variantResponse struct {
|
|
Name string `json:"name"`
|
|
Width *int `json:"width"`
|
|
Height *int `json:"height"`
|
|
Fit image.VariantFit `json:"fit"`
|
|
Format image.VariantFormat `json:"format"`
|
|
Quality *int `json:"quality"`
|
|
GenerateOnUpload bool `json:"generate_on_upload"`
|
|
Enabled bool `json:"enabled"`
|
|
GeneratedFiles int `json:"generated_files"`
|
|
CacheSize int64 `json:"cache_size"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type updatedVariantResponse struct {
|
|
variantResponse
|
|
PurgedFiles int `json:"purged_files"`
|
|
}
|
|
|
|
type listResponse struct {
|
|
Items []variantResponse `json:"items"`
|
|
}
|
|
|
|
type purgeResponse struct {
|
|
PurgedFiles int `json:"purged_files"`
|
|
PurgedBytes int64 `json:"purged_bytes"`
|
|
}
|
|
|
|
func responseOf(summary useCases.VariantSummary) variantResponse {
|
|
variant := summary.Variant
|
|
return variantResponse{
|
|
Name: variant.Name,
|
|
Width: variant.Width,
|
|
Height: variant.Height,
|
|
Fit: variant.Fit,
|
|
Format: variant.Format,
|
|
Quality: variant.Quality,
|
|
GenerateOnUpload: variant.GenerateOnUpload,
|
|
Enabled: variant.Enabled,
|
|
GeneratedFiles: summary.GeneratedFiles,
|
|
CacheSize: summary.CacheSize,
|
|
CreatedAt: variant.CreatedAt,
|
|
UpdatedAt: variant.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
func (h *Handler) List(w http.ResponseWriter, _ *http.Request) {
|
|
summaries, err := h.list.List()
|
|
if err != nil {
|
|
h.writeError(w, "", err)
|
|
return
|
|
}
|
|
items := make([]variantResponse, 0, len(summaries))
|
|
for _, summary := range summaries {
|
|
items = append(items, responseOf(summary))
|
|
}
|
|
writeJSON(w, http.StatusOK, listResponse{items})
|
|
}
|
|
|
|
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
|
|
var request createRequest
|
|
if !h.decode(w, r, &request) {
|
|
return
|
|
}
|
|
|
|
summary, err := h.create.Create(r.Context(), useCases.CreateVariantCommand{
|
|
Name: request.Name,
|
|
VariantDefinitionCommand: request.definitionRequest.command(),
|
|
GenerateOnUpload: request.GenerateOnUpload,
|
|
Enabled: request.Enabled,
|
|
})
|
|
if err != nil {
|
|
h.writeError(w, request.Name, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, responseOf(summary))
|
|
}
|
|
|
|
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|
var request updateRequest
|
|
if !h.decode(w, r, &request) {
|
|
return
|
|
}
|
|
|
|
name := chi.URLParam(r, "name")
|
|
result, err := h.update.Update(r.Context(), useCases.UpdateVariantCommand{
|
|
Name: name,
|
|
VariantDefinitionCommand: request.definitionRequest.command(),
|
|
GenerateOnUpload: request.GenerateOnUpload,
|
|
Enabled: request.Enabled,
|
|
})
|
|
if err != nil {
|
|
h.writeError(w, name, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, updatedVariantResponse{responseOf(result.VariantSummary), result.PurgedFiles})
|
|
}
|
|
|
|
func (h *Handler) SetEnabled(w http.ResponseWriter, r *http.Request) {
|
|
var request enabledRequest
|
|
if !h.decode(w, r, &request) {
|
|
return
|
|
}
|
|
|
|
name := chi.URLParam(r, "name")
|
|
summary, err := h.setEnabled.SetEnabled(r.Context(), name, *request.Enabled)
|
|
if err != nil {
|
|
h.writeError(w, name, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, responseOf(summary))
|
|
}
|
|
|
|
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
if err := h.remove.Delete(r.Context(), name); err != nil {
|
|
h.writeError(w, name, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *Handler) Purge(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
purged, err := h.purge.Purge(r.Context(), name)
|
|
if err != nil {
|
|
h.writeError(w, name, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, purgeResponse{purged.Files, purged.Bytes})
|
|
}
|
|
|
|
func (d definitionRequest) command() useCases.VariantDefinitionCommand {
|
|
return useCases.VariantDefinitionCommand{Width: d.Width, Height: d.Height, Fit: d.Fit, Format: d.Format, Quality: d.Quality}
|
|
}
|
|
|
|
func (h *Handler) decode(w http.ResponseWriter, r *http.Request, request any) bool {
|
|
if err := json.NewDecoder(r.Body).Decode(request); err != nil {
|
|
http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest)
|
|
return false
|
|
}
|
|
if err := validator.Validate.Struct(request); err != nil {
|
|
http.Error(w, h.validationMessage(err), http.StatusUnprocessableEntity)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (h *Handler) validationMessage(err error) string {
|
|
var failures validatorlib.ValidationErrors
|
|
if !errors.As(err, &failures) || len(failures) == 0 {
|
|
return apperr.VariantInvalidErrorMessage
|
|
}
|
|
switch failures[0].StructField() {
|
|
case "Name":
|
|
return apperr.VariantNameErrorMessage
|
|
case "Width", "Height":
|
|
return apperr.VariantDimensionsErrorMessage
|
|
case "Fit":
|
|
return apperr.VariantFitErrorMessage
|
|
case "Format":
|
|
return h.formatMessage()
|
|
case "Quality":
|
|
return apperr.VariantQualityErrorMessage
|
|
default:
|
|
return apperr.VariantInvalidErrorMessage
|
|
}
|
|
}
|
|
|
|
func (h *Handler) formatMessage() string {
|
|
encodable := h.create.EncodableFormats()
|
|
names := make([]string, 0, len(encodable))
|
|
for _, format := range encodable {
|
|
names = append(names, string(format))
|
|
}
|
|
last := len(names) - 1
|
|
return fmt.Sprintf(apperr.VariantFormatErrorMessageFormat, strings.Join(names[:last], ", ")+" or "+names[last])
|
|
}
|
|
|
|
func (h *Handler) writeError(w http.ResponseWriter, name string, err error) {
|
|
switch {
|
|
case errors.Is(err, image.ErrVariantNotFound):
|
|
http.Error(w, apperr.VariantNotFoundErrorMessage, http.StatusNotFound)
|
|
case errors.Is(err, image.ErrVariantExists):
|
|
http.Error(w, fmt.Sprintf(apperr.VariantExistsErrorMessageFormat, name), http.StatusConflict)
|
|
case errors.Is(err, image.ErrVariantFormatUnavailable):
|
|
http.Error(w, h.formatMessage(), http.StatusUnprocessableEntity)
|
|
case errors.Is(err, image.ErrVariantInvalid):
|
|
http.Error(w, apperr.VariantInvalidErrorMessage, http.StatusUnprocessableEntity)
|
|
default:
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, value any) {
|
|
body, err := json.Marshal(value)
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.WriteHeader(status)
|
|
_, _ = w.Write(body)
|
|
}
|