123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- package admin
- import (
- "context"
- "errors"
- "strconv"
- "git.dmitriygnatenko.ru/dima/go-common/db"
- "git.dmitriygnatenko.ru/dima/go-common/logger"
- validation "github.com/go-ozzo/ozzo-validation/v4"
- "github.com/gofiber/fiber/v2"
- "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/dto"
- customErrors "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helper/errors"
- "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helper/request"
- "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/mapper"
- "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/model"
- "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/service/handler"
- "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/service/i18n"
- )
- type editArticlePage struct {
- tm TransactionManager
- articleRepository ArticleRepository
- tagRepository TagRepository
- articleTagRepository ArticleTagRepository
- languageRepository LanguageRepository
- cacheService CacheService
- }
- func NewEditArticlePageHandler(
- tm TransactionManager,
- articleRepository ArticleRepository,
- tagRepository TagRepository,
- articleTagRepository ArticleTagRepository,
- languageRepository LanguageRepository,
- cacheService CacheService,
- ) fiber.Handler {
- h := editArticlePage{
- tm: tm,
- articleRepository: articleRepository,
- tagRepository: tagRepository,
- articleTagRepository: articleTagRepository,
- languageRepository: languageRepository,
- cacheService: cacheService,
- }
- return h.handler()
- }
- func (h editArticlePage) handler() fiber.Handler {
- return func(fctx *fiber.Ctx) error {
- ctx := fctx.Context()
- lang := i18n.LanguageFromContext(fctx)
- validationErrors := make(map[string]string)
- articleID, err := request.ConvertToUint64(fctx, "id")
- if err != nil {
- logger.Error(ctx, customErrors.Wrap(err, "edit article page: can't get id from request").Error())
- return err
- }
- tags, err := h.getTags(ctx)
- if err != nil {
- logger.Error(ctx, customErrors.Wrap(err, "edit article page: can't get tags").Error())
- return err
- }
- languages, err := h.getLanguages(ctx)
- if err != nil {
- logger.Error(ctx, customErrors.Wrap(err, "edit article page: can't get languages").Error())
- return err
- }
- article, err := h.getArticle(ctx, articleID)
- if err != nil {
- return err // logging in the getArticle method
- }
- articleTags, err := h.tagRepository.GetByArticleID(ctx, articleID)
- if err != nil {
- logger.Error(ctx, customErrors.Wrap(err, "edit article page: can't get article tags").Error())
- return err
- }
- var form dto.ArticleForm
- if fctx.Method() == fiber.MethodGet {
- form = mapper.ToArticleForm(*article, articleTags)
- } else if fctx.Method() == fiber.MethodPost {
- form = dto.ArticleForm{
- ID: articleID,
- ActiveTags: make(map[uint64]bool),
- }
- if err = fctx.BodyParser(&form); err != nil {
- logger.Info(ctx, customErrors.Wrap(err, "edit article page: can't parse body").Error())
- return err
- }
- if err = h.validateArticleForm(ctx, form, *article); err != nil {
- logger.Info(ctx, customErrors.Wrap(err, "edit article page: validation").Error())
- validationErrors = mapper.ToErrorsMap(err)
- }
- tagIDs, err := h.formatTags(form.Tags)
- if err != nil {
- logger.Error(ctx, customErrors.Wrap(err, "edit article page: can't format tags").Error())
- return err
- }
- for i := range tagIDs {
- form.ActiveTags[tagIDs[i]] = true
- }
- if len(validationErrors) == 0 {
- articleModel, err := mapper.ToArticleModel(form)
- if err != nil {
- logger.Error(
- ctx,
- customErrors.Wrap(err, "edit article page: can't create article model").Error(),
- )
- return err
- }
- var tagsToAdd, tagsToDelete []uint64
- oldTagsMap := make(map[int]struct{}, len(articleTags))
- for i := range articleTags {
- oldTagsMap[int(articleTags[i].ID)] = struct{}{}
- if _, ok := form.ActiveTags[articleTags[i].ID]; !ok {
- tagsToDelete = append(tagsToDelete, articleTags[i].ID)
- }
- }
- for i := range tagIDs {
- if _, ok := oldTagsMap[int(tagIDs[i])]; !ok {
- tagsToAdd = append(tagsToAdd, tagIDs[i])
- }
- }
- err = h.tm.ReadCommitted(ctx, func(ctx context.Context) error {
- txErr := h.articleRepository.Update(ctx, *articleModel)
- if txErr != nil {
- return customErrors.Wrap(txErr, "can't update article")
- }
- if len(tagsToAdd) > 0 {
- if txErr = h.articleTagRepository.Add(ctx, articleID, tagsToAdd); txErr != nil {
- return customErrors.Wrap(txErr, "can't add tags")
- }
- }
- if len(tagsToDelete) > 0 {
- if txErr = h.articleTagRepository.Delete(ctx, articleID, tagsToDelete); txErr != nil {
- return customErrors.Wrap(txErr, "can't delete tags")
- }
- }
- return nil
- })
- if err != nil {
- logger.Error(ctx, customErrors.Wrap(err, "edit article page").Error())
- return err
- }
- h.cacheService.Delete(handler.ArticleCacheKey + form.URL + form.Language)
- h.cacheService.Delete(handler.RecentArticlesCacheKey + form.Language)
- h.cacheService.Delete(handler.UsedTagsCacheKey + form.Language)
- h.cacheService.Delete(handler.AllPreviewArticlesCacheKey + form.Language)
- if fctx.FormValue("action", "save") == "save" {
- return fctx.Redirect("/admin")
- }
- }
- }
- return fctx.Render("admin/article_edit", fiber.Map{
- "lang": lang,
- "title": i18n.T(lang, "admin_article_edit_title"),
- "section": "article",
- "show_apply": true,
- "form": form,
- "errors": validationErrors,
- "tags": tags,
- "languages": languages,
- }, "admin/_layout")
- }
- }
- func (h editArticlePage) getArticle(ctx context.Context, id uint64) (*model.Article, error) {
- article, err := h.articleRepository.GetByID(ctx, id)
- if err != nil {
- if db.IsNotFoundError(err) {
- return nil, fiber.ErrNotFound
- }
- logger.Error(ctx, customErrors.Wrap(err, "edit article page: can't get article").Error())
- return nil, err
- }
- return article, nil
- }
- func (h editArticlePage) getTags(ctx context.Context) ([]dto.Tag, error) {
- tagModels, err := h.tagRepository.GetAll(ctx)
- if err != nil {
- return nil, err
- }
- return mapper.ToTagsList(tagModels), nil
- }
- func (h editArticlePage) getLanguages(ctx context.Context) ([]string, error) {
- langModels, err := h.languageRepository.GetAll(ctx)
- if err != nil {
- return nil, err
- }
- res := make([]string, 0, len(langModels))
- for i := range langModels {
- res = append(res, langModels[i].URL)
- }
- return res, nil
- }
- func (h editArticlePage) formatTags(tags []string) ([]uint64, error) {
- ids := make([]uint64, 0, len(tags))
- for i := range tags {
- id, err := strconv.ParseUint(tags[i], 10, 64)
- if err != nil {
- return nil, err
- }
- ids = append(ids, id)
- }
- return ids, nil
- }
- func (h editArticlePage) validateArticleForm(
- ctx context.Context,
- req dto.ArticleForm,
- article model.Article,
- ) error {
- lang := i18n.DefaultLanguage()
- requiredMsg := i18n.T(lang, "admin_err_required")
- lengthMsg := i18n.T(lang, "admin_err_length_255")
- return validation.ValidateStructWithContext(
- ctx,
- &req,
- validation.Field(
- &req.Title,
- validation.Required.Error(requiredMsg),
- validation.Length(1, 255).Error(lengthMsg),
- ),
- validation.Field(
- &req.Image,
- validation.Required.Error(requiredMsg),
- validation.Length(1, 255).Error(lengthMsg),
- ),
- validation.Field(
- &req.URL,
- validation.Required.Error(requiredMsg),
- validation.Length(1, 255).Error(lengthMsg),
- validation.By(h.validateDuplicateURL(article)),
- ),
- validation.Field(
- &req.Text,
- validation.Required.Error(requiredMsg),
- ),
- validation.Field(
- &req.MetaKeywords,
- validation.Length(1, 255).Error(lengthMsg),
- ),
- validation.Field(
- &req.MetaDescription,
- validation.Length(1, 255).Error(lengthMsg),
- ),
- validation.Field(
- &req.PublishTime,
- validation.Required.Error(requiredMsg),
- ),
- validation.Field(
- &req.Language,
- validation.Required.Error(requiredMsg),
- ),
- )
- }
- func (h editArticlePage) validateDuplicateURL(
- article model.Article,
- ) func(value interface{}) error {
- return func(value interface{}) error {
- url, _ := value.(string)
- if url == article.URL {
- return nil
- }
- ctx := context.Background()
- _, err := h.articleRepository.GetByURL(ctx, url, article.Language)
- if err != nil {
- if !db.IsNotFoundError(err) {
- logger.Error(ctx, err.Error())
- }
- return nil
- }
- return errors.New(i18n.T(i18n.DefaultLanguage(), "admin_err_article_duplicate", url))
- }
- }
|