feat: add feature upload file coverimage in article

This commit is contained in:
pahmiudahgede 2025-02-08 03:54:36 +07:00
parent 76bd158b55
commit fc1298e894
4 changed files with 63 additions and 21 deletions

3
.gitignore vendored
View File

@ -27,4 +27,5 @@ go.work.sum
.env.dev .env.dev
# Ignore avatar images # Ignore avatar images
/public/uploads/avatars/ /public/uploads/avatars/
/public/uploads/articles/

View File

@ -29,9 +29,7 @@ func (r *RequestArticleDTO) Validate() (map[string][]string, bool) {
if strings.TrimSpace(r.Title) == "" { if strings.TrimSpace(r.Title) == "" {
errors["title"] = append(errors["title"], "Title is required") errors["title"] = append(errors["title"], "Title is required")
} }
if strings.TrimSpace(r.CoverImage) == "" {
errors["coverImage"] = append(errors["coverImage"], "Cover image is required")
}
if strings.TrimSpace(r.Author) == "" { if strings.TrimSpace(r.Author) == "" {
errors["author"] = append(errors["author"], "Author is required") errors["author"] = append(errors["author"], "Author is required")
} }

View File

@ -18,22 +18,28 @@ func NewArticleHandler(articleService services.ArticleService) *ArticleHandler {
} }
func (h *ArticleHandler) CreateArticle(c *fiber.Ctx) error { func (h *ArticleHandler) CreateArticle(c *fiber.Ctx) error {
var requestArticleDTO dto.RequestArticleDTO
if err := c.BodyParser(&requestArticleDTO); err != nil { var request dto.RequestArticleDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}}) return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
} }
errors, valid := requestArticleDTO.Validate() errors, valid := request.Validate()
if !valid { if !valid {
return utils.ValidationErrorResponse(c, errors) return utils.ValidationErrorResponse(c, errors)
} }
articleResponse, err := h.ArticleService.CreateArticle(requestArticleDTO) coverImage, err := c.FormFile("coverImage")
if err != nil { if err != nil {
return utils.GenericErrorResponse(c, fiber.StatusBadRequest, err.Error()) return utils.GenericErrorResponse(c, fiber.StatusBadRequest, "Cover image is required")
} }
return utils.CreateResponse(c, articleResponse, "Article created successfully") articleResponse, err := h.ArticleService.CreateArticle(request, coverImage)
if err != nil {
return utils.GenericErrorResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.SuccessResponse(c, articleResponse, "Article created successfully")
} }
func (h *ArticleHandler) GetAllArticles(c *fiber.Ctx) error { func (h *ArticleHandler) GetAllArticles(c *fiber.Ctx) error {

View File

@ -2,8 +2,12 @@ package services
import ( import (
"fmt" "fmt"
"mime/multipart"
"os"
"path/filepath"
"time" "time"
"github.com/google/uuid"
"github.com/pahmiudahgede/senggoldong/dto" "github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories" "github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/model" "github.com/pahmiudahgede/senggoldong/model"
@ -11,7 +15,7 @@ import (
) )
type ArticleService interface { type ArticleService interface {
CreateArticle(articleDTO dto.RequestArticleDTO) (*dto.ArticleResponseDTO, error) CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error)
GetAllArticles(page, limit int) ([]dto.ArticleResponseDTO, int, error) GetAllArticles(page, limit int) ([]dto.ArticleResponseDTO, int, error)
GetArticleByID(id string) (*dto.ArticleResponseDTO, error) GetArticleByID(id string) (*dto.ArticleResponseDTO, error)
} }
@ -24,22 +28,55 @@ func NewArticleService(articleRepo repositories.ArticleRepository) ArticleServic
return &articleService{ArticleRepo: articleRepo} return &articleService{ArticleRepo: articleRepo}
} }
func (s *articleService) CreateArticle(articleDTO dto.RequestArticleDTO) (*dto.ArticleResponseDTO, error) { func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error) {
article := &model.Article{ coverImageDir := "./public/uploads/articles"
Title: articleDTO.Title, if _, err := os.Stat(coverImageDir); os.IsNotExist(err) {
CoverImage: articleDTO.CoverImage, err := os.MkdirAll(coverImageDir, os.ModePerm)
Author: articleDTO.Author, if err != nil {
Heading: articleDTO.Heading, return nil, fmt.Errorf("failed to create directory for cover image: %v", err)
Content: articleDTO.Content, }
} }
err := s.ArticleRepo.CreateArticle(article) 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)
if err != nil {
return nil, fmt.Errorf("failed to save cover image: %v", err)
}
article := model.Article{
Title: request.Title,
CoverImage: coverImagePath,
Author: request.Author,
Heading: request.Heading,
Content: request.Content,
}
err = s.ArticleRepo.CreateArticle(&article)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create article: %v", err) return nil, fmt.Errorf("failed to create article: %v", err)
} }
publishedAt, _ := utils.FormatDateToIndonesianFormat(article.PublishedAt) createdAt, _ := utils.FormatDateToIndonesianFormat(article.PublishedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(article.UpdatedAt) updatedAt, _ := utils.FormatDateToIndonesianFormat(article.UpdatedAt)
articleResponseDTO := &dto.ArticleResponseDTO{ articleResponseDTO := &dto.ArticleResponseDTO{
@ -49,7 +86,7 @@ func (s *articleService) CreateArticle(articleDTO dto.RequestArticleDTO) (*dto.A
Author: article.Author, Author: article.Author,
Heading: article.Heading, Heading: article.Heading,
Content: article.Content, Content: article.Content,
PublishedAt: publishedAt, PublishedAt: createdAt,
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
} }