package language import ( "RenewCMS/api/pkg/apperr" validator "RenewCMS/api/pkg/validator" domainLanguage "RenewCMS/internal/domain/language" "RenewCMS/internal/domain/localization" "RenewCMS/internal/domain/site" "RenewCMS/internal/infrastructure/useCases" "encoding/json" "errors" "log" "net/http" "github.com/go-chi/chi/v5" ) type SiteLanguagesHandler struct { GetUseCase *useCases.GetSiteLanguagesUseCase UpdateUseCase *useCases.UpdateSiteLanguagesUseCase } func NewSiteLanguagesHandler(get *useCases.GetSiteLanguagesUseCase, update *useCases.UpdateSiteLanguagesUseCase) *SiteLanguagesHandler { return &SiteLanguagesHandler{GetUseCase: get, UpdateUseCase: update} } type PutLanguage struct { Lang string `json:"lang"` Enabled *bool `json:"enabled" validate:"required"` SocialLocale *string `json:"social_locale"` } type PutSiteLanguages struct { Revision *string `json:"revision" validate:"required"` Default *string `json:"default" validate:"required"` Languages []PutLanguage `json:"languages" validate:"required,dive"` } func (p PutSiteLanguages) command(key string) useCases.UpdateSiteLanguagesCommand { entries := make([]useCases.SiteLanguageEntry, 0, len(p.Languages)) for _, entry := range p.Languages { entries = append(entries, useCases.SiteLanguageEntry{Lang: entry.Lang, Enabled: *entry.Enabled, SocialLocale: entry.SocialLocale}) } return useCases.UpdateSiteLanguagesCommand{Key: key, Revision: *p.Revision, Default: *p.Default, Languages: entries} } func (h *SiteLanguagesHandler) Get(w http.ResponseWriter, r *http.Request) { view, err := h.GetUseCase.Get(chi.URLParam(r, "key")) if err != nil { writeLocalizationError(w, err) return } writeJSON(w, view) } func (h *SiteLanguagesHandler) Update(w http.ResponseWriter, r *http.Request) { var request PutSiteLanguages if err := json.NewDecoder(r.Body).Decode(&request); err != nil { http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) return } if err := validator.Validate.Struct(request); err != nil { http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) return } view, err := h.UpdateUseCase.Update(r.Context(), request.command(chi.URLParam(r, "key"))) if err != nil { writeLocalizationError(w, err) return } writeJSON(w, view) } type SetupHandler struct { GetUseCase *useCases.GetLanguageSetupUseCase ApplyUseCase *useCases.ApplyLanguageSetupUseCase } func NewSetupHandler(get *useCases.GetLanguageSetupUseCase, apply *useCases.ApplyLanguageSetupUseCase) *SetupHandler { return &SetupHandler{GetUseCase: get, ApplyUseCase: apply} } type PostSetupSite struct { Key string `json:"key" validate:"required"` Lang string `json:"lang"` } type PostSetup struct { Sites []PostSetupSite `json:"sites" validate:"required,dive"` } func (h *SetupHandler) Get(w http.ResponseWriter, r *http.Request) { view, err := h.GetUseCase.Get() if err != nil { writeLocalizationError(w, err) return } writeJSON(w, view) } func (h *SetupHandler) Apply(w http.ResponseWriter, r *http.Request) { var request PostSetup if err := json.NewDecoder(r.Body).Decode(&request); err != nil { http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) return } if err := validator.Validate.Struct(request); err != nil { http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) return } choices := make([]localization.SetupChoice, 0, len(request.Sites)) for _, entry := range request.Sites { choices = append(choices, localization.SetupChoice{Key: entry.Key, Lang: entry.Lang}) } result, err := h.ApplyUseCase.Apply(r.Context(), choices) if err != nil { writeLocalizationError(w, err) return } writeJSON(w, result) } func writeJSON(w http.ResponseWriter, value any) { body, err := json.Marshal(value) if err != nil { http.Error(w, "Failed to encode response", http.StatusInternalServerError) return } if _, err := w.Write(body); err != nil { log.Printf("Failed to write response: %v", err) } } type localizationFailure struct { sentinel error status int body string } var localizationFailures = []localizationFailure{ {site.ErrSiteNotFound, http.StatusNotFound, apperr.SiteNotFoundErrorMessage}, {localization.ErrSetupRequired, http.StatusConflict, apperr.SetupRequiredErrorMessage}, {localization.ErrLanguagesChanged, http.StatusConflict, apperr.LanguagesChangedErrorMessage}, {localization.ErrLanguageInUse, http.StatusConflict, apperr.LanguageInUseErrorMessage}, {localization.ErrSetupInProgress, http.StatusConflict, apperr.SetupInProgressErrorMessage}, {localization.ErrDefaultNotEnabled, http.StatusUnprocessableEntity, apperr.DefaultNotEnabledErrorMessage}, {localization.ErrNoEnabledLanguage, http.StatusUnprocessableEntity, apperr.NoEnabledLanguageErrorMessage}, {localization.ErrDuplicateLanguage, http.StatusUnprocessableEntity, apperr.DuplicateLanguageErrorMessage}, {localization.ErrInvalidSocialLocale, http.StatusUnprocessableEntity, apperr.InvalidSocialLocaleErrorMessage}, {localization.ErrSetupIncomplete, http.StatusUnprocessableEntity, apperr.SetupIncompleteErrorMessage}, {localization.ErrDuplicateSite, http.StatusUnprocessableEntity, apperr.DuplicateSiteErrorMessage}, {domainLanguage.ErrInvalidLanguage, http.StatusUnprocessableEntity, apperr.InvalidLanguageErrorMessage}, {domainLanguage.ErrUnsupportedLanguage, http.StatusUnprocessableEntity, apperr.UnsupportedLanguageErrorMessage}, } func writeLocalizationError(w http.ResponseWriter, err error) { for _, failure := range localizationFailures { if errors.Is(err, failure.sentinel) { http.Error(w, failure.body, failure.status) return } } http.Error(w, "Internal server error", http.StatusInternalServerError) }