article.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package handler
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "git.dmitriygnatenko.ru/dima/go-common/logger"
  7. "github.com/gofiber/fiber/v2"
  8. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/dto"
  9. customErrors "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helpers/errors"
  10. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/mapper"
  11. "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/i18n"
  12. )
  13. const (
  14. ArticleCacheKey = "article"
  15. RecentArticlesCacheKey = "recent-articles"
  16. UsedTagsCacheKey = "used-tags"
  17. previewArticlesCount = 3
  18. articleParam = "article"
  19. )
  20. type articlePage struct {
  21. cacheService CacheService
  22. articleRepository ArticleRepository
  23. tagRepository TagRepository
  24. }
  25. func NewArticlePageHandler(
  26. cacheService CacheService,
  27. articleRepository ArticleRepository,
  28. tagRepository TagRepository,
  29. ) fiber.Handler {
  30. h := articlePage{
  31. cacheService: cacheService,
  32. articleRepository: articleRepository,
  33. tagRepository: tagRepository,
  34. }
  35. return h.handler()
  36. }
  37. func (h articlePage) handler() fiber.Handler {
  38. return func(fctx *fiber.Ctx) error {
  39. ctx := fctx.Context()
  40. url := fctx.Params(articleParam)
  41. lang := i18n.LanguageFromContext(fctx)
  42. // article
  43. renderData, err := h.article(ctx, lang, url)
  44. if err != nil {
  45. if !errors.Is(err, fiber.ErrNotFound) {
  46. logger.Error(ctx, customErrors.Wrap(err, "article page").Error())
  47. }
  48. return err
  49. }
  50. // recent articles
  51. articles, err := h.recentArticles(ctx, lang)
  52. if err != nil {
  53. logger.Error(ctx, customErrors.Wrap(err, "article page").Error())
  54. return err
  55. }
  56. renderData["sidebarArticles"] = articles
  57. // tags
  58. tags, err := h.tags(ctx, lang)
  59. if err != nil {
  60. logger.Error(ctx, customErrors.Wrap(err, "article page").Error())
  61. return err
  62. }
  63. renderData["sidebarTags"] = tags
  64. return fctx.Render("article", renderData, "_layout")
  65. }
  66. }
  67. func (h articlePage) article(
  68. ctx context.Context,
  69. lang i18n.Language,
  70. url string,
  71. ) (fiber.Map, error) {
  72. articleCacheKey := ArticleCacheKey + url
  73. cacheData, found := h.cacheService.Get(articleCacheKey)
  74. if found {
  75. if articleData, ok := cacheData.(fiber.Map); ok {
  76. return articleData, nil
  77. }
  78. }
  79. articleModel, err := h.articleRepository.GetByURL(ctx, url)
  80. if err != nil {
  81. if errors.Is(err, sql.ErrNoRows) {
  82. return nil, fiber.ErrNotFound
  83. }
  84. return nil, customErrors.Wrap(err, "can't get article by url")
  85. }
  86. if !articleModel.IsActive {
  87. return nil, fiber.ErrNotFound
  88. }
  89. article := mapper.ToArticle(lang, *articleModel)
  90. renderData := fiber.Map{
  91. "lang": lang,
  92. "headTitle": i18n.T(lang, "head_title"),
  93. "headDescription": article.MetaDescription,
  94. "headKeywords": article.MetaKeywords,
  95. "pageTitle": i18n.T(lang, "article_page_title", article.Title),
  96. "article": article,
  97. }
  98. h.cacheService.Set(articleCacheKey, renderData, nil)
  99. return renderData, nil
  100. }
  101. func (h articlePage) recentArticles(
  102. ctx context.Context,
  103. lang i18n.Language,
  104. ) ([]dto.ArticlePreview, error) {
  105. recentArticlesCacheKey := RecentArticlesCacheKey + string(lang)
  106. cacheData, found := h.cacheService.Get(recentArticlesCacheKey)
  107. if found {
  108. if articles, ok := cacheData.([]dto.ArticlePreview); ok {
  109. return articles, nil
  110. }
  111. }
  112. articleModels, err := h.articleRepository.GetAllPreview(ctx, i18n.LanguageID(lang))
  113. if err != nil {
  114. return nil, customErrors.Wrap(err, "can't get articles")
  115. }
  116. if len(articleModels) > previewArticlesCount {
  117. articleModels = articleModels[:previewArticlesCount]
  118. }
  119. articles := mapper.ToArticlesPreview(lang, articleModels)
  120. h.cacheService.Set(recentArticlesCacheKey, articles, nil)
  121. return articles, nil
  122. }
  123. func (h articlePage) tags(
  124. ctx context.Context,
  125. lang i18n.Language,
  126. ) ([]dto.Tag, error) {
  127. usedTagsCacheKey := UsedTagsCacheKey + string(lang)
  128. cacheData, found := h.cacheService.Get(usedTagsCacheKey)
  129. if found {
  130. if tagsData, ok := cacheData.([]dto.Tag); ok {
  131. return tagsData, nil
  132. }
  133. }
  134. tagModels, err := h.tagRepository.GetAllUsed(ctx, i18n.LanguageID(lang))
  135. if err != nil {
  136. return nil, customErrors.Wrap(err, "can't get tags")
  137. }
  138. tags := mapper.ToTagsList(tagModels)
  139. h.cacheService.Set(usedTagsCacheKey, tags, nil)
  140. return tags, nil
  141. }