article_add.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. package admin
  2. import (
  3. "context"
  4. "errors"
  5. "strconv"
  6. "git.dmitriygnatenko.ru/dima/go-common/db"
  7. "git.dmitriygnatenko.ru/dima/go-common/logger"
  8. validation "github.com/go-ozzo/ozzo-validation/v4"
  9. "github.com/gofiber/fiber/v2"
  10. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/dto"
  11. customErrors "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helper/errors"
  12. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/mapper"
  13. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/service/handler"
  14. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/service/i18n"
  15. )
  16. type addArticlePage struct {
  17. tm TransactionManager
  18. articleRepository ArticleRepository
  19. tagRepository TagRepository
  20. articleTagRepository ArticleTagRepository
  21. languageRepository LanguageRepository
  22. cacheService CacheService
  23. }
  24. func NewAddArticlePageHandler(
  25. tm TransactionManager,
  26. articleRepository ArticleRepository,
  27. tagRepository TagRepository,
  28. articleTagRepository ArticleTagRepository,
  29. languageRepository LanguageRepository,
  30. cacheService CacheService,
  31. ) fiber.Handler {
  32. h := addArticlePage{
  33. tm: tm,
  34. articleRepository: articleRepository,
  35. tagRepository: tagRepository,
  36. articleTagRepository: articleTagRepository,
  37. languageRepository: languageRepository,
  38. cacheService: cacheService,
  39. }
  40. return h.handler()
  41. }
  42. func (h addArticlePage) handler() fiber.Handler {
  43. return func(fctx *fiber.Ctx) error {
  44. ctx := fctx.Context()
  45. lang := i18n.LanguageFromContext(fctx)
  46. validationErrors := make(map[string]string)
  47. form := dto.ArticleForm{
  48. ActiveTags: make(map[uint64]bool),
  49. }
  50. tags, err := h.getTags(ctx)
  51. if err != nil {
  52. logger.Error(ctx, customErrors.Wrap(err, "add article page: can't get tags").Error())
  53. return err
  54. }
  55. languages, err := h.getLanguages(ctx)
  56. if err != nil {
  57. logger.Error(ctx, customErrors.Wrap(err, "add article page: can't get languages").Error())
  58. return err
  59. }
  60. if fctx.Method() == fiber.MethodPost {
  61. if err = fctx.BodyParser(&form); err != nil {
  62. logger.Info(ctx, customErrors.Wrap(err, "add article page: can't parse body").Error())
  63. return err
  64. }
  65. if err = h.validateArticleForm(ctx, form); err != nil {
  66. logger.Info(ctx, customErrors.Wrap(err, "add article page: validation").Error())
  67. validationErrors = mapper.ToErrorsMap(err)
  68. }
  69. tagIDs, err := h.formatTags(form.Tags)
  70. if err != nil {
  71. logger.Error(ctx, customErrors.Wrap(err, "add article page: can't format tags").Error())
  72. return err
  73. }
  74. for i := range tagIDs {
  75. form.ActiveTags[tagIDs[i]] = true
  76. }
  77. if len(validationErrors) == 0 {
  78. articleModel, err := mapper.ToArticleModel(form)
  79. if err != nil {
  80. logger.Error(ctx, customErrors.Wrap(err, "add article page").Error())
  81. return err
  82. }
  83. err = h.tm.ReadCommitted(ctx, func(ctx context.Context) error {
  84. articleID, txErr := h.articleRepository.Add(ctx, *articleModel)
  85. if txErr != nil {
  86. return customErrors.Wrap(txErr, "can't add article")
  87. }
  88. if len(tagIDs) > 0 {
  89. if txErr = h.articleTagRepository.Add(ctx, articleID, tagIDs); txErr != nil {
  90. return customErrors.Wrap(txErr, "can't add tags")
  91. }
  92. }
  93. return nil
  94. })
  95. if err != nil {
  96. logger.Error(ctx, customErrors.Wrap(err, "add article page").Error())
  97. return err
  98. }
  99. h.cacheService.Delete(handler.RecentArticlesCacheKey + form.Language)
  100. h.cacheService.Delete(handler.UsedTagsCacheKey + form.Language)
  101. h.cacheService.Delete(handler.AllPreviewArticlesCacheKey + form.Language)
  102. return fctx.Redirect("/admin")
  103. }
  104. }
  105. return fctx.Render("admin/article_edit", fiber.Map{
  106. "lang": lang,
  107. "title": i18n.T(lang, "admin_article_add_title"),
  108. "section": "article",
  109. "show_apply": false,
  110. "form": form,
  111. "errors": validationErrors,
  112. "tags": tags,
  113. "languages": languages,
  114. }, "admin/_layout")
  115. }
  116. }
  117. func (h addArticlePage) getTags(ctx context.Context) ([]dto.Tag, error) {
  118. tagModels, err := h.tagRepository.GetAll(ctx)
  119. if err != nil {
  120. return nil, err
  121. }
  122. return mapper.ToTagsList(tagModels), nil
  123. }
  124. func (h addArticlePage) getLanguages(ctx context.Context) ([]string, error) {
  125. langModels, err := h.languageRepository.GetAll(ctx)
  126. if err != nil {
  127. return nil, err
  128. }
  129. res := make([]string, 0, len(langModels))
  130. for i := range langModels {
  131. res = append(res, langModels[i].URL)
  132. }
  133. return res, nil
  134. }
  135. func (h addArticlePage) formatTags(tags []string) ([]uint64, error) {
  136. ids := make([]uint64, 0, len(tags))
  137. for i := range tags {
  138. id, err := strconv.ParseUint(tags[i], 10, 64)
  139. if err != nil {
  140. return nil, err
  141. }
  142. ids = append(ids, id)
  143. }
  144. return ids, nil
  145. }
  146. func (h addArticlePage) validateArticleForm(ctx context.Context, req dto.ArticleForm) error {
  147. lang := i18n.DefaultLanguage()
  148. requiredMsg := i18n.T(lang, "admin_err_required")
  149. lengthMsg := i18n.T(lang, "admin_err_length_255")
  150. return validation.ValidateStructWithContext(
  151. ctx,
  152. &req,
  153. validation.Field(
  154. &req.Title,
  155. validation.Required.Error(requiredMsg),
  156. validation.Length(1, 255).Error(lengthMsg),
  157. ),
  158. validation.Field(
  159. &req.Image,
  160. validation.Required.Error(requiredMsg),
  161. validation.Length(1, 255).Error(lengthMsg),
  162. ),
  163. validation.Field(
  164. &req.URL,
  165. validation.Required.Error(requiredMsg),
  166. validation.Length(1, 255).Error(lengthMsg),
  167. validation.By(h.validateDuplicateURL(req.Language)),
  168. ),
  169. validation.Field(
  170. &req.Text,
  171. validation.Required.Error(requiredMsg),
  172. ),
  173. validation.Field(
  174. &req.MetaKeywords,
  175. validation.Length(1, 255).Error(lengthMsg),
  176. ),
  177. validation.Field(
  178. &req.MetaDescription,
  179. validation.Length(1, 255).Error(lengthMsg),
  180. ),
  181. validation.Field(
  182. &req.PublishTime,
  183. validation.Required.Error(requiredMsg),
  184. ),
  185. validation.Field(
  186. &req.Language,
  187. validation.Required.Error(requiredMsg),
  188. ),
  189. )
  190. }
  191. func (h addArticlePage) validateDuplicateURL(
  192. lang string,
  193. ) func(value interface{}) error {
  194. return func(value interface{}) error {
  195. url, _ := value.(string)
  196. ctx := context.Background()
  197. _, err := h.articleRepository.GetByURL(ctx, url, i18n.LanguageID(i18n.Language(lang)))
  198. if err != nil {
  199. if !db.IsNotFoundError(err) {
  200. logger.Error(ctx, err.Error())
  201. }
  202. return nil
  203. }
  204. return errors.New(i18n.T(i18n.DefaultLanguage(), "admin_err_article_duplicate", url))
  205. }
  206. }