fix: fixing caching and response api
This commit is contained in:
parent
170fc79148
commit
fc38a43050
|
@ -1,6 +1,8 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
@ -43,7 +45,6 @@ func (h *ArticleHandler) CreateArticle(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
func (h *ArticleHandler) GetAllArticles(c *fiber.Ctx) error {
|
||||
|
||||
page, err := strconv.Atoi(c.Query("page", "0"))
|
||||
if err != nil || page < 1 {
|
||||
page = 0
|
||||
|
@ -54,24 +55,17 @@ func (h *ArticleHandler) GetAllArticles(c *fiber.Ctx) error {
|
|||
limit = 0
|
||||
}
|
||||
|
||||
var articles []dto.ArticleResponseDTO
|
||||
var totalArticles int
|
||||
|
||||
if page == 0 && limit == 0 {
|
||||
|
||||
articles, totalArticles, err = h.ArticleService.GetAllArticles(0, 0)
|
||||
if err != nil {
|
||||
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch articles")
|
||||
}
|
||||
|
||||
return utils.NonPaginatedResponse(c, articles, totalArticles, "Articles fetched successfully")
|
||||
}
|
||||
|
||||
articles, totalArticles, err = h.ArticleService.GetAllArticles(page, limit)
|
||||
articles, totalArticles, err := h.ArticleService.GetAllArticles(page, limit)
|
||||
if err != nil {
|
||||
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch articles")
|
||||
}
|
||||
|
||||
fmt.Printf("Total Articles: %d\n", totalArticles)
|
||||
|
||||
if page == 0 && limit == 0 {
|
||||
return utils.NonPaginatedResponse(c, articles, totalArticles, "Articles fetched successfully")
|
||||
}
|
||||
|
||||
return utils.PaginatedResponse(c, articles, page, limit, totalArticles, "Articles fetched successfully")
|
||||
}
|
||||
|
||||
|
@ -83,7 +77,8 @@ func (h *ArticleHandler) GetArticleByID(c *fiber.Ctx) error {
|
|||
|
||||
article, err := h.ArticleService.GetArticleByID(id)
|
||||
if err != nil {
|
||||
return utils.GenericResponse(c, fiber.StatusNotFound, err.Error())
|
||||
|
||||
return utils.GenericResponse(c, fiber.StatusNotFound, "Article not found")
|
||||
}
|
||||
|
||||
return utils.SuccessResponse(c, article, "Article fetched successfully")
|
||||
|
@ -96,7 +91,6 @@ func (h *ArticleHandler) UpdateArticle(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
var request dto.RequestArticleDTO
|
||||
|
||||
if err := c.BodyParser(&request); err != nil {
|
||||
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
|
||||
}
|
||||
|
@ -106,6 +100,7 @@ func (h *ArticleHandler) UpdateArticle(c *fiber.Ctx) error {
|
|||
return utils.ValidationErrorResponse(c, errors)
|
||||
}
|
||||
|
||||
var coverImage *multipart.FileHeader
|
||||
coverImage, err := c.FormFile("coverImage")
|
||||
if err != nil && err.Error() != "no such file" {
|
||||
return utils.GenericResponse(c, fiber.StatusBadRequest, "Cover image is required")
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package repositories
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pahmiudahgede/senggoldong/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
@ -28,7 +30,10 @@ func (r *articleRepository) FindArticleByID(id string) (*model.Article, error) {
|
|||
var article model.Article
|
||||
err := r.DB.Where("id = ?", id).First(&article).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("article with ID %s not found", id)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to fetch article: %v", err)
|
||||
}
|
||||
return &article, nil
|
||||
}
|
||||
|
@ -37,21 +42,21 @@ func (r *articleRepository) FindAllArticles(page, limit int) ([]model.Article, i
|
|||
var articles []model.Article
|
||||
var total int64
|
||||
|
||||
err := r.DB.Model(&model.Article{}).Count(&total).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
if err := r.DB.Model(&model.Article{}).Count(&total).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to count articles: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Total Articles Count: %d\n", total)
|
||||
|
||||
if page > 0 && limit > 0 {
|
||||
err := r.DB.Offset((page - 1) * limit).Limit(limit).Find(&articles).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, 0, fmt.Errorf("failed to fetch articles: %v", err)
|
||||
}
|
||||
} else {
|
||||
|
||||
err := r.DB.Find(&articles).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, 0, fmt.Errorf("failed to fetch articles: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,5 +64,5 @@ func (r *articleRepository) FindAllArticles(page, limit int) ([]model.Article, i
|
|||
}
|
||||
|
||||
func (r *articleRepository) UpdateArticle(id string, article *model.Article) error {
|
||||
return r.DB.Save(article).Error
|
||||
return r.DB.Model(&model.Article{}).Where("id = ?", id).Updates(article).Error
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
|
@ -36,8 +37,9 @@ func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage
|
|||
return nil, fmt.Errorf("failed to create directory for cover image: %v", err)
|
||||
}
|
||||
|
||||
allowedExtensions := map[string]bool{".jpg": true, ".jpeg": true, ".png": true}
|
||||
extension := filepath.Ext(coverImage.Filename)
|
||||
if extension != ".jpg" && extension != ".jpeg" && extension != ".png" {
|
||||
if !allowedExtensions[extension] {
|
||||
return nil, fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
|
||||
}
|
||||
|
||||
|
@ -90,83 +92,77 @@ func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage
|
|||
cacheData := map[string]interface{}{
|
||||
"data": articleResponseDTO,
|
||||
}
|
||||
|
||||
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
|
||||
|
||||
fmt.Printf("Error caching article to Redis: %v\n", err)
|
||||
}
|
||||
|
||||
articlesCacheKey := "articles:all"
|
||||
if err := utils.DeleteData(articlesCacheKey); err != nil {
|
||||
|
||||
fmt.Printf("Error deleting articles cache: %v\n", err)
|
||||
articles, total, err := s.ArticleRepo.FindAllArticles(0, 0)
|
||||
if err != nil {
|
||||
fmt.Printf("Error fetching all articles: %v\n", err)
|
||||
}
|
||||
|
||||
articles, _, err := s.ArticleRepo.FindAllArticles(0, 0)
|
||||
if err == nil {
|
||||
var articleDTOs []dto.ArticleResponseDTO
|
||||
for _, a := range articles {
|
||||
createdAt, _ := utils.FormatDateToIndonesianFormat(a.PublishedAt)
|
||||
updatedAt, _ := utils.FormatDateToIndonesianFormat(a.UpdatedAt)
|
||||
var articleDTOs []dto.ArticleResponseDTO
|
||||
for _, a := range articles {
|
||||
createdAt, _ := utils.FormatDateToIndonesianFormat(a.PublishedAt)
|
||||
updatedAt, _ := utils.FormatDateToIndonesianFormat(a.UpdatedAt)
|
||||
|
||||
articleDTOs = append(articleDTOs, dto.ArticleResponseDTO{
|
||||
ID: a.ID,
|
||||
Title: a.Title,
|
||||
CoverImage: a.CoverImage,
|
||||
Author: a.Author,
|
||||
Heading: a.Heading,
|
||||
Content: a.Content,
|
||||
PublishedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
})
|
||||
}
|
||||
articleDTOs = append(articleDTOs, dto.ArticleResponseDTO{
|
||||
ID: a.ID,
|
||||
Title: a.Title,
|
||||
CoverImage: a.CoverImage,
|
||||
Author: a.Author,
|
||||
Heading: a.Heading,
|
||||
Content: a.Content,
|
||||
PublishedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
cacheData = map[string]interface{}{
|
||||
"data": articleDTOs,
|
||||
}
|
||||
if err := utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24); err != nil {
|
||||
|
||||
fmt.Printf("Error caching all articles to Redis: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
|
||||
fmt.Printf("Error fetching all articles: %v\n", err)
|
||||
articlesCacheKey := "articles:all"
|
||||
cacheData = map[string]interface{}{
|
||||
"data": articleDTOs,
|
||||
"total": total,
|
||||
}
|
||||
if err := utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24); err != nil {
|
||||
fmt.Printf("Error caching all articles to Redis: %v\n", err)
|
||||
}
|
||||
|
||||
return articleResponseDTO, nil
|
||||
}
|
||||
|
||||
func (s *articleService) GetAllArticles(page, limit int) ([]dto.ArticleResponseDTO, int, error) {
|
||||
var cacheKey string
|
||||
|
||||
cacheKey := fmt.Sprintf("articles_page:%d_limit:%d", page, limit)
|
||||
if page == 0 && limit == 0 {
|
||||
cacheKey = "articles:all"
|
||||
cachedData, err := utils.GetJSONData(cacheKey)
|
||||
if err == nil && cachedData != nil {
|
||||
if data, ok := cachedData["data"].([]interface{}); ok {
|
||||
var articles []dto.ArticleResponseDTO
|
||||
for _, item := range data {
|
||||
articleData, ok := item.(map[string]interface{})
|
||||
if ok {
|
||||
articles = append(articles, dto.ArticleResponseDTO{
|
||||
ID: articleData["article_id"].(string),
|
||||
Title: articleData["title"].(string),
|
||||
CoverImage: articleData["coverImage"].(string),
|
||||
Author: articleData["author"].(string),
|
||||
Heading: articleData["heading"].(string),
|
||||
Content: articleData["content"].(string),
|
||||
PublishedAt: articleData["publishedAt"].(string),
|
||||
UpdatedAt: articleData["updatedAt"].(string),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
cachedData, err := utils.GetJSONData(cacheKey)
|
||||
if err == nil && cachedData != nil {
|
||||
var articles []dto.ArticleResponseDTO
|
||||
|
||||
if data, ok := cachedData["data"].([]interface{}); ok {
|
||||
for _, item := range data {
|
||||
articleData, ok := item.(map[string]interface{})
|
||||
if ok {
|
||||
|
||||
articles = append(articles, dto.ArticleResponseDTO{
|
||||
ID: articleData["article_id"].(string),
|
||||
Title: articleData["title"].(string),
|
||||
CoverImage: articleData["coverImage"].(string),
|
||||
Author: articleData["author"].(string),
|
||||
Heading: articleData["heading"].(string),
|
||||
Content: articleData["content"].(string),
|
||||
PublishedAt: articleData["publishedAt"].(string),
|
||||
UpdatedAt: articleData["updatedAt"].(string),
|
||||
})
|
||||
if total, ok := cachedData["total"].(float64); ok {
|
||||
fmt.Printf("Cached Total Articles: %f\n", total)
|
||||
return articles, int(total), nil
|
||||
} else {
|
||||
fmt.Println("Total articles not found in cache, using 0 as fallback.")
|
||||
return articles, 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
total, ok := cachedData["total"].(float64)
|
||||
if !ok {
|
||||
return nil, 0, fmt.Errorf("invalid total count in cache")
|
||||
}
|
||||
return articles, int(total), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,6 +171,8 @@ func (s *articleService) GetAllArticles(page, limit int) ([]dto.ArticleResponseD
|
|||
return nil, 0, fmt.Errorf("failed to fetch articles: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Total Articles from Database: %d\n", total)
|
||||
|
||||
var articleDTOs []dto.ArticleResponseDTO
|
||||
for _, article := range articles {
|
||||
publishedAt, _ := utils.FormatDateToIndonesianFormat(article.PublishedAt)
|
||||
|
@ -192,12 +190,14 @@ func (s *articleService) GetAllArticles(page, limit int) ([]dto.ArticleResponseD
|
|||
})
|
||||
}
|
||||
|
||||
cacheKey = fmt.Sprintf("articles_page:%d_limit:%d", page, limit)
|
||||
cacheData := map[string]interface{}{
|
||||
"data": articleDTOs,
|
||||
"total": total,
|
||||
}
|
||||
err = utils.SetJSONData(cacheKey, cacheData, time.Hour*24)
|
||||
if err != nil {
|
||||
|
||||
fmt.Printf("Setting cache with total: %d\n", total)
|
||||
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
|
||||
fmt.Printf("Error caching articles to Redis: %v\n", err)
|
||||
}
|
||||
|
||||
|
@ -205,24 +205,15 @@ func (s *articleService) GetAllArticles(page, limit int) ([]dto.ArticleResponseD
|
|||
}
|
||||
|
||||
func (s *articleService) GetArticleByID(id string) (*dto.ArticleResponseDTO, error) {
|
||||
cacheKey := fmt.Sprintf("article:%s", id)
|
||||
|
||||
cacheKey := fmt.Sprintf("article:%s", id)
|
||||
cachedData, err := utils.GetJSONData(cacheKey)
|
||||
if err == nil && cachedData != nil {
|
||||
articleData, ok := cachedData["data"].(map[string]interface{})
|
||||
if ok {
|
||||
|
||||
article := dto.ArticleResponseDTO{
|
||||
ID: articleData["article_id"].(string),
|
||||
Title: articleData["title"].(string),
|
||||
CoverImage: articleData["coverImage"].(string),
|
||||
Author: articleData["author"].(string),
|
||||
Heading: articleData["heading"].(string),
|
||||
Content: articleData["content"].(string),
|
||||
PublishedAt: articleData["publishedAt"].(string),
|
||||
UpdatedAt: articleData["updatedAt"].(string),
|
||||
articleResponse := &dto.ArticleResponseDTO{}
|
||||
if data, ok := cachedData["data"].(string); ok {
|
||||
if err := json.Unmarshal([]byte(data), articleResponse); err == nil {
|
||||
return articleResponse, nil
|
||||
}
|
||||
return &article, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,9 +239,7 @@ func (s *articleService) GetArticleByID(id string) (*dto.ArticleResponseDTO, err
|
|||
cacheData := map[string]interface{}{
|
||||
"data": articleResponseDTO,
|
||||
}
|
||||
err = utils.SetJSONData(cacheKey, cacheData, time.Hour*24)
|
||||
if err != nil {
|
||||
|
||||
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
|
||||
fmt.Printf("Error caching article to Redis: %v\n", err)
|
||||
}
|
||||
|
||||
|
@ -258,6 +247,7 @@ func (s *articleService) GetArticleByID(id string) (*dto.ArticleResponseDTO, err
|
|||
}
|
||||
|
||||
func (s *articleService) UpdateArticle(id string, request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error) {
|
||||
|
||||
article, err := s.ArticleRepo.FindArticleByID(id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("article not found: %v", err)
|
||||
|
@ -270,39 +260,10 @@ func (s *articleService) UpdateArticle(id string, request dto.RequestArticleDTO,
|
|||
|
||||
var coverImagePath string
|
||||
if coverImage != nil {
|
||||
coverImageDir := "./public/uploads/articles"
|
||||
if _, err := os.Stat(coverImageDir); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(coverImageDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory for cover image: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
extension := filepath.Ext(coverImage.Filename)
|
||||
if extension != ".jpg" && extension != ".jpeg" && extension != ".png" {
|
||||
return nil, fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
|
||||
}
|
||||
|
||||
coverImageFileName := fmt.Sprintf("%s_cover%s", uuid.New().String(), extension)
|
||||
coverImagePath = filepath.Join(coverImageDir, coverImageFileName)
|
||||
|
||||
src, err := coverImage.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open uploaded file: %v", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(coverImagePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create cover image file: %v", err)
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
_, err = dst.ReadFrom(src)
|
||||
coverImagePath, err = s.saveCoverImage(coverImage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to save cover image: %v", err)
|
||||
}
|
||||
|
||||
article.CoverImage = coverImagePath
|
||||
}
|
||||
|
||||
|
@ -331,15 +292,7 @@ func (s *articleService) UpdateArticle(id string, request dto.RequestArticleDTO,
|
|||
}
|
||||
|
||||
articleCacheKey := fmt.Sprintf("article:%s", updatedArticle.ID)
|
||||
err = utils.DeleteData(articleCacheKey)
|
||||
if err != nil {
|
||||
fmt.Printf("Error deleting old cache for article: %v\n", err)
|
||||
}
|
||||
|
||||
cacheData := map[string]interface{}{
|
||||
"data": articleResponseDTO,
|
||||
}
|
||||
err = utils.SetJSONData(articleCacheKey, cacheData, time.Hour*24)
|
||||
err = utils.SetJSONData(articleCacheKey, map[string]interface{}{"data": articleResponseDTO}, time.Hour*24)
|
||||
if err != nil {
|
||||
fmt.Printf("Error caching updated article to Redis: %v\n", err)
|
||||
}
|
||||
|
@ -371,7 +324,7 @@ func (s *articleService) UpdateArticle(id string, request dto.RequestArticleDTO,
|
|||
})
|
||||
}
|
||||
|
||||
cacheData = map[string]interface{}{
|
||||
cacheData := map[string]interface{}{
|
||||
"data": articleDTOs,
|
||||
}
|
||||
err = utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24)
|
||||
|
@ -382,3 +335,39 @@ func (s *articleService) UpdateArticle(id string, request dto.RequestArticleDTO,
|
|||
|
||||
return articleResponseDTO, nil
|
||||
}
|
||||
|
||||
func (s *articleService) saveCoverImage(coverImage *multipart.FileHeader) (string, error) {
|
||||
coverImageDir := "./public/uploads/articles"
|
||||
if _, err := os.Stat(coverImageDir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(coverImageDir, os.ModePerm); err != nil {
|
||||
return "", fmt.Errorf("failed to create directory for cover image: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
extension := filepath.Ext(coverImage.Filename)
|
||||
if extension != ".jpg" && extension != ".jpeg" && extension != ".png" {
|
||||
return "", fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
|
||||
}
|
||||
|
||||
coverImageFileName := fmt.Sprintf("%s_cover%s", uuid.New().String(), extension)
|
||||
coverImagePath := filepath.Join(coverImageDir, coverImageFileName)
|
||||
|
||||
src, err := coverImage.Open()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open uploaded file: %v", err)
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(coverImagePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create cover image file: %v", err)
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
_, err = dst.ReadFrom(src)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to save cover image: %v", err)
|
||||
}
|
||||
|
||||
return coverImagePath, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue