416 lines
12 KiB
Go
416 lines
12 KiB
Go
package services
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"mime/multipart"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"rijig/dto"
|
|
"rijig/internal/repositories"
|
|
"rijig/model"
|
|
"rijig/utils"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type ArticleService interface {
|
|
CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error)
|
|
GetAllArticles(page, limit int) ([]dto.ArticleResponseDTO, int, error)
|
|
GetArticleByID(id string) (*dto.ArticleResponseDTO, error)
|
|
UpdateArticle(id string, request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error)
|
|
DeleteArticle(id string) error
|
|
}
|
|
|
|
type articleService struct {
|
|
ArticleRepo repositories.ArticleRepository
|
|
}
|
|
|
|
func NewArticleService(articleRepo repositories.ArticleRepository) ArticleService {
|
|
return &articleService{ArticleRepo: articleRepo}
|
|
}
|
|
|
|
func (s *articleService) saveCoverArticle(coverArticle *multipart.FileHeader) (string, error) {
|
|
pathImage := "/uploads/articles/"
|
|
coverArticleDir := "./public" + os.Getenv("BASE_URL") + pathImage
|
|
if _, err := os.Stat(coverArticleDir); os.IsNotExist(err) {
|
|
|
|
if err := os.MkdirAll(coverArticleDir, os.ModePerm); err != nil {
|
|
return "", fmt.Errorf("failed to create directory for cover article: %v", err)
|
|
}
|
|
}
|
|
|
|
allowedExtensions := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".svg": true}
|
|
extension := filepath.Ext(coverArticle.Filename)
|
|
if !allowedExtensions[extension] {
|
|
return "", fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
|
|
}
|
|
|
|
coverArticleFileName := fmt.Sprintf("%s_coverarticle%s", uuid.New().String(), extension)
|
|
coverArticlePath := filepath.Join(coverArticleDir, coverArticleFileName)
|
|
|
|
src, err := coverArticle.Open()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to open uploaded file: %v", err)
|
|
}
|
|
defer src.Close()
|
|
|
|
dst, err := os.Create(coverArticlePath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create cover article file: %v", err)
|
|
}
|
|
defer dst.Close()
|
|
|
|
if _, err := dst.ReadFrom(src); err != nil {
|
|
return "", fmt.Errorf("failed to save cover article: %v", err)
|
|
}
|
|
|
|
iconTrashUrl := fmt.Sprintf("%s%s", pathImage, coverArticleFileName)
|
|
|
|
return iconTrashUrl, nil
|
|
}
|
|
|
|
func deleteCoverArticle(imagePath string) error {
|
|
if imagePath == "" {
|
|
return nil
|
|
}
|
|
|
|
baseDir := "./public/" + os.Getenv("BASE_URL")
|
|
absolutePath := baseDir + imagePath
|
|
|
|
if _, err := os.Stat(absolutePath); os.IsNotExist(err) {
|
|
return fmt.Errorf("image file not found: %v", err)
|
|
}
|
|
|
|
err := os.Remove(absolutePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete image: %v", err)
|
|
}
|
|
|
|
log.Printf("Image deleted successfully: %s", absolutePath)
|
|
return nil
|
|
}
|
|
|
|
func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error) {
|
|
|
|
coverArticlePath, err := s.saveCoverArticle(coverImage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("gagal menyimpan ikon sampah: %v", err)
|
|
}
|
|
|
|
article := model.Article{
|
|
Title: request.Title,
|
|
CoverImage: coverArticlePath,
|
|
Author: request.Author,
|
|
Heading: request.Heading,
|
|
Content: request.Content,
|
|
}
|
|
|
|
if err := s.ArticleRepo.CreateArticle(&article); err != nil {
|
|
return nil, fmt.Errorf("failed to create article: %v", err)
|
|
}
|
|
|
|
createdAt, _ := utils.FormatDateToIndonesianFormat(article.PublishedAt)
|
|
updatedAt, _ := utils.FormatDateToIndonesianFormat(article.UpdatedAt)
|
|
|
|
articleResponseDTO := &dto.ArticleResponseDTO{
|
|
ID: article.ID,
|
|
Title: article.Title,
|
|
CoverImage: article.CoverImage,
|
|
Author: article.Author,
|
|
Heading: article.Heading,
|
|
Content: article.Content,
|
|
PublishedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
}
|
|
|
|
cacheKey := fmt.Sprintf("article:%s", article.ID)
|
|
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)
|
|
}
|
|
|
|
articles, total, err := s.ArticleRepo.FindAllArticles(0, 0)
|
|
if err != nil {
|
|
fmt.Printf("Error fetching all articles: %v\n", err)
|
|
}
|
|
|
|
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,
|
|
})
|
|
}
|
|
|
|
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
|
|
|
|
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),
|
|
})
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
articles, total, err := s.ArticleRepo.FindAllArticles(page, limit)
|
|
if err != nil {
|
|
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)
|
|
updatedAt, _ := utils.FormatDateToIndonesianFormat(article.UpdatedAt)
|
|
|
|
articleDTOs = append(articleDTOs, dto.ArticleResponseDTO{
|
|
ID: article.ID,
|
|
Title: article.Title,
|
|
CoverImage: article.CoverImage,
|
|
Author: article.Author,
|
|
Heading: article.Heading,
|
|
Content: article.Content,
|
|
PublishedAt: publishedAt,
|
|
UpdatedAt: updatedAt,
|
|
})
|
|
}
|
|
|
|
cacheKey = fmt.Sprintf("articles_page:%d_limit:%d", page, limit)
|
|
cacheData := map[string]interface{}{
|
|
"data": articleDTOs,
|
|
"total": total,
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
return articleDTOs, total, nil
|
|
}
|
|
|
|
func (s *articleService) GetArticleByID(id string) (*dto.ArticleResponseDTO, error) {
|
|
|
|
cacheKey := fmt.Sprintf("article:%s", id)
|
|
cachedData, err := utils.GetJSONData(cacheKey)
|
|
if err == nil && cachedData != nil {
|
|
articleResponse := &dto.ArticleResponseDTO{}
|
|
if data, ok := cachedData["data"].(string); ok {
|
|
if err := json.Unmarshal([]byte(data), articleResponse); err == nil {
|
|
return articleResponse, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
article, err := s.ArticleRepo.FindArticleByID(id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to fetch article by ID: %v", err)
|
|
}
|
|
|
|
createdAt, _ := utils.FormatDateToIndonesianFormat(article.PublishedAt)
|
|
updatedAt, _ := utils.FormatDateToIndonesianFormat(article.UpdatedAt)
|
|
|
|
articleResponseDTO := &dto.ArticleResponseDTO{
|
|
ID: article.ID,
|
|
Title: article.Title,
|
|
CoverImage: article.CoverImage,
|
|
Author: article.Author,
|
|
Heading: article.Heading,
|
|
Content: article.Content,
|
|
PublishedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
return articleResponseDTO, nil
|
|
}
|
|
|
|
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", id)
|
|
}
|
|
|
|
if article.CoverImage != "" {
|
|
err := deleteCoverArticle(article.CoverImage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to delete old image: %v", err)
|
|
}
|
|
}
|
|
|
|
var coverArticlePath string
|
|
if coverImage != nil {
|
|
coverArticlePath, err = s.saveCoverArticle(coverImage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to save card photo: %v", err)
|
|
}
|
|
}
|
|
|
|
if coverArticlePath != "" {
|
|
article.CoverImage = coverArticlePath
|
|
}
|
|
|
|
article.Title = request.Title
|
|
article.Heading = request.Heading
|
|
article.Content = request.Content
|
|
article.Author = request.Author
|
|
|
|
err = s.ArticleRepo.UpdateArticle(id, article)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to update article: %v", err)
|
|
}
|
|
|
|
updatedArticle, err := s.ArticleRepo.FindArticleByID(id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to fetch updated article: %v", err)
|
|
}
|
|
|
|
createdAt, _ := utils.FormatDateToIndonesianFormat(updatedArticle.PublishedAt)
|
|
updatedAt, _ := utils.FormatDateToIndonesianFormat(updatedArticle.UpdatedAt)
|
|
|
|
articleResponseDTO := &dto.ArticleResponseDTO{
|
|
ID: updatedArticle.ID,
|
|
Title: updatedArticle.Title,
|
|
CoverImage: updatedArticle.CoverImage,
|
|
Author: updatedArticle.Author,
|
|
Heading: updatedArticle.Heading,
|
|
Content: updatedArticle.Content,
|
|
PublishedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
}
|
|
|
|
articleCacheKey := fmt.Sprintf("article:%s", updatedArticle.ID)
|
|
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)
|
|
}
|
|
|
|
articlesCacheKey := "articles:all"
|
|
err = utils.DeleteData(articlesCacheKey)
|
|
if err != nil {
|
|
fmt.Printf("Error deleting articles cache: %v\n", err)
|
|
}
|
|
|
|
articles, _, err := s.ArticleRepo.FindAllArticles(0, 0)
|
|
if err != nil {
|
|
fmt.Printf("Error fetching all articles: %v\n", err)
|
|
} else {
|
|
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,
|
|
})
|
|
}
|
|
|
|
cacheData := map[string]interface{}{
|
|
"data": articleDTOs,
|
|
}
|
|
err = utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24)
|
|
if err != nil {
|
|
fmt.Printf("Error caching updated articles to Redis: %v\n", err)
|
|
}
|
|
}
|
|
|
|
return articleResponseDTO, nil
|
|
}
|
|
|
|
func (s *articleService) DeleteArticle(id string) error {
|
|
article, err := s.ArticleRepo.FindArticleByID(id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find article: %v", id)
|
|
}
|
|
|
|
if err := deleteCoverArticle(article.CoverImage); err != nil {
|
|
return fmt.Errorf("error waktu menghapus cover image article %s: %v", id, err)
|
|
}
|
|
|
|
err = s.ArticleRepo.DeleteArticle(id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete article: %v", err)
|
|
}
|
|
|
|
articleCacheKey := fmt.Sprintf("article:%s", id)
|
|
err = utils.DeleteData(articleCacheKey)
|
|
if err != nil {
|
|
fmt.Printf("Error deleting cache for article: %v\n", err)
|
|
}
|
|
|
|
articlesCacheKey := "articles:all"
|
|
err = utils.DeleteData(articlesCacheKey)
|
|
if err != nil {
|
|
fmt.Printf("Error deleting cache for all articles: %v\n", err)
|
|
}
|
|
|
|
return nil
|
|
}
|