diff --git a/domain/article.go b/domain/article.go index a30cf98..1006852 100644 --- a/domain/article.go +++ b/domain/article.go @@ -3,12 +3,12 @@ package domain import "time" type Article struct { - ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` - Title string `gorm:"not null" json:"title"` - CoverImage string `gorm:"not null" json:"coverImage"` - Author string `gorm:"not null" json:"author"` - Heading string `gorm:"not null" json:"heading"` - Content string `gorm:"not null" json:"content"` - PublishedAt time.Time `gorm:"not null" json:"publishedAt"` - UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` + ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` + Title string `gorm:"not null" json:"title"` + CoverImage string `gorm:"not null" json:"coverImage"` + Author string `gorm:"not null" json:"author"` + Heading string `gorm:"not null" json:"heading"` + Content string `gorm:"not null" json:"content"` + PublishedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"` + UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"` } diff --git a/internal/controllers/article.go b/internal/controllers/article.go index ec2381b..673000f 100644 --- a/internal/controllers/article.go +++ b/internal/controllers/article.go @@ -13,13 +13,12 @@ func CreateArticle(c *fiber.Ctx) error { if err := c.BodyParser(&articleRequest); err != nil { return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( fiber.StatusBadRequest, - "Invalid input", + "Input tidak valid", nil, )) } if err := articleRequest.ValidatePostArticle(); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( fiber.StatusBadRequest, err.Error(), @@ -31,7 +30,7 @@ func CreateArticle(c *fiber.Ctx) error { if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( fiber.StatusInternalServerError, - "Failed to create article", + "Gagal membuat artikel", nil, )) } @@ -52,7 +51,7 @@ func CreateArticle(c *fiber.Ctx) error { return c.Status(fiber.StatusCreated).JSON(utils.FormatResponse( fiber.StatusCreated, - "Article created successfully", + "Artikel berhasil dibuat", response, )) } @@ -63,7 +62,7 @@ func GetArticles(c *fiber.Ctx) error { if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( fiber.StatusInternalServerError, - "Failed to fetch articles", + "Gagal mengambil artikel", nil, )) } @@ -71,14 +70,13 @@ func GetArticles(c *fiber.Ctx) error { if len(articles) == 0 { return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( fiber.StatusOK, - "Articles fetched successfully but data is empty", + "Artikel berhasil diambil tetapi data kosong", []dto.FormattedResponse{}, )) } var formattedArticles []dto.FormattedResponse for _, article := range articles { - article.PublishedAtFormatted = utils.FormatDateToIndonesianFormat(article.PublishedAt) article.UpdatedAtFormatted = utils.FormatDateToIndonesianFormat(article.UpdatedAt) @@ -96,19 +94,20 @@ func GetArticles(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( fiber.StatusOK, - "Articles fetched successfully", + "Artikel berhasil diambil", formattedArticles, )) } func GetArticleByID(c *fiber.Ctx) error { + id := c.Params("id") article, err := services.GetArticleByID(id) if err != nil { return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse( fiber.StatusNotFound, - "Article not found", + "Artikel tidak ditemukan", nil, )) } @@ -129,7 +128,7 @@ func GetArticleByID(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( fiber.StatusOK, - "Article fetched successfully", + "Artikel berhasil diambil", response, )) } @@ -142,13 +141,12 @@ func UpdateArticle(c *fiber.Ctx) error { if err := c.BodyParser(&articleUpdateRequest); err != nil { return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( fiber.StatusBadRequest, - "Invalid input", + "Input tidak valid", nil, )) } if err := articleUpdateRequest.ValidateUpdateArticle(); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( fiber.StatusBadRequest, err.Error(), @@ -158,9 +156,17 @@ func UpdateArticle(c *fiber.Ctx) error { updatedArticle, err := services.UpdateArticle(id, &articleUpdateRequest) if err != nil { + + if err.Error() == "record not found" { + return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse( + fiber.StatusNotFound, + "id article tidak diketahui", + nil, + )) + } return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( fiber.StatusInternalServerError, - "Failed to update article", + "Gagal memperbarui artikel", nil, )) } @@ -181,7 +187,7 @@ func UpdateArticle(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( fiber.StatusOK, - "Article updated successfully", + "Artikel berhasil diperbarui", response, )) } @@ -191,16 +197,24 @@ func DeleteArticle(c *fiber.Ctx) error { err := services.DeleteArticle(id) if err != nil { + + if err.Error() == "record not found" { + return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse( + fiber.StatusNotFound, + "id article tidak diketahui", + nil, + )) + } return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( fiber.StatusInternalServerError, - "Failed to delete article", + "Gagal menghapus artikel", nil, )) } return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( fiber.StatusOK, - "Article deleted successfully", + "Artikel berhasil dihapus", nil, )) } diff --git a/internal/repositories/article.go b/internal/repositories/article.go index b19d253..fba1b4e 100644 --- a/internal/repositories/article.go +++ b/internal/repositories/article.go @@ -1,49 +1,87 @@ package repositories import ( + "encoding/json" + "time" + "github.com/pahmiudahgede/senggoldong/config" "github.com/pahmiudahgede/senggoldong/domain" + "golang.org/x/net/context" ) +var ctx = context.Background() + func CreateArticle(article *domain.Article) error { err := config.DB.Create(article).Error if err != nil { return err } + config.RedisClient.Del(ctx, "articles") return nil } func GetArticles() ([]domain.Article, error) { var articles []domain.Article - err := config.DB.Find(&articles).Error + + cachedArticles, err := config.RedisClient.Get(ctx, "articles").Result() + if err == nil { + err := json.Unmarshal([]byte(cachedArticles), &articles) + if err != nil { + return nil, err + } + return articles, nil + } + + err = config.DB.Find(&articles).Error if err != nil { return nil, err } + + articlesJSON, _ := json.Marshal(articles) + config.RedisClient.Set(ctx, "articles", articlesJSON, 10*time.Minute) + return articles, nil } func GetArticleByID(id string) (domain.Article, error) { var article domain.Article - err := config.DB.Where("id = ?", id).First(&article).Error + + cachedArticle, err := config.RedisClient.Get(ctx, "article:"+id).Result() + if err == nil { + err := json.Unmarshal([]byte(cachedArticle), &article) + if err != nil { + return article, err + } + return article, nil + } + + err = config.DB.Where("id = ?", id).First(&article).Error if err != nil { return article, err } + + articleJSON, _ := json.Marshal(article) + config.RedisClient.Set(ctx, "article:"+id, articleJSON, 10*time.Minute) + return article, nil } func UpdateArticle(article *domain.Article) error { - err := config.DB.Save(article).Error if err != nil { return err } + + config.RedisClient.Del(ctx, "article:"+article.ID) + config.RedisClient.Del(ctx, "articles") + return nil } func DeleteArticle(id string) error { - var article domain.Article + err := config.DB.Where("id = ?", id).First(&article).Error if err != nil { return err @@ -53,5 +91,9 @@ func DeleteArticle(id string) error { if err != nil { return err } + + config.RedisClient.Del(ctx, "article:"+id) + config.RedisClient.Del(ctx, "articles") + return nil -} +} \ No newline at end of file diff --git a/internal/services/article.go b/internal/services/article.go index 51a9318..9d2a77c 100644 --- a/internal/services/article.go +++ b/internal/services/article.go @@ -1,22 +1,25 @@ package services import ( + "context" + "encoding/json" "time" + "github.com/pahmiudahgede/senggoldong/config" "github.com/pahmiudahgede/senggoldong/domain" "github.com/pahmiudahgede/senggoldong/dto" "github.com/pahmiudahgede/senggoldong/internal/repositories" ) +var ctx = context.Background() + func CreateArticle(articleRequest *dto.ArticleRequest) (dto.ArticleResponse, error) { article := domain.Article{ - Title: articleRequest.Title, - CoverImage: articleRequest.CoverImage, - Author: articleRequest.Author, - Heading: articleRequest.Heading, - Content: articleRequest.Content, - PublishedAt: time.Now(), - UpdatedAt: time.Now(), + Title: articleRequest.Title, + CoverImage: articleRequest.CoverImage, + Author: articleRequest.Author, + Heading: articleRequest.Heading, + Content: articleRequest.Content, } err := repositories.CreateArticle(&article) @@ -24,6 +27,8 @@ func CreateArticle(articleRequest *dto.ArticleRequest) (dto.ArticleResponse, err return dto.ArticleResponse{}, err } + config.RedisClient.Del(ctx, "articles") + articleResponse := dto.ArticleResponse{ ID: article.ID, Title: article.Title, @@ -39,12 +44,22 @@ func CreateArticle(articleRequest *dto.ArticleRequest) (dto.ArticleResponse, err } func GetArticles() ([]dto.ArticleResponse, error) { + var response []dto.ArticleResponse + + cachedArticles, err := config.RedisClient.Get(ctx, "articles").Result() + if err == nil { + err := json.Unmarshal([]byte(cachedArticles), &response) + if err != nil { + return nil, err + } + return response, nil + } + articles, err := repositories.GetArticles() if err != nil { return nil, err } - var response []dto.ArticleResponse for _, article := range articles { response = append(response, dto.ArticleResponse{ ID: article.ID, @@ -57,15 +72,30 @@ func GetArticles() ([]dto.ArticleResponse, error) { UpdatedAt: article.UpdatedAt, }) } + + articlesJSON, _ := json.Marshal(response) + config.RedisClient.Set(ctx, "articles", articlesJSON, 10*time.Minute) + return response, nil } func GetArticleByID(id string) (dto.ArticleResponse, error) { + cachedArticle, err := config.RedisClient.Get(ctx, "article:"+id).Result() + if err == nil { + var article dto.ArticleResponse + err := json.Unmarshal([]byte(cachedArticle), &article) + if err != nil { + return article, err + } + return article, nil + } + article, err := repositories.GetArticleByID(id) if err != nil { return dto.ArticleResponse{}, err } - return dto.ArticleResponse{ + + articleResponse := dto.ArticleResponse{ ID: article.ID, Title: article.Title, CoverImage: article.CoverImage, @@ -74,7 +104,12 @@ func GetArticleByID(id string) (dto.ArticleResponse, error) { Content: article.Content, PublishedAt: article.PublishedAt, UpdatedAt: article.UpdatedAt, - }, nil + } + + articleJSON, _ := json.Marshal(articleResponse) + config.RedisClient.Set(ctx, "article:"+id, articleJSON, 10*time.Minute) + + return articleResponse, nil } func UpdateArticle(id string, articleUpdateRequest *dto.ArticleUpdateRequest) (*dto.ArticleResponse, error) { @@ -96,7 +131,10 @@ func UpdateArticle(id string, articleUpdateRequest *dto.ArticleUpdateRequest) (* return nil, err } - return &dto.ArticleResponse{ + config.RedisClient.Del(ctx, "article:"+id) + config.RedisClient.Del(ctx, "articles") + + updatedArticleResponse := dto.ArticleResponse{ ID: article.ID, Title: article.Title, CoverImage: article.CoverImage, @@ -105,7 +143,9 @@ func UpdateArticle(id string, articleUpdateRequest *dto.ArticleUpdateRequest) (* Content: article.Content, PublishedAt: article.PublishedAt, UpdatedAt: article.UpdatedAt, - }, nil + } + + return &updatedArticleResponse, nil } func DeleteArticle(id string) error { @@ -114,5 +154,9 @@ func DeleteArticle(id string) error { if err != nil { return err } + + config.RedisClient.Del(ctx, "article:"+id) + config.RedisClient.Del(ctx, "articles") + return nil -} +} \ No newline at end of file