package article import ( "RenewCMS/api/pkg/apperr" validator "RenewCMS/api/pkg/validator" domain "RenewCMS/internal/domain/article" "RenewCMS/internal/domain/language" "RenewCMS/internal/domain/site" "RenewCMS/internal/infrastructure/useCases" "encoding/json" "errors" "log" "net/http" "strconv" "github.com/go-chi/chi/v5" ) type PublicHandler struct { resolveUseCase *useCases.ResolvePublicArticleUseCase } func NewPublicHandler(resolve *useCases.ResolvePublicArticleUseCase) *PublicHandler { return &PublicHandler{resolveUseCase: resolve} } type publicLookupResponse struct { articleResponse Alternates []domain.Alternate `json:"alternates"` } type publicStoryResponse struct { articleResponse Alternates []domain.Alternate `json:"alternates"` RequestedLang string `json:"requested_lang"` Fallback bool `json:"fallback"` } type publicStoryQuery struct { Fallback *string `validate:"omitempty,eq=default"` } var publicErrors = []struct { cause error message string status int }{ {domain.ErrArticleNotFound, apperr.ArticleNotFoundCode, http.StatusNotFound}, {domain.ErrLanguageNotAvailable, apperr.LanguageNotAvailableCode, http.StatusNotFound}, {site.ErrSiteNotFound, apperr.SiteNotFoundCode, http.StatusNotFound}, {language.ErrInvalidLanguage, apperr.InvalidLanguageCode, http.StatusBadRequest}, } func writePublicError(w http.ResponseWriter, err error) { for _, known := range publicErrors { if errors.Is(err, known.cause) { http.Error(w, known.message, known.status) return } } http.Error(w, "Internal server error", http.StatusInternalServerError) } func (h *PublicHandler) Lookup(w http.ResponseWriter, r *http.Request) { format, ok := parseBodyFormat(w, r) if !ok { return } found, err := h.resolveUseCase.ByLangSlug(useCases.PublicLookupQuery{ SiteKey: chi.URLParam(r, "key"), Lang: chi.URLParam(r, "lang"), Slug: chi.URLParam(r, "slug"), Format: format, }) if err != nil { writePublicError(w, err) return } h.write(w, found, format, func(response articleResponse) any { return publicLookupResponse{articleResponse: response, Alternates: found.Alternates} }) } func (h *PublicHandler) Story(w http.ResponseWriter, r *http.Request) { storyID, err := strconv.ParseUint(chi.URLParam(r, "story_id"), 10, 32) if err != nil { http.Error(w, apperr.IdUint32ErrorMessage, http.StatusBadRequest) return } format, ok := parseBodyFormat(w, r) if !ok { return } values := r.URL.Query() query := publicStoryQuery{} if values.Has("fallback") { fallback := values.Get("fallback") query.Fallback = &fallback } if validator.Validate.Struct(query) != nil { http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest) return } command := useCases.PublicStoryQuery{ SiteKey: chi.URLParam(r, "key"), StoryID: uint32(storyID), FallbackDefault: query.Fallback != nil, Format: format, } if values.Has("lang") { requested := values.Get("lang") command.Lang = &requested } found, err := h.resolveUseCase.ByStory(command) if err != nil { writePublicError(w, err) return } h.write(w, found, format, func(response articleResponse) any { return publicStoryResponse{articleResponse: response, Alternates: found.Alternates, RequestedLang: found.RequestedLang, Fallback: found.Fallback} }) } func (h *PublicHandler) write(w http.ResponseWriter, found useCases.PublicArticle, format domain.BodyFormat, shape func(articleResponse) any) { response, err := toArticleResponse(found.Article, format, found.Viewer, found.Context) if err != nil { http.Error(w, "Failed to encode response", http.StatusInternalServerError) return } body, err := json.Marshal(shape(response)) 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) } }