From fc1298e8943e69a39e9110774aa9dc7817101e07 Mon Sep 17 00:00:00 2001 From: pahmiudahgede Date: Sat, 8 Feb 2025 03:54:36 +0700 Subject: [PATCH] feat: add feature upload file coverimage in article --- .gitignore | 3 +- dto/article_dto.go | 4 +- internal/handler/article_handler.go | 18 ++++++--- internal/services/article_service.go | 59 ++++++++++++++++++++++------ 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index d929b21..ffcfdc8 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ go.work.sum .env.dev # Ignore avatar images -/public/uploads/avatars/ \ No newline at end of file +/public/uploads/avatars/ +/public/uploads/articles/ \ No newline at end of file diff --git a/dto/article_dto.go b/dto/article_dto.go index dea36b8..1df6ac2 100644 --- a/dto/article_dto.go +++ b/dto/article_dto.go @@ -29,9 +29,7 @@ func (r *RequestArticleDTO) Validate() (map[string][]string, bool) { if strings.TrimSpace(r.Title) == "" { 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) == "" { errors["author"] = append(errors["author"], "Author is required") } diff --git a/internal/handler/article_handler.go b/internal/handler/article_handler.go index 59db089..02c892c 100644 --- a/internal/handler/article_handler.go +++ b/internal/handler/article_handler.go @@ -18,22 +18,28 @@ func NewArticleHandler(articleService services.ArticleService) *ArticleHandler { } 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"}}) } - errors, valid := requestArticleDTO.Validate() + errors, valid := request.Validate() if !valid { return utils.ValidationErrorResponse(c, errors) } - articleResponse, err := h.ArticleService.CreateArticle(requestArticleDTO) + coverImage, err := c.FormFile("coverImage") 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 { diff --git a/internal/services/article_service.go b/internal/services/article_service.go index f766e5e..01814a4 100644 --- a/internal/services/article_service.go +++ b/internal/services/article_service.go @@ -2,8 +2,12 @@ package services import ( "fmt" + "mime/multipart" + "os" + "path/filepath" "time" + "github.com/google/uuid" "github.com/pahmiudahgede/senggoldong/dto" "github.com/pahmiudahgede/senggoldong/internal/repositories" "github.com/pahmiudahgede/senggoldong/model" @@ -11,7 +15,7 @@ import ( ) 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) GetArticleByID(id string) (*dto.ArticleResponseDTO, error) } @@ -24,22 +28,55 @@ func NewArticleService(articleRepo repositories.ArticleRepository) ArticleServic 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{ - Title: articleDTO.Title, - CoverImage: articleDTO.CoverImage, - Author: articleDTO.Author, - Heading: articleDTO.Heading, - Content: articleDTO.Content, + 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) + } } - 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 { 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) articleResponseDTO := &dto.ArticleResponseDTO{ @@ -49,7 +86,7 @@ func (s *articleService) CreateArticle(articleDTO dto.RequestArticleDTO) (*dto.A Author: article.Author, Heading: article.Heading, Content: article.Content, - PublishedAt: publishedAt, + PublishedAt: createdAt, UpdatedAt: updatedAt, }