feat: site languages, setup and site URL settings
This commit is contained in:
1 parent
0806733d5c
commit
d9158025d4
106 files changed
+10184
-56
No files matched your search
@@ -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)
|
||||
}
|
||||
@@ -71,6 +71,7 @@ type PatchSite struct {
|
||||
SiteName *string `json:"site_name" validate:"omitempty,max=100"`
|
||||
TitleTemplate *string `json:"title_template" validate:"omitempty,max=200"`
|
||||
DefaultDescription *string `json:"default_description" validate:"omitempty,max=320"`
|
||||
PublicURLPattern optional[string] `json:"public_url_pattern"`
|
||||
}
|
||||
|
||||
func (p PatchSite) toPatch() domain.Patch {
|
||||
@@ -85,6 +86,7 @@ func (p PatchSite) toPatch() domain.Patch {
|
||||
SiteName: p.SiteName,
|
||||
TitleTemplate: p.TitleTemplate,
|
||||
DefaultDescription: p.DefaultDescription,
|
||||
PublicURLPattern: domain.Nullable[string]{Set: p.PublicURLPattern.Set, Value: p.PublicURLPattern.Value},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +127,18 @@ func writeSiteError(w http.ResponseWriter, err error) {
|
||||
http.Error(w, apperr.InvalidTitleTemplateErrorMessage, http.StatusBadRequest)
|
||||
case errors.Is(err, domain.ErrInvalidLocale):
|
||||
http.Error(w, apperr.InvalidLocaleErrorMessage, http.StatusBadRequest)
|
||||
case errors.Is(err, domain.ErrInvalidPublicURLPattern):
|
||||
http.Error(w, apperr.InvalidPublicURLPatternErrorMessage, http.StatusUnprocessableEntity)
|
||||
case errors.Is(err, domain.ErrPublicURLScheme):
|
||||
http.Error(w, apperr.PublicURLSchemeErrorMessage, http.StatusUnprocessableEntity)
|
||||
case errors.Is(err, domain.ErrPublicURLHost):
|
||||
http.Error(w, apperr.PublicURLHostErrorMessage, http.StatusUnprocessableEntity)
|
||||
case errors.Is(err, domain.ErrPublicURLPlaceholder):
|
||||
http.Error(w, apperr.PublicURLPlaceholderErrorMessage, http.StatusUnprocessableEntity)
|
||||
case errors.Is(err, domain.ErrPublicURLNotEncoded):
|
||||
http.Error(w, apperr.PublicURLNotEncodedErrorMessage, http.StatusUnprocessableEntity)
|
||||
case errors.Is(err, domain.ErrPublicURLMissingSlug):
|
||||
http.Error(w, apperr.PublicURLMissingSlugErrorMessage, http.StatusUnprocessableEntity)
|
||||
case errors.Is(err, image.ErrImageNotFound):
|
||||
http.Error(w, apperr.SiteImageNotFoundErrorMessage, http.StatusBadRequest)
|
||||
case errors.Is(err, domain.ErrUseSetDefault):
|
||||
|
||||
Reference in new issue
Block a user