feat: site languages, setup and site URL settings

This commit is contained in:
Florian Sylvain committed 2026-11-01 22:13:02 +01:00
1 parent 0806733d5c
commit d9158025d4
106 files changed
+10184 -56

No files matched your search

+164
View File
@@ -0,0 +1,164 @@
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)
}