article_edit.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. package admin
  2. import (
  3. "context"
  4. "strconv"
  5. "git.dmitriygnatenko.ru/dima/go-common/logger"
  6. "github.com/go-playground/validator/v10"
  7. "github.com/gofiber/fiber/v2"
  8. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/dto"
  9. helper "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helpers/i18n"
  10. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helpers/request"
  11. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/mapper"
  12. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler"
  13. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/i18n"
  14. )
  15. type articleEditPage struct {
  16. tm TransactionManager
  17. articleRepository ArticleRepository
  18. tagRepository TagRepository
  19. articleTagRepository ArticleTagRepository
  20. cacheService CacheService
  21. }
  22. func NewArticleEditPageHandler(
  23. tm TransactionManager,
  24. articleRepository ArticleRepository,
  25. tagRepository TagRepository,
  26. articleTagRepository ArticleTagRepository,
  27. cacheService CacheService,
  28. ) fiber.Handler {
  29. h := articleEditPage{
  30. tm: tm,
  31. articleRepository: articleRepository,
  32. tagRepository: tagRepository,
  33. articleTagRepository: articleTagRepository,
  34. cacheService: cacheService,
  35. }
  36. return h.handler()
  37. }
  38. func (h articleEditPage) handler() fiber.Handler {
  39. return func(fctx *fiber.Ctx) error {
  40. ctx := fctx.Context()
  41. lang := i18n.LanguageFromContext(fctx)
  42. var validate = validator.New()
  43. validateErrors := make(map[string]string)
  44. trans, err := helper.GetDefaultTranslator(validate)
  45. if err != nil {
  46. logger.Error(ctx, err.Error())
  47. return err
  48. }
  49. id, err := request.ConvertToUint64(fctx, "id")
  50. if err != nil {
  51. logger.Error(ctx, err.Error())
  52. return err
  53. }
  54. article, err := h.articleRepository.GetByID(ctx, id)
  55. if err != nil {
  56. logger.Error(ctx, err.Error())
  57. return err
  58. }
  59. if article == nil {
  60. return fiber.ErrNotFound
  61. }
  62. articleTags, err := h.tagRepository.GetByArticleID(ctx, id)
  63. if err != nil {
  64. logger.Error(ctx, err.Error())
  65. return err
  66. }
  67. tags, err := h.tagRepository.GetAll(ctx)
  68. if err != nil {
  69. logger.Error(ctx, err.Error())
  70. return err
  71. }
  72. tagsDTO := mapper.ToTagsList(tags)
  73. var form *dto.ArticleForm
  74. if fctx.Method() == fiber.MethodGet {
  75. form = mapper.ToArticleForm(*article, articleTags)
  76. } else if fctx.Method() == fiber.MethodPost {
  77. form = &dto.ArticleForm{
  78. ID: id,
  79. ActiveTags: make(map[uint64]bool),
  80. }
  81. if err = fctx.BodyParser(form); err != nil {
  82. logger.Info(ctx, err.Error())
  83. return err
  84. }
  85. if err = validate.Struct(*form); err != nil {
  86. validateErrors = helper.FormatValidateErrors(err, trans)
  87. }
  88. if res, _ := h.articleRepository.GetByURL(ctx, form.URL); res != nil {
  89. if res.ID != id {
  90. validateErrors["ArticleForm.URL"] = i18n.T(lang, "admin_err_article_exists")
  91. }
  92. }
  93. tagIDs := make([]uint64, 0, len(form.Tags))
  94. for i := range form.Tags {
  95. tagID, tagErr := strconv.ParseUint(form.Tags[i], 10, 64)
  96. if tagErr != nil {
  97. return tagErr
  98. }
  99. tagIDs = append(tagIDs, tagID)
  100. }
  101. for i := range tagIDs {
  102. form.ActiveTags[tagIDs[i]] = true
  103. }
  104. if len(validateErrors) == 0 {
  105. articleModel, err := mapper.ToArticleModel(*form)
  106. if err != nil {
  107. logger.Error(ctx, err.Error())
  108. return err
  109. }
  110. var tagsToAdd, tagsToDelete []uint64
  111. oldTagsMap := make(map[int]struct{}, len(articleTags))
  112. for i := range articleTags {
  113. oldTagsMap[int(articleTags[i].ID)] = struct{}{}
  114. if _, ok := form.ActiveTags[articleTags[i].ID]; !ok {
  115. tagsToDelete = append(tagsToDelete, articleTags[i].ID)
  116. }
  117. }
  118. for i := range tagIDs {
  119. if _, ok := oldTagsMap[int(tagIDs[i])]; !ok {
  120. tagsToAdd = append(tagsToAdd, tagIDs[i])
  121. }
  122. }
  123. err = h.tm.ReadCommitted(ctx, func(ctx context.Context) error {
  124. txErr := h.articleRepository.Update(ctx, *articleModel)
  125. if txErr != nil {
  126. return txErr
  127. }
  128. if len(tagsToAdd) > 0 {
  129. if txErr = h.articleTagRepository.Add(ctx, id, tagsToAdd); txErr != nil {
  130. return txErr
  131. }
  132. }
  133. if len(tagsToDelete) > 0 {
  134. if txErr = h.articleTagRepository.Delete(ctx, id, tagsToDelete); txErr != nil {
  135. return txErr
  136. }
  137. }
  138. return nil
  139. })
  140. if err != nil {
  141. logger.Error(ctx, err.Error())
  142. return err
  143. }
  144. h.cacheService.Delete(handler.ArticleCacheKey + string(lang) + article.URL)
  145. if fctx.FormValue("action", "save") == "save" {
  146. return fctx.Redirect("/admin")
  147. }
  148. }
  149. }
  150. return fctx.Render("admin/article_edit", fiber.Map{
  151. "section": "article",
  152. "lang": lang,
  153. "form": form,
  154. "errors": validateErrors,
  155. "tags": tagsDTO,
  156. "show_apply": true,
  157. "title": i18n.T(lang, "admin_edit_article_title"),
  158. }, "admin/_layout")
  159. }
  160. }