From 5549b8fdd880af88eb330a404dee30865d76bf0b Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 01:22:46 +0700 Subject: [PATCH 01/72] change method for update data achievemnts from put to ptach --- .../dto/manage_achievement_request.go | 4 +-- .../manage_achievement_handler_impl.go | 9 ++--- .../manage_achievement_usecase_impl.go | 35 ++++++++++++------- internal/server/route.go | 2 +- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/internal/achievements/manage_achievements/dto/manage_achievement_request.go b/internal/achievements/manage_achievements/dto/manage_achievement_request.go index b125a2d..8e7bf4f 100644 --- a/internal/achievements/manage_achievements/dto/manage_achievement_request.go +++ b/internal/achievements/manage_achievements/dto/manage_achievement_request.go @@ -3,7 +3,7 @@ package dto import "mime/multipart" type UpdateAchievementRequest struct { - Level string `json:"level" validate:"required"` - TargetPoint int `json:"target_point" validate:"required"` + Level string `json:"level"` + TargetPoint int `json:"target_point"` Badge *multipart.FileHeader `json:"-"` } diff --git a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go index 2a8882a..6345c69 100644 --- a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go +++ b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go @@ -3,6 +3,7 @@ package handler import ( "encoding/json" "errors" + "mime/multipart" "net/http" "strconv" @@ -84,15 +85,15 @@ func (handler ManageAchievementHandlerImpl) UpdateAchievementHandler(c echo.Cont if errForm != nil { return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) } - badge := form.File["badge"] + var badge []*multipart.FileHeader + if form != nil { + badge = form.File["badge"] + } err := handler.usecae.UpdateAchievementUsecase(&request, badge, achievementIdInt) if err != nil { if errors.Is(err, pkg.ErrAchievementNotFound) { return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrAchievementNotFound.Error()) } - if errors.Is(err, pkg.ErrBadge) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrBadge.Error()) - } if errors.Is(err, pkg.ErrBadgeMaximum) { return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrBadgeMaximum.Error()) } diff --git a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go index 0c89d24..f3c72d0 100644 --- a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go +++ b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go @@ -37,28 +37,37 @@ func (repository ManageAchievementUsecaseImpl) GetAchievementByIdUsecase(id int) } func (repository ManageAchievementUsecaseImpl) UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge []*multipart.FileHeader, id int) error { - if len(badge) == 0 { - return pkg.ErrBadge - } if len(badge) > 1 { return pkg.ErrBadgeMaximum } - validImages, errImages := helper.ImagesValidation(badge) - if errImages != nil { - return errImages - } - urlBadge, errUpload := helper.UploadToCloudinary(validImages[0], "achievements_badge") - if errUpload != nil { - return pkg.ErrUploadCloudinary + var urlBadge string + if len(badge) == 1 { + validImages, errImages := helper.ImagesValidation(badge) + if errImages != nil { + return errImages + } + urlBadgeUpload, errUpload := helper.UploadToCloudinary(validImages[0], "achievements_badge") + if errUpload != nil { + return pkg.ErrUploadCloudinary + } + urlBadge = urlBadgeUpload } achievement, err := repository.repository.GetAchievementById(id) if err != nil { return pkg.ErrAchievementNotFound } - achievement.Level = strings.ToLower(request.Level) - achievement.TargetPoint = request.TargetPoint - achievement.BadgeUrl = urlBadge + + if request.Level != "" { + achievement.Level = strings.ToLower(request.Level) + } + if request.TargetPoint != 0 { + achievement.TargetPoint = request.TargetPoint + } + if urlBadge != "" { + achievement.BadgeUrl = urlBadge + } + if err := repository.repository.UpdateAchievement(achievement, id); err != nil { return err } diff --git a/internal/server/route.go b/internal/server/route.go index 0f744ba..d5e6345 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -264,7 +264,7 @@ func (s *echoServer) manageAchievement() { s.gr.GET("/achievements/:achievementId", handler.GetAchievementByIdHandler, SuperAdminOrAdminMiddleware) // update achievement - s.gr.PUT("/achievements/:achievementId", handler.UpdateAchievementHandler, SuperAdminOrAdminMiddleware) + s.gr.PATCH("/achievements/:achievementId", handler.UpdateAchievementHandler, SuperAdminOrAdminMiddleware) // delete achievement s.gr.DELETE("/achievements/:achievementId", handler.DeleteAchievementHandler, SuperAdminOrAdminMiddleware) From 3439d778af064c6d78ddfdd569d70a66f887260e Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 12:07:19 +0700 Subject: [PATCH 02/72] docs: channge method for update data schievement from put to patch --- docs/swagger.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index d5f2d17..04966b5 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -1153,11 +1153,11 @@ paths: type: string example: achievement not found - put: + patch: tags: - manage achievements - summary: Update target point of an achievement - description: Endpoint admin to update the target point of an existing achievement/badge. + summary: Update data achievement + description: Endpoint admin for update data achievement. operationId: updateAchievement parameters: - name: achievementId @@ -1170,7 +1170,7 @@ paths: security: - Bearer: [] requestBody: - required: true + required: false content: multipart/form-data:: schema: From 4e3181d08e0b5fce03b0cc2978e52b7ee39cc467 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 17:58:07 +0700 Subject: [PATCH 03/72] refactor(manage vide): change method for update video to patch and change some logic on use case and handler --- internal/server/route.go | 2 +- .../manage_video/dto/manage_video_request.go | 8 +-- .../handler/manage_video_handler_impl.go | 7 ++- .../usecase/manage_video_usecase_impl.go | 63 +++++++++++-------- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/internal/server/route.go b/internal/server/route.go index 86a38c0..38610b7 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -289,7 +289,7 @@ func (s *echoServer) manageVideo() { s.gr.GET("/videos/data/:videoId", handler.GetDetailsDataVideoByIdHandler, SuperAdminOrAdminMiddleware) // update data video - s.gr.PUT("/videos/data/:videoId", handler.UpdateDataVideoHandler, SuperAdminOrAdminMiddleware) + s.gr.PATCH("/videos/data/:videoId", handler.UpdateDataVideoHandler, SuperAdminOrAdminMiddleware) // delete data video s.gr.DELETE("/videos/data/:videoId", handler.DeleteDataVideoHandler, SuperAdminOrAdminMiddleware) diff --git a/internal/video/manage_video/dto/manage_video_request.go b/internal/video/manage_video/dto/manage_video_request.go index 4566819..5c496a7 100644 --- a/internal/video/manage_video/dto/manage_video_request.go +++ b/internal/video/manage_video/dto/manage_video_request.go @@ -15,9 +15,9 @@ type CreateCategoryVideoRequest struct { } type UpdateDataVideoRequest struct { - Title string `json:"title" validate:"required"` - Description string `json:"description" validate:"required"` - LinkVideo string `json:"link_video" validate:"required"` - CategoryId int `json:"category_id" validate:"required"` + Title string `json:"title"` + Description string `json:"description"` + LinkVideo string `json:"link_video"` + CategoryId int `json:"category_id"` Thumbnail *multipart.FileHeader `json:"-"` } diff --git a/internal/video/manage_video/handler/manage_video_handler_impl.go b/internal/video/manage_video/handler/manage_video_handler_impl.go index 392bd3c..3ca95ff 100644 --- a/internal/video/manage_video/handler/manage_video_handler_impl.go +++ b/internal/video/manage_video/handler/manage_video_handler_impl.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "math" + "mime/multipart" "net/http" "strconv" @@ -210,7 +211,11 @@ func (handler *ManageVideoHandlerImpl) UpdateDataVideoHandler(c echo.Context) er if errForm != nil { return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) } - thumbnail := form.File["thumbnail"] + var thumbnail []*multipart.FileHeader + if form != nil { + thumbnail = form.File["thumbnail"] + } + if err := handler.ManageVideoUsecase.UpdateDataVideoUseCase(&request, thumbnail, idInt); err != nil { if errors.Is(err, pkg.ErrVideoNotFound) { return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) diff --git a/internal/video/manage_video/usecase/manage_video_usecase_impl.go b/internal/video/manage_video/usecase/manage_video_usecase_impl.go index b351a41..77ea74d 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase_impl.go +++ b/internal/video/manage_video/usecase/manage_video_usecase_impl.go @@ -107,42 +107,55 @@ func (usecase *ManageVideoUsecaseImpl) GetDetailsDataVideoByIdUseCase(id int) (* } func (usecase *ManageVideoUsecaseImpl) UpdateDataVideoUseCase(request *dto.UpdateDataVideoRequest, thumbnail []*multipart.FileHeader, id int) error { - if len(thumbnail) == 0 { - return pkg.ErrThumbnail - } if len(thumbnail) > 1 { return pkg.ErrThumbnailMaximum } - validImages, errImages := helper.ImagesValidation(thumbnail) - if errImages != nil { - return errImages + var urlThumbnail string + if len(thumbnail) == 1 { + validImages, errImages := helper.ImagesValidation(thumbnail) + if errImages != nil { + return errImages + } + urlThumbnailUpload, errUpload := helper.UploadToCloudinary(validImages[0], "video_thumbnail_update") + if errUpload != nil { + return pkg.ErrUploadCloudinary + } + urlThumbnail = urlThumbnailUpload } - if _, err := usecase.manageVideoRepository.GetDetailsDataVideoById(id); err != nil { + video, err := usecase.manageVideoRepository.GetDetailsDataVideoById(id) + if err != nil { return pkg.ErrVideoNotFound } - if _, err := usecase.manageVideoRepository.GetCategoryVideoById(request.CategoryId); err != nil { - return pkg.ErrVideoCategoryNotFound + + if request.Title != "" { + video.Title = request.Title } - view, errGetView := helper.GetVideoViewCount(request.LinkVideo) - if errGetView != nil { - return errGetView + if request.Description != "" { + video.Description = request.Description } - urlThumbnail, errUpload := helper.UploadToCloudinary(validImages[0], "video_thumbnail_update") - if errUpload != nil { - return pkg.ErrUploadCloudinary + if urlThumbnail != "" { + video.Thumbnail = urlThumbnail } - - intView := int(view) - video := video.Video{ - Title: request.Title, - Description: request.Description, - Thumbnail: urlThumbnail, - Link: request.LinkVideo, - VideoCategoryID: request.CategoryId, - Viewer: intView, + if request.LinkVideo != "" { + view, errGetView := helper.GetVideoViewCount(request.LinkVideo) + if errGetView != nil { + return errGetView + } + if view != 0 { + intView := int(view) + video.Viewer = intView + } + video.Link = request.LinkVideo + } + if request.CategoryId != 0 { + if _, err := usecase.manageVideoRepository.GetCategoryVideoById(request.CategoryId); err != nil { + return pkg.ErrVideoCategoryNotFound + } + video.VideoCategoryID = request.CategoryId } - if err := usecase.manageVideoRepository.UpdateDataVideo(&video, id); err != nil { + + if err := usecase.manageVideoRepository.UpdateDataVideo(video, id); err != nil { return err } return nil From ac5fec68f950a6edeb431c8514abdc2e5bc18ad7 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 18:06:05 +0700 Subject: [PATCH 04/72] docs: change http method for update data video from put to patch --- docs/swagger.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 84af25d..da6e92b 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -3288,7 +3288,7 @@ paths: message: type: string example: video not found - put: + patch: tags: - manage videos summary: Update Data Video @@ -3305,7 +3305,7 @@ paths: example: 1 description: ID of video requestBody: - required: true + required: false content: multipart/form-data: schema: From 7ce15192e0729f3d4032a24b5e77059ed75547b6 Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Mon, 10 Jun 2024 19:57:30 +0700 Subject: [PATCH 05/72] chore(report): update report response --- internal/report/dto.go | 32 +++++++++++++++---------- internal/report/usecase/usecase.go | 33 ++++++++++++++++++++++---- internal/server/route.go | 5 ++-- internal/user/repository/repository.go | 2 +- 4 files changed, 51 insertions(+), 21 deletions(-) diff --git a/internal/report/dto.go b/internal/report/dto.go index 2697e91..e36145e 100644 --- a/internal/report/dto.go +++ b/internal/report/dto.go @@ -24,20 +24,26 @@ type UpdateStatus struct { Reason string `json:"reason"` } +type UserDetail struct { + ID string `json:"id"` + Name string `json:"name"` + ImageURL string `json:"image_url"` +} + type ReportDetail struct { - ID string `json:"id"` - AuthorID string `json:"author_id"` - ReportType string `json:"report_type"` - Title string `json:"title"` - Description string `json:"description"` - WasteType string `json:"waste_type"` - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` - Address string `json:"address"` - City string `json:"city"` - Province string `json:"province"` - Status string `json:"status"` - Reason string `json:"reason"` + ID string `json:"id"` + Author UserDetail `json:"author"` + ReportType string `json:"report_type"` + Title string `json:"title"` + Description string `json:"description"` + WasteType string `json:"waste_type"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Address string `json:"address"` + City string `json:"city"` + Province string `json:"province"` + Status string `json:"status"` + Reason string `json:"reason"` WasteMaterials []WasteMaterial `json:"waste_materials"` ReportImages []string `json:"report_images"` diff --git a/internal/report/usecase/usecase.go b/internal/report/usecase/usecase.go index 2930fff..d221d94 100644 --- a/internal/report/usecase/usecase.go +++ b/internal/report/usecase/usecase.go @@ -6,15 +6,17 @@ import ( "github.com/google/uuid" "github.com/sawalreverr/recything/internal/helper" rpt "github.com/sawalreverr/recything/internal/report" + user "github.com/sawalreverr/recything/internal/user" "github.com/sawalreverr/recything/pkg" ) type reportUsecase struct { reportRepository rpt.ReportRepository + userRepository user.UserRepository } -func NewReportUsecase(repo rpt.ReportRepository) rpt.ReportUsecase { - return &reportUsecase{reportRepository: repo} +func NewReportUsecase(reportRepo rpt.ReportRepository, userRepo user.UserRepository) rpt.ReportUsecase { + return &reportUsecase{reportRepository: reportRepo, userRepository: userRepo} } func (uc *reportUsecase) CreateReport(report rpt.ReportInput, authorID string, imageURLs []string) (*rpt.ReportDetail, error) { @@ -83,9 +85,16 @@ func (uc *reportUsecase) CreateReport(report rpt.ReportInput, authorID string, i return nil, pkg.ErrStatusInternalError } + userFound, _ := uc.userRepository.FindByID(authorID) + author := rpt.UserDetail{ + ID: userFound.ID, + Name: userFound.Name, + ImageURL: userFound.PictureURL, + } + reportDetail := rpt.ReportDetail{ ID: createdReport.ID, - AuthorID: createdReport.AuthorID, + Author: author, ReportType: createdReport.ReportType, Title: createdReport.Title, Description: createdReport.Description, @@ -123,9 +132,16 @@ func (uc *reportUsecase) FindHistoryUserReports(authorID string) (*[]rpt.ReportD return nil, pkg.ErrStatusInternalError } + userFound, _ := uc.userRepository.FindByID(authorID) + author := rpt.UserDetail{ + ID: userFound.ID, + Name: userFound.Name, + ImageURL: userFound.PictureURL, + } + reportDetail := rpt.ReportDetail{ ID: report.ID, - AuthorID: report.AuthorID, + Author: author, ReportType: report.ReportType, Title: report.Title, Description: report.Description, @@ -185,9 +201,16 @@ func (uc *reportUsecase) FindAllReports(page, limit int, reportType, status stri return nil, 0, pkg.ErrStatusInternalError } + userFound, _ := uc.userRepository.FindByID(report.AuthorID) + author := rpt.UserDetail{ + ID: userFound.ID, + Name: userFound.Name, + ImageURL: userFound.PictureURL, + } + reportDetail := rpt.ReportDetail{ ID: report.ID, - AuthorID: report.AuthorID, + Author: author, ReportType: report.ReportType, Title: report.Title, Description: report.Description, diff --git a/internal/server/route.go b/internal/server/route.go index d2043ae..efad7f0 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -150,8 +150,9 @@ func (s *echoServer) supAdminHttpHandler() { } func (s *echoServer) reportHttpHandler() { - repository := reportRepo.NewReportRepository(s.db) - usecase := reportUsecase.NewReportUsecase(repository) + reportRepository := reportRepo.NewReportRepository(s.db) + userRepository := userRepo.NewUserRepository(s.db) + usecase := reportUsecase.NewReportUsecase(reportRepository, userRepository) handler := reportHandler.NewReportHandler(usecase) // User create new report diff --git a/internal/user/repository/repository.go b/internal/user/repository/repository.go index 17f908b..ab3701a 100644 --- a/internal/user/repository/repository.go +++ b/internal/user/repository/repository.go @@ -43,7 +43,7 @@ func (r *userRepository) FindByPhoneNumber(phoneNumber string) (*u.User, error) func (r *userRepository) FindByID(userID string) (*u.User, error) { var user u.User - if err := r.DB.GetDB().Where("id = ?", userID).First(&user).Error; err != nil { + if err := r.DB.GetDB().Unscoped().Where("id = ?", userID).First(&user).Error; err != nil { return nil, err } From aba3ee57abb48137bad1d86178c8ab156e93e1e7 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 21:37:09 +0700 Subject: [PATCH 06/72] refactor(manage task): change method for update task to patch and change some logic on use case, handler and repo --- internal/server/route.go | 2 +- .../manage_task/dto/manage_task_request.go | 10 +- .../handler/manage_task_handler.go | 3 +- .../handler/manage_task_handler_impl.go | 17 ++-- .../repository/manage_task_repository_impl.go | 15 +-- .../usecase/manage_task_usecase_impl.go | 97 ++++++++++--------- 6 files changed, 78 insertions(+), 66 deletions(-) diff --git a/internal/server/route.go b/internal/server/route.go index d2043ae..1c0087e 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -197,7 +197,7 @@ func (s *echoServer) manageTask() { s.gr.GET("/tasks/:taskId", handler.GetTaskByIdHandler, SuperAdminOrAdminMiddleware) // update task challenge - s.gr.PUT("/tasks/:taskId", handler.UpdateTaskHandler, SuperAdminOrAdminMiddleware) + s.gr.PATCH("/tasks/:taskId", handler.UpdateTaskHandler, SuperAdminOrAdminMiddleware) // delete task challenge s.gr.DELETE("/tasks/:taskId", handler.DeleteTaskHandler, SuperAdminOrAdminMiddleware) diff --git a/internal/task/manage_task/dto/manage_task_request.go b/internal/task/manage_task/dto/manage_task_request.go index 028d32d..6b0a9c6 100644 --- a/internal/task/manage_task/dto/manage_task_request.go +++ b/internal/task/manage_task/dto/manage_task_request.go @@ -19,11 +19,11 @@ type TaskSteps struct { } type UpdateTaskRequest struct { - Title string `json:"title" validate:"required"` - Description string `json:"description" validate:"required"` - StartDate string `json:"start_date" validate:"required"` - EndDate string `json:"end_date" validate:"required"` - Point int `json:"point" validate:"required"` + Title string `json:"title"` + Description string `json:"description"` + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + Point int `json:"point"` Thumbnail *multipart.FileHeader `json:"-"` TaskSteps []TaskSteps `json:"task_steps" validate:"required"` } diff --git a/internal/task/manage_task/handler/manage_task_handler.go b/internal/task/manage_task/handler/manage_task_handler.go index 1d977d6..d225609 100644 --- a/internal/task/manage_task/handler/manage_task_handler.go +++ b/internal/task/manage_task/handler/manage_task_handler.go @@ -4,8 +4,7 @@ import "github.com/labstack/echo/v4" type ManageTaskHandler interface { CreateTaskHandler(c echo.Context) error - GetTaskChallangePagginationHandler(c echo.Context) error - UploadThumbnailHandler(c echo.Context) error + GetTaskChallengePaginationHandler(c echo.Context) error GetTaskByIdHandler(c echo.Context) error UpdateTaskHandler(c echo.Context) error DeleteTaskHandler(c echo.Context) error diff --git a/internal/task/manage_task/handler/manage_task_handler_impl.go b/internal/task/manage_task/handler/manage_task_handler_impl.go index 367abcc..e4c1a4f 100644 --- a/internal/task/manage_task/handler/manage_task_handler_impl.go +++ b/internal/task/manage_task/handler/manage_task_handler_impl.go @@ -3,6 +3,7 @@ package handler import ( "encoding/json" "errors" + "mime/multipart" "net/http" "strconv" @@ -17,7 +18,7 @@ type ManageTaskHandlerImpl struct { Usecase usecase.ManageTaskUsecase } -func NewManageTaskHandler(usecase usecase.ManageTaskUsecase) *ManageTaskHandlerImpl { +func NewManageTaskHandler(usecase usecase.ManageTaskUsecase) ManageTaskHandler { return &ManageTaskHandlerImpl{Usecase: usecase} } @@ -198,17 +199,19 @@ func (handler *ManageTaskHandlerImpl) UpdateTaskHandler(c echo.Context) error { var request dto.UpdateTaskRequest id := c.Param("taskId") json_data := c.FormValue("json_data") - if err := json.Unmarshal([]byte(json_data), &request); err != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) - } - if err := c.Validate(&request); err != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + if json_data != "" { + if err := json.Unmarshal([]byte(json_data), &request); err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + } } form, errForm := c.MultipartForm() if errForm != nil { return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) } - thumbnail := form.File["thumbnail"] + var thumbnail []*multipart.FileHeader + if form != nil { + thumbnail = form.File["thumbnail"] + } task, err := handler.Usecase.UpdateTaskChallengeUsecase(&request, thumbnail, id) if err != nil { diff --git a/internal/task/manage_task/repository/manage_task_repository_impl.go b/internal/task/manage_task/repository/manage_task_repository_impl.go index ecf84af..b8cfda1 100644 --- a/internal/task/manage_task/repository/manage_task_repository_impl.go +++ b/internal/task/manage_task/repository/manage_task_repository_impl.go @@ -66,9 +66,8 @@ func (repository *ManageTaskRepositoryImpl) GetTaskById(id string) (*task.TaskCh } func (repository *ManageTaskRepositoryImpl) FindTask(id string) (*task.TaskChallenge, error) { - log.Println("Finding task with ID:", id) var task task.TaskChallenge - if err := repository.DB.GetDB().Where("id = ?", id).First(&task).Error; err != nil { + if err := repository.DB.GetDB().Preload("TaskSteps").Where("id = ?", id).First(&task).Error; err != nil { log.Println("Error finding task:", err) return nil, err } @@ -76,7 +75,6 @@ func (repository *ManageTaskRepositoryImpl) FindTask(id string) (*task.TaskChall } func (repository *ManageTaskRepositoryImpl) UpdateTaskChallenge(taskChallenge *task.TaskChallenge, taskId string) (*task.TaskChallenge, error) { - log.Println("Updating task with ID:", taskId) tx := repository.DB.GetDB().Begin() defer func() { @@ -85,10 +83,13 @@ func (repository *ManageTaskRepositoryImpl) UpdateTaskChallenge(taskChallenge *t log.Println("Transaction rollback due to panic:", r) } }() - if err := tx.Where("task_challenge_id = ?", taskId).Delete(&task.TaskStep{}).Error; err != nil { - log.Println("Error deleting task steps:", err) - tx.Rollback() - return nil, err + + if len(taskChallenge.TaskSteps) != 0 { + if err := tx.Where("task_challenge_id = ?", taskId).Delete(&task.TaskStep{}).Error; err != nil { + log.Println("Error deleting task steps:", err) + tx.Rollback() + return nil, err + } } if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Updates(taskChallenge).Error; err != nil { diff --git a/internal/task/manage_task/usecase/manage_task_usecase_impl.go b/internal/task/manage_task/usecase/manage_task_usecase_impl.go index b8ed75e..dea55d4 100644 --- a/internal/task/manage_task/usecase/manage_task_usecase_impl.go +++ b/internal/task/manage_task/usecase/manage_task_usecase_impl.go @@ -97,63 +97,72 @@ func (usecase *ManageTaskUsecaseImpl) GetTaskByIdUsecase(id string) (*task.TaskC } func (usecase *ManageTaskUsecaseImpl) UpdateTaskChallengeUsecase(request *dto.UpdateTaskRequest, thumbnail []*multipart.FileHeader, id string) (*task.TaskChallenge, error) { - if len(thumbnail) == 0 { - return nil, pkg.ErrThumbnail - } + if len(thumbnail) > 1 { return nil, pkg.ErrThumbnailMaximum } - if len(request.TaskSteps) == 0 { - return nil, pkg.ErrTaskStepsNull - } - validImages, errImages := helper.ImagesValidation(thumbnail) - if errImages != nil { - return nil, errImages - } - urlThumbnail, errUpload := helper.UploadToCloudinary(validImages[0], "task_thumbnail_update") - if errUpload != nil { - return nil, pkg.ErrUploadCloudinary - } - findTask, _ := usecase.ManageTaskRepository.FindTask(id) + var urlThumbnail string + if len(thumbnail) == 1 { + validImages, errImages := helper.ImagesValidation(thumbnail) + if errImages != nil { + return nil, errImages + } + urlThumbnailUpload, errUpload := helper.UploadToCloudinary(validImages[0], "task_thumbnail_update") + if errUpload != nil { + return nil, pkg.ErrUploadCloudinary + } + urlThumbnail = urlThumbnailUpload + } - if findTask == nil { - return nil, pkg.ErrTaskNotFound + tasks, err := usecase.ManageTaskRepository.FindTask(id) + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, pkg.ErrTaskNotFound + } + return nil, err } - startDateString := request.StartDate - endDateString := request.EndDate - parsedStartDate, errParsedStartDate := time.Parse("2006-01-02", startDateString) - if errParsedStartDate != nil { - return nil, pkg.ErrParsedTime + if request.Title != "" { + tasks.Title = request.Title } - parsedEndDate, errParsedEndDate := time.Parse("2006-01-02", endDateString) - if errParsedEndDate != nil { - return nil, pkg.ErrParsedTime + if request.Description != "" { + tasks.Description = request.Description } - - taskChallenge := &task.TaskChallenge{ - ID: id, - AdminId: findTask.AdminId, - Title: request.Title, - Description: request.Description, - Thumbnail: urlThumbnail, - StartDate: parsedStartDate, - EndDate: parsedEndDate, - Point: request.Point, + if request.Point != 0 { + tasks.Point = request.Point } - - // Add new steps - for _, step := range request.TaskSteps { - taskStep := task.TaskStep{ - TaskChallengeId: id, - Title: step.Title, - Description: step.Description, + if urlThumbnail != "" { + tasks.Thumbnail = urlThumbnail + } + if request.StartDate != "" { + parsedStartDate, errParsedStartDate := time.Parse("2006-01-02", request.StartDate) + if errParsedStartDate != nil { + return nil, pkg.ErrParsedTime + } + tasks.StartDate = parsedStartDate + } + if request.EndDate != "" { + parsedEndDate, errParsedEndDate := time.Parse("2006-01-02", request.EndDate) + if errParsedEndDate != nil { + return nil, pkg.ErrParsedTime + } + tasks.EndDate = parsedEndDate + } + + if len(request.TaskSteps) != 0 { + tasks.TaskSteps = []task.TaskStep{} + for _, step := range request.TaskSteps { + taskStep := task.TaskStep{ + TaskChallengeId: id, + Title: step.Title, + Description: step.Description, + } + tasks.TaskSteps = append(tasks.TaskSteps, taskStep) } - taskChallenge.TaskSteps = append(taskChallenge.TaskSteps, taskStep) } - updatedTaskChallenge, err := usecase.ManageTaskRepository.UpdateTaskChallenge(taskChallenge, id) + updatedTaskChallenge, err := usecase.ManageTaskRepository.UpdateTaskChallenge(tasks, id) if err != nil { return nil, err } From 62830f877ea70019c6280c9478410911985aa4b3 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 21:38:21 +0700 Subject: [PATCH 07/72] docs: change http method for update task from put to patch --- docs/swagger.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 84af25d..fbdcc18 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -1814,7 +1814,7 @@ paths: message: type: string example: task not found - put: + patch: tags: - manage tasks summary: Update task @@ -1831,7 +1831,7 @@ paths: description: ID of the task to be updated example: TM0001 requestBody: - required: true + required: false content: multipart/form-data:: schema: From 36e606851347ba928fe4d71e13f41c349f783fbe Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Jun 2024 00:31:20 +0700 Subject: [PATCH 08/72] refactor(manage achievement): change request body and some code on handler and usecase --- docs/swagger.yml | 19 +++++------ .../dto/manage_achievement_request.go | 7 ++-- .../manage_achievement_handler_impl.go | 30 +++++----------- .../usecase/manage_achievement_usecase.go | 2 +- .../manage_achievement_usecase_impl.go | 34 +++++++++++-------- pkg/errors.go | 5 +++ 6 files changed, 44 insertions(+), 53 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 04966b5..1713bef 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -1176,17 +1176,14 @@ paths: schema: type: object properties: - json_data: - type: object - properties: - level: - type: string - description: New level for the achievement - example: platinum - target_point: - type: integer - description: New target point for the badge - example: 1200 + level: + type: string + description: New level for the achievement + example: platinum + target_point: + type: integer + description: New target point for the badge + example: 1200 badge: type: string format: binary diff --git a/internal/achievements/manage_achievements/dto/manage_achievement_request.go b/internal/achievements/manage_achievements/dto/manage_achievement_request.go index 8e7bf4f..06a096d 100644 --- a/internal/achievements/manage_achievements/dto/manage_achievement_request.go +++ b/internal/achievements/manage_achievements/dto/manage_achievement_request.go @@ -1,9 +1,6 @@ package dto -import "mime/multipart" - type UpdateAchievementRequest struct { - Level string `json:"level"` - TargetPoint int `json:"target_point"` - Badge *multipart.FileHeader `json:"-"` + Level string `json:"level" form:"level"` + TargetPoint int `json:"target_point" form:"target_point"` } diff --git a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go index 6345c69..8479af5 100644 --- a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go +++ b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go @@ -1,9 +1,7 @@ package handler import ( - "encoding/json" "errors" - "mime/multipart" "net/http" "strconv" @@ -74,34 +72,24 @@ func (handler ManageAchievementHandlerImpl) UpdateAchievementHandler(c echo.Cont } request := dto.UpdateAchievementRequest{} - json_data := c.FormValue("json_data") - if err := json.Unmarshal([]byte(json_data), &request); err != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) - } - if err := c.Validate(&request); err != nil { + + if err := c.Bind(&request); err != nil { return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) } - form, errForm := c.MultipartForm() - if errForm != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) - } - var badge []*multipart.FileHeader - if form != nil { - badge = form.File["badge"] - } + badge, _ := c.FormFile("badge") err := handler.usecae.UpdateAchievementUsecase(&request, badge, achievementIdInt) if err != nil { if errors.Is(err, pkg.ErrAchievementNotFound) { return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrAchievementNotFound.Error()) } - if errors.Is(err, pkg.ErrBadgeMaximum) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrBadgeMaximum.Error()) + if errors.Is(err, pkg.ErrFileTooLarge) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrFileTooLarge.Error()) } - if errors.Is(err, errors.New("upload image size must less than 2MB")) { - return helper.ErrorHandler(c, http.StatusBadRequest, "upload image size must less than 2MB") + if errors.Is(err, pkg.ErrInvalidFileType) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrInvalidFileType.Error()) } - if errors.Is(err, errors.New("only image allowed")) { - return helper.ErrorHandler(c, http.StatusBadRequest, "only image allowed") + if errors.Is(err, pkg.ErrOpenFile) { + return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrOpenFile.Error()) } if errors.Is(err, pkg.ErrUploadCloudinary) { return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrUploadCloudinary.Error()) diff --git a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go index d5e8803..b762503 100644 --- a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go +++ b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go @@ -10,6 +10,6 @@ import ( type ManageAchievementUsecase interface { GetAllArchievementUsecase() ([]*archievement.Achievement, error) GetAchievementByIdUsecase(id int) (*archievement.Achievement, error) - UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge []*multipart.FileHeader, id int) error + UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge *multipart.FileHeader, id int) error DeleteAchievementUsecase(id int) error } diff --git a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go index f3c72d0..cb7cb7d 100644 --- a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go +++ b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go @@ -36,30 +36,34 @@ func (repository ManageAchievementUsecaseImpl) GetAchievementByIdUsecase(id int) return achievement, nil } -func (repository ManageAchievementUsecaseImpl) UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge []*multipart.FileHeader, id int) error { - if len(badge) > 1 { - return pkg.ErrBadgeMaximum +func (repository ManageAchievementUsecaseImpl) UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge *multipart.FileHeader, id int) error { + achievement, err := repository.repository.GetAchievementById(id) + if err != nil { + return pkg.ErrAchievementNotFound } var urlBadge string - if len(badge) == 1 { - validImages, errImages := helper.ImagesValidation(badge) - if errImages != nil { - return errImages + if badge != nil { + if badge.Size > 2*1024*1024 { + return pkg.ErrFileTooLarge } - urlBadgeUpload, errUpload := helper.UploadToCloudinary(validImages[0], "achievements_badge") + if !strings.HasPrefix(badge.Header.Get("Content-Type"), "image") { + return pkg.ErrInvalidFileType + } + src, errOpen := badge.Open() + if errOpen != nil { + return pkg.ErrOpenFile + } + + urlBadgeUpload, errUpload := helper.UploadToCloudinary(src, "achievement_badge") if errUpload != nil { - return pkg.ErrUploadCloudinary + return errUpload } urlBadge = urlBadgeUpload - } - - achievement, err := repository.repository.GetAchievementById(id) - if err != nil { - return pkg.ErrAchievementNotFound + defer src.Close() } if request.Level != "" { - achievement.Level = strings.ToLower(request.Level) + achievement.Level = request.Level } if request.TargetPoint != 0 { achievement.TargetPoint = request.TargetPoint diff --git a/pkg/errors.go b/pkg/errors.go index 408ecf8..28afddb 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -71,4 +71,9 @@ var ( // About Us ErrAboutUsCategoryNotFound = errors.New("about us with that category not found") + + // Error file + ErrFileTooLarge = errors.New("upload image size must less than 2MB") + ErrInvalidFileType = errors.New("invalid file type") + ErrOpenFile = errors.New("failed to open file") ) From aa7e062250d2e339683a6d2cde2fadc866b8d7ae Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Tue, 11 Jun 2024 00:55:24 +0700 Subject: [PATCH 09/72] feat(article): add some repository, usecase, handler for article --- cmd/api/main.go | 6 ++ internal/article/dto.go | 44 ++++++++ internal/article/entity.go | 88 ++++++++++++++++ internal/article/handler/handler.go | 33 ++++++ internal/article/repository/repository.go | 116 ++++++++++++++++++++++ internal/article/usecase/usecase.go | 89 +++++++++++++++++ internal/database/database.go | 2 + internal/database/migrate.go | 5 + internal/database/mysql.go | 58 +++++++++++ internal/server/echo_server.go | 3 + internal/server/route.go | 13 +++ pkg/errors.go | 3 + 12 files changed, 460 insertions(+) create mode 100644 internal/article/dto.go create mode 100644 internal/article/entity.go create mode 100644 internal/article/handler/handler.go create mode 100644 internal/article/repository/repository.go create mode 100644 internal/article/usecase/usecase.go diff --git a/cmd/api/main.go b/cmd/api/main.go index 8a60d73..f5edf7e 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -45,6 +45,12 @@ func main() { // Init Videos db.InitDataVideos() + // Init Article Categories + db.InitArticleCategory() + + // Init Article + db.InitArticle() + app := server.NewEchoServer(conf, db) c := cron.New() diff --git a/internal/article/dto.go b/internal/article/dto.go new file mode 100644 index 0000000..7e06079 --- /dev/null +++ b/internal/article/dto.go @@ -0,0 +1,44 @@ +package article + +import ( + "time" +) + +type ArticleInput struct { + Title string `json:"title"` + Description string `json:"description"` + ThumbnailURL string `json:"thumbnail_url"` + Categories []string `json:"categories"` + Sections []ArticleSectionInput `json:"sections"` +} + +type ArticleSectionInput struct { + Title string `json:"title"` + Description string `json:"description"` + ImageURL string `json:"image_url"` +} + +type AdminDetail struct { + ID string `json:"id"` + Name string `json:"name"` + ImageURL string `json:"image_url"` +} + +type ArticleDetail struct { + ID string `json:"id"` + AuthorID AdminDetail `json:"author"` + Title string `json:"title"` + Description string `json:"description"` + ThumbnailURL string `json:"thumbnail_url"` + CreatedAt time.Time `json:"created_at"` + + Categories []WasteCategory `json:"categories"` + Sections []ArticleSection `json:"sections"` +} + +type ArticleResponsePagination struct { + Total int64 `json:"total"` + Page int `json:"page"` + Limit int `json:"limit"` + Articles []ArticleDetail `json:"articles"` +} diff --git a/internal/article/entity.go b/internal/article/entity.go new file mode 100644 index 0000000..31e2d48 --- /dev/null +++ b/internal/article/entity.go @@ -0,0 +1,88 @@ +package article + +import ( + "time" + + "github.com/labstack/echo/v4" + "gorm.io/gorm" +) + +type Article struct { + ID string `gorm:"primaryKey;type:varchar(7)"` + Title string `gorm:"type:varchar(255)"` + Description string `gorm:"type:text"` + ThumbnailURL string `gorm:"type:varchar(255)"` + AuthorID string + + Categories []ArticleCategories + Sections []ArticleSection + + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +} + +type WasteCategory struct { + ID uint `json:"id" gorm:"primaryKey"` + Name string `json:"name" gorm:"type:varchar(50);unique;not null"` + + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +} + +type ArticleCategories struct { + ID uint `gorm:"primaryKey"` + ArticleID string `gorm:"type:varchar(7)"` + CategoryID uint + + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +} + +type ArticleSection struct { + ID uint `json:"-" gorm:"primaryKey"` + ArticleID string `json:"-" gorm:"type:varchar(7)"` + Title string `json:"title" gorm:"type:varchar(255)"` + Description string `json:"description" gorm:"type:text"` + ImageURL string `json:"image_url" gorm:"type:varchar(255)"` + + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +} + +type ArticleRepository interface { + // Article Repository + Create(article Article) (*Article, error) + FindByID(articleID string) (*Article, error) + FindAll(page, limit uint) (*[]Article, error) + FindLastID() (string, error) + FindByKeyword(keyword string) (*[]Article, error) + FindByCategory(categoryName string) (*[]Article, error) + Update(article Article) error + Delete(articleID string) error + + // Category Repository + FindCategories(articleID string) (*[]WasteCategory, error) + + // Article Section Repository +} + +type ArticleUsecase interface { + NewArticle(article ArticleInput) (*ArticleDetail, error) + GetArticleByID(articleID string) (*ArticleDetail, error) + GetAllArticle(page, limit uint) (*ArticleResponsePagination, error) + GetArticleByKeyword(keyword string) (*[]ArticleDetail, error) + GetArticleByCategory(categoryName string) (*[]ArticleDetail, error) + Update(articleID string, article ArticleInput) error + Delete(articleID string) error + + GetArticleDetail(article Article) *ArticleDetail + GetDetailAuthor(authorID string) (*AdminDetail, error) +} + +type ArticleHandler interface { + GetArticleByID(c echo.Context) error +} diff --git a/internal/article/handler/handler.go b/internal/article/handler/handler.go new file mode 100644 index 0000000..9d71d2a --- /dev/null +++ b/internal/article/handler/handler.go @@ -0,0 +1,33 @@ +package article + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v4" + art "github.com/sawalreverr/recything/internal/article" + "github.com/sawalreverr/recything/internal/helper" + "github.com/sawalreverr/recything/pkg" +) + +type articleHandler struct { + usecase art.ArticleUsecase +} + +func NewArticleHandler(uc art.ArticleUsecase) art.ArticleHandler { + return &articleHandler{usecase: uc} +} + +func (h *articleHandler) GetArticleByID(c echo.Context) error { + articleId := c.Param("articleId") + + articleFound, err := h.usecase.GetArticleByID(articleId) + if err != nil { + if errors.Is(pkg.ErrArticleNotFound, err) { + return helper.ErrorHandler(c, http.StatusNotFound, err.Error()) + } + return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + } + + return helper.ResponseHandler(c, http.StatusOK, "ok", articleFound) +} diff --git a/internal/article/repository/repository.go b/internal/article/repository/repository.go new file mode 100644 index 0000000..4c3773f --- /dev/null +++ b/internal/article/repository/repository.go @@ -0,0 +1,116 @@ +package article + +import ( + "errors" + + art "github.com/sawalreverr/recything/internal/article" + "github.com/sawalreverr/recything/internal/database" + "github.com/sawalreverr/recything/pkg" + "gorm.io/gorm" +) + +type articleRepository struct { + DB database.Database +} + +func NewArticleRepository(db database.Database) art.ArticleRepository { + return &articleRepository{DB: db} +} + +func (r *articleRepository) Create(article art.Article) (*art.Article, error) { + if err := r.DB.GetDB().Create(&article).Error; err != nil { + return nil, err + } + + return &article, nil +} + +func (r *articleRepository) FindByID(articleID string) (*art.Article, error) { + var article art.Article + if err := r.DB.GetDB().Preload("Categories").Preload("Sections").First(&article, "id = ?", articleID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, pkg.ErrArticleNotFound + } + return nil, err + } + return &article, nil +} + +func (r *articleRepository) FindAll(page, limit uint) (*[]art.Article, error) { + var articles []art.Article + offset := (page - 1) * limit + if err := r.DB.GetDB().Preload("Categories").Preload("Sections").Limit(int(limit)).Offset(int(offset)).Find(&articles).Error; err != nil { + return nil, err + } + return &articles, nil +} + +func (r *articleRepository) FindLastID() (string, error) { + var article art.Article + if err := r.DB.GetDB().Unscoped().Order("id DESC").First(&article).Error; err != nil { + return "ART0000", err + } + + return article.ID, nil +} + +func (r *articleRepository) FindByKeyword(keyword string) (*[]art.Article, error) { + var articles []art.Article + query := "%" + keyword + "%" + if err := r.DB.GetDB().Preload("Categories").Preload("Sections"). + Where("title LIKE ? OR description LIKE ?", query, query). + Find(&articles).Error; err != nil { + return nil, err + } + return &articles, nil +} + +func (r *articleRepository) FindByCategory(categoryName string) (*[]art.Article, error) { + var articles []art.Article + if err := r.DB.GetDB().Preload("Categories").Preload("Sections"). + Joins("JOIN article_categories ON articles.id = article_categories.article_id"). + Joins("JOIN categories ON article_categories.category_id = categories.id"). + Where("categories.name = ?", categoryName). + Find(&articles).Error; err != nil { + return nil, err + } + return &articles, nil +} + +func (r *articleRepository) Update(article art.Article) error { + if err := r.DB.GetDB().Save(&article).Error; err != nil { + return err + } + return nil +} + +func (r *articleRepository) Delete(articleID string) error { + var article art.Article + if err := r.DB.GetDB().Delete(&article, "id = ?", articleID).Error; err != nil { + return err + } + return nil +} + +func (r *articleRepository) FindCategories(articleID string) (*[]art.WasteCategory, error) { + var articleCategories []art.ArticleCategories + var categories []art.WasteCategory + + if err := r.DB.GetDB().Where("article_id = ?", articleID).Find(&articleCategories).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, pkg.ErrArticleNotFound + } + return nil, err + } + + var categoryIDs []uint + for _, ac := range articleCategories { + categoryIDs = append(categoryIDs, ac.CategoryID) + } + + if err := r.DB.GetDB().Where("id IN (?)", categoryIDs).Find(&categories).Error; err != nil { + return nil, err + } + + return &categories, nil +} diff --git a/internal/article/usecase/usecase.go b/internal/article/usecase/usecase.go new file mode 100644 index 0000000..f262842 --- /dev/null +++ b/internal/article/usecase/usecase.go @@ -0,0 +1,89 @@ +package article + +import ( + admin "github.com/sawalreverr/recything/internal/admin/repository" + art "github.com/sawalreverr/recything/internal/article" +) + +type articleUsecase struct { + articleRepo art.ArticleRepository + adminRepo admin.AdminRepository +} + +func NewArticleUsecase(articleRepo art.ArticleRepository, adminRepo admin.AdminRepository) art.ArticleUsecase { + return &articleUsecase{articleRepo: articleRepo, adminRepo: adminRepo} +} + +func (uc *articleUsecase) NewArticle(article art.ArticleInput) (*art.ArticleDetail, error) { + + return nil, nil +} + +func (uc *articleUsecase) GetArticleByID(articleID string) (*art.ArticleDetail, error) { + articleFound, err := uc.articleRepo.FindByID(articleID) + if err != nil { + return nil, err + } + + articleDetail := uc.GetArticleDetail(*articleFound) + + return articleDetail, nil +} + +func (uc *articleUsecase) GetAllArticle(page, limit uint) (*art.ArticleResponsePagination, error) { + + return nil, nil +} + +func (uc *articleUsecase) GetArticleByKeyword(keyword string) (*[]art.ArticleDetail, error) { + + return nil, nil +} + +func (uc *articleUsecase) GetArticleByCategory(categoryName string) (*[]art.ArticleDetail, error) { + + return nil, nil +} + +func (uc *articleUsecase) Update(articleID string, article art.ArticleInput) error { + + return nil +} + +func (uc *articleUsecase) Delete(articleID string) error { + + return nil +} + +func (uc *articleUsecase) GetArticleDetail(article art.Article) *art.ArticleDetail { + adminDetail, _ := uc.GetDetailAuthor(article.AuthorID) + categories, _ := uc.articleRepo.FindCategories(article.ID) + + articleDetail := art.ArticleDetail{ + ID: article.ID, + AuthorID: *adminDetail, + Title: article.Title, + Description: article.Description, + ThumbnailURL: article.ThumbnailURL, + CreatedAt: article.CreatedAt, + Categories: *categories, + Sections: article.Sections, + } + + return &articleDetail +} + +func (uc *articleUsecase) GetDetailAuthor(authorID string) (*art.AdminDetail, error) { + adminFound, err := uc.adminRepo.FindAdminByID(authorID) + if err != nil { + return nil, err + } + + adminDetail := art.AdminDetail{ + ID: adminFound.ID, + Name: adminFound.Name, + ImageURL: adminFound.ImageUrl, + } + + return &adminDetail, nil +} diff --git a/internal/database/database.go b/internal/database/database.go index c593a19..10d2058 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -14,4 +14,6 @@ type Database interface { InitVideoCategories() InitAboutUs() InitDataVideos() + InitArticleCategory() + InitArticle() } diff --git a/internal/database/migrate.go b/internal/database/migrate.go index 91dcbaa..fa598a4 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -6,6 +6,7 @@ import ( aboutus "github.com/sawalreverr/recything/internal/about-us" achievement "github.com/sawalreverr/recything/internal/achievements/manage_achievements/entity" "github.com/sawalreverr/recything/internal/admin/entity" + "github.com/sawalreverr/recything/internal/article" customdata "github.com/sawalreverr/recything/internal/custom-data" "github.com/sawalreverr/recything/internal/faq" "github.com/sawalreverr/recything/internal/report" @@ -35,6 +36,10 @@ func AutoMigrate(db Database) { &video.Comment{}, &aboutus.AboutUs{}, &aboutus.AboutUsImage{}, + &article.WasteCategory{}, + &article.Article{}, + &article.ArticleSection{}, + &article.ArticleCategories{}, ); err != nil { log.Fatal("Database Migration Failed!") } diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 3c61d6f..670e6d1 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -9,6 +9,7 @@ import ( aboutus "github.com/sawalreverr/recything/internal/about-us" achievement "github.com/sawalreverr/recything/internal/achievements/manage_achievements/entity" adminEntity "github.com/sawalreverr/recything/internal/admin/entity" + "github.com/sawalreverr/recything/internal/article" customDataEntity "github.com/sawalreverr/recything/internal/custom-data" faqEntity "github.com/sawalreverr/recything/internal/faq" "github.com/sawalreverr/recything/internal/helper" @@ -342,6 +343,63 @@ func (m *mysqlDatabase) InitDataVideos() { log.Println("Video data added!") } +func (m *mysqlDatabase) InitArticleCategory() { + categories := []article.WasteCategory{ + {ID: 1, Name: "plastik"}, + {ID: 2, Name: "besi"}, + {ID: 3, Name: "kaca"}, + {ID: 4, Name: "organik"}, + {ID: 5, Name: "kayu"}, + {ID: 6, Name: "kertas"}, + {ID: 7, Name: "baterai"}, + {ID: 8, Name: "kaleng"}, + {ID: 9, Name: "elektronik"}, + {ID: 10, Name: "tekstil"}, + {ID: 11, Name: "minyak"}, + {ID: 12, Name: "bola lampu"}, + {ID: 13, Name: "berbahaya"}, + } + + for _, category := range categories { + m.GetDB().FirstOrCreate(&category, category) + } + log.Println("Article categories data added!") +} + +func (m *mysqlDatabase) InitArticle() { + articles := []article.Article{ + {ID: "ART0001", Title: "The Future of AI", Description: "An in-depth look at the future of artificial intelligence.", ThumbnailURL: "http://example.com/ai.jpg", AuthorID: "AD0001"}, + {ID: "ART0002", Title: "Healthy Living Tips", Description: "Tips for a healthier lifestyle.", ThumbnailURL: "http://example.com/health.jpg", AuthorID: "AD0001"}, + } + + articleSections := []article.ArticleSection{ + {ID: 1, ArticleID: "ART0001", Title: "Introduction", Description: "Introduction to AI", ImageURL: "http://example.com/ai.jpg"}, + {ID: 2, ArticleID: "ART0001", Title: "Impact on Society", Description: "How AI will impact society.", ImageURL: "http://example.com/ai.jpg"}, + {ID: 3, ArticleID: "ART0002", Title: "Diet", Description: "Healthy eating habits.", ImageURL: "http://example.com/diet.jpg"}, + {ID: 4, ArticleID: "ART0002", Title: "Exercise", Description: "The importance of regular exercise.", ImageURL: "http://example.com/ai.jpg"}, + } + + articleCategories := []article.ArticleCategories{ + {ID: 1, ArticleID: "ART0001", CategoryID: 1}, + {ID: 2, ArticleID: "ART0001", CategoryID: 3}, + {ID: 3, ArticleID: "ART0002", CategoryID: 7}, + } + + for _, article := range articles { + m.GetDB().FirstOrCreate(&article, article) + } + + for _, articleSection := range articleSections { + m.GetDB().FirstOrCreate(&articleSection, articleSection) + } + + for _, articleCategory := range articleCategories { + m.GetDB().FirstOrCreate(&articleCategory, articleCategory) + } + + log.Println("Article data added!") +} + func (m *mysqlDatabase) GetDB() *gorm.DB { return dbInstance.DB } diff --git a/internal/server/echo_server.go b/internal/server/echo_server.go index e5a899f..2c28281 100644 --- a/internal/server/echo_server.go +++ b/internal/server/echo_server.go @@ -97,6 +97,9 @@ func (s *echoServer) Start() { // leaderboard handler s.leaderboardHandler() + // Article Handler + s.articleHandler() + serverPORT := fmt.Sprintf(":%d", s.conf.Server.Port) s.app.Logger.Fatal(s.app.Start(serverPORT)) } diff --git a/internal/server/route.go b/internal/server/route.go index efad7f0..046ab68 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -16,6 +16,9 @@ import ( "github.com/sawalreverr/recything/internal/admin/handler" "github.com/sawalreverr/recything/internal/admin/repository" "github.com/sawalreverr/recything/internal/admin/usecase" + articleHandler "github.com/sawalreverr/recything/internal/article/handler" + articleRepository "github.com/sawalreverr/recything/internal/article/repository" + articleUsecase "github.com/sawalreverr/recything/internal/article/usecase" authHandler "github.com/sawalreverr/recything/internal/auth/handler" authUsecase "github.com/sawalreverr/recything/internal/auth/usecase" customDataHandler "github.com/sawalreverr/recything/internal/custom-data/handler" @@ -375,3 +378,13 @@ func (s *echoServer) leaderboardHandler() { // Get leaderboard s.gr.GET("/leaderboard", handler.GetLeaderboardHandler, AllRoleMiddleware) } + +func (s *echoServer) articleHandler() { + repositoryArticle := articleRepository.NewArticleRepository(s.db) + repositoryAdmin := repository.NewAdminRepository(s.db) + usecase := articleUsecase.NewArticleUsecase(repositoryArticle, repositoryAdmin) + handler := articleHandler.NewArticleHandler(usecase) + + // Get article by id + s.gr.GET("/article/:articleId", handler.GetArticleByID, AllRoleMiddleware) +} diff --git a/pkg/errors.go b/pkg/errors.go index 408ecf8..5efbf77 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -71,4 +71,7 @@ var ( // About Us ErrAboutUsCategoryNotFound = errors.New("about us with that category not found") + + // Article + ErrArticleNotFound = errors.New("article not found") ) From 135df03449701f4245ecb592d3c59177f74aad05 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 01:22:46 +0700 Subject: [PATCH 10/72] change method for update data achievemnts from put to ptach --- .../dto/manage_achievement_request.go | 4 +-- .../manage_achievement_handler_impl.go | 9 ++--- .../manage_achievement_usecase_impl.go | 35 ++++++++++++------- internal/server/route.go | 2 +- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/internal/achievements/manage_achievements/dto/manage_achievement_request.go b/internal/achievements/manage_achievements/dto/manage_achievement_request.go index b125a2d..8e7bf4f 100644 --- a/internal/achievements/manage_achievements/dto/manage_achievement_request.go +++ b/internal/achievements/manage_achievements/dto/manage_achievement_request.go @@ -3,7 +3,7 @@ package dto import "mime/multipart" type UpdateAchievementRequest struct { - Level string `json:"level" validate:"required"` - TargetPoint int `json:"target_point" validate:"required"` + Level string `json:"level"` + TargetPoint int `json:"target_point"` Badge *multipart.FileHeader `json:"-"` } diff --git a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go index 2a8882a..6345c69 100644 --- a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go +++ b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go @@ -3,6 +3,7 @@ package handler import ( "encoding/json" "errors" + "mime/multipart" "net/http" "strconv" @@ -84,15 +85,15 @@ func (handler ManageAchievementHandlerImpl) UpdateAchievementHandler(c echo.Cont if errForm != nil { return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) } - badge := form.File["badge"] + var badge []*multipart.FileHeader + if form != nil { + badge = form.File["badge"] + } err := handler.usecae.UpdateAchievementUsecase(&request, badge, achievementIdInt) if err != nil { if errors.Is(err, pkg.ErrAchievementNotFound) { return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrAchievementNotFound.Error()) } - if errors.Is(err, pkg.ErrBadge) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrBadge.Error()) - } if errors.Is(err, pkg.ErrBadgeMaximum) { return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrBadgeMaximum.Error()) } diff --git a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go index 0c89d24..f3c72d0 100644 --- a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go +++ b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go @@ -37,28 +37,37 @@ func (repository ManageAchievementUsecaseImpl) GetAchievementByIdUsecase(id int) } func (repository ManageAchievementUsecaseImpl) UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge []*multipart.FileHeader, id int) error { - if len(badge) == 0 { - return pkg.ErrBadge - } if len(badge) > 1 { return pkg.ErrBadgeMaximum } - validImages, errImages := helper.ImagesValidation(badge) - if errImages != nil { - return errImages - } - urlBadge, errUpload := helper.UploadToCloudinary(validImages[0], "achievements_badge") - if errUpload != nil { - return pkg.ErrUploadCloudinary + var urlBadge string + if len(badge) == 1 { + validImages, errImages := helper.ImagesValidation(badge) + if errImages != nil { + return errImages + } + urlBadgeUpload, errUpload := helper.UploadToCloudinary(validImages[0], "achievements_badge") + if errUpload != nil { + return pkg.ErrUploadCloudinary + } + urlBadge = urlBadgeUpload } achievement, err := repository.repository.GetAchievementById(id) if err != nil { return pkg.ErrAchievementNotFound } - achievement.Level = strings.ToLower(request.Level) - achievement.TargetPoint = request.TargetPoint - achievement.BadgeUrl = urlBadge + + if request.Level != "" { + achievement.Level = strings.ToLower(request.Level) + } + if request.TargetPoint != 0 { + achievement.TargetPoint = request.TargetPoint + } + if urlBadge != "" { + achievement.BadgeUrl = urlBadge + } + if err := repository.repository.UpdateAchievement(achievement, id); err != nil { return err } diff --git a/internal/server/route.go b/internal/server/route.go index 046ab68..ba8554a 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -271,7 +271,7 @@ func (s *echoServer) manageAchievement() { s.gr.GET("/achievements/:achievementId", handler.GetAchievementByIdHandler, SuperAdminOrAdminMiddleware) // update achievement - s.gr.PUT("/achievements/:achievementId", handler.UpdateAchievementHandler, SuperAdminOrAdminMiddleware) + s.gr.PATCH("/achievements/:achievementId", handler.UpdateAchievementHandler, SuperAdminOrAdminMiddleware) // delete achievement s.gr.DELETE("/achievements/:achievementId", handler.DeleteAchievementHandler, SuperAdminOrAdminMiddleware) From 088f6f35e9415234bf3f5e8df5ed78b18c5ebbda Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 12:07:19 +0700 Subject: [PATCH 11/72] docs: channge method for update data schievement from put to patch --- docs/swagger.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 84af25d..f0f023c 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -1154,11 +1154,11 @@ paths: type: string example: achievement not found - put: + patch: tags: - manage achievements - summary: Update target point of an achievement - description: Endpoint admin to update the target point of an existing achievement/badge. + summary: Update data achievement + description: Endpoint admin for update data achievement. operationId: updateAchievement parameters: - name: achievementId @@ -1171,7 +1171,7 @@ paths: security: - Bearer: [] requestBody: - required: true + required: false content: multipart/form-data:: schema: From 728865f8364abdf09b4e69050a5cfe7ea75b1d2c Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Jun 2024 00:31:20 +0700 Subject: [PATCH 12/72] refactor(manage achievement): change request body and some code on handler and usecase --- docs/swagger.yml | 19 +++++------ .../dto/manage_achievement_request.go | 7 ++-- .../manage_achievement_handler_impl.go | 30 +++++----------- .../usecase/manage_achievement_usecase.go | 2 +- .../manage_achievement_usecase_impl.go | 34 +++++++++++-------- pkg/errors.go | 5 +++ 6 files changed, 44 insertions(+), 53 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index f0f023c..94d1046 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -1177,17 +1177,14 @@ paths: schema: type: object properties: - json_data: - type: object - properties: - level: - type: string - description: New level for the achievement - example: platinum - target_point: - type: integer - description: New target point for the badge - example: 1200 + level: + type: string + description: New level for the achievement + example: platinum + target_point: + type: integer + description: New target point for the badge + example: 1200 badge: type: string format: binary diff --git a/internal/achievements/manage_achievements/dto/manage_achievement_request.go b/internal/achievements/manage_achievements/dto/manage_achievement_request.go index 8e7bf4f..06a096d 100644 --- a/internal/achievements/manage_achievements/dto/manage_achievement_request.go +++ b/internal/achievements/manage_achievements/dto/manage_achievement_request.go @@ -1,9 +1,6 @@ package dto -import "mime/multipart" - type UpdateAchievementRequest struct { - Level string `json:"level"` - TargetPoint int `json:"target_point"` - Badge *multipart.FileHeader `json:"-"` + Level string `json:"level" form:"level"` + TargetPoint int `json:"target_point" form:"target_point"` } diff --git a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go index 6345c69..8479af5 100644 --- a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go +++ b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go @@ -1,9 +1,7 @@ package handler import ( - "encoding/json" "errors" - "mime/multipart" "net/http" "strconv" @@ -74,34 +72,24 @@ func (handler ManageAchievementHandlerImpl) UpdateAchievementHandler(c echo.Cont } request := dto.UpdateAchievementRequest{} - json_data := c.FormValue("json_data") - if err := json.Unmarshal([]byte(json_data), &request); err != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) - } - if err := c.Validate(&request); err != nil { + + if err := c.Bind(&request); err != nil { return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) } - form, errForm := c.MultipartForm() - if errForm != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) - } - var badge []*multipart.FileHeader - if form != nil { - badge = form.File["badge"] - } + badge, _ := c.FormFile("badge") err := handler.usecae.UpdateAchievementUsecase(&request, badge, achievementIdInt) if err != nil { if errors.Is(err, pkg.ErrAchievementNotFound) { return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrAchievementNotFound.Error()) } - if errors.Is(err, pkg.ErrBadgeMaximum) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrBadgeMaximum.Error()) + if errors.Is(err, pkg.ErrFileTooLarge) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrFileTooLarge.Error()) } - if errors.Is(err, errors.New("upload image size must less than 2MB")) { - return helper.ErrorHandler(c, http.StatusBadRequest, "upload image size must less than 2MB") + if errors.Is(err, pkg.ErrInvalidFileType) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrInvalidFileType.Error()) } - if errors.Is(err, errors.New("only image allowed")) { - return helper.ErrorHandler(c, http.StatusBadRequest, "only image allowed") + if errors.Is(err, pkg.ErrOpenFile) { + return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrOpenFile.Error()) } if errors.Is(err, pkg.ErrUploadCloudinary) { return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrUploadCloudinary.Error()) diff --git a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go index d5e8803..b762503 100644 --- a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go +++ b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go @@ -10,6 +10,6 @@ import ( type ManageAchievementUsecase interface { GetAllArchievementUsecase() ([]*archievement.Achievement, error) GetAchievementByIdUsecase(id int) (*archievement.Achievement, error) - UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge []*multipart.FileHeader, id int) error + UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge *multipart.FileHeader, id int) error DeleteAchievementUsecase(id int) error } diff --git a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go index f3c72d0..cb7cb7d 100644 --- a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go +++ b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go @@ -36,30 +36,34 @@ func (repository ManageAchievementUsecaseImpl) GetAchievementByIdUsecase(id int) return achievement, nil } -func (repository ManageAchievementUsecaseImpl) UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge []*multipart.FileHeader, id int) error { - if len(badge) > 1 { - return pkg.ErrBadgeMaximum +func (repository ManageAchievementUsecaseImpl) UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge *multipart.FileHeader, id int) error { + achievement, err := repository.repository.GetAchievementById(id) + if err != nil { + return pkg.ErrAchievementNotFound } var urlBadge string - if len(badge) == 1 { - validImages, errImages := helper.ImagesValidation(badge) - if errImages != nil { - return errImages + if badge != nil { + if badge.Size > 2*1024*1024 { + return pkg.ErrFileTooLarge } - urlBadgeUpload, errUpload := helper.UploadToCloudinary(validImages[0], "achievements_badge") + if !strings.HasPrefix(badge.Header.Get("Content-Type"), "image") { + return pkg.ErrInvalidFileType + } + src, errOpen := badge.Open() + if errOpen != nil { + return pkg.ErrOpenFile + } + + urlBadgeUpload, errUpload := helper.UploadToCloudinary(src, "achievement_badge") if errUpload != nil { - return pkg.ErrUploadCloudinary + return errUpload } urlBadge = urlBadgeUpload - } - - achievement, err := repository.repository.GetAchievementById(id) - if err != nil { - return pkg.ErrAchievementNotFound + defer src.Close() } if request.Level != "" { - achievement.Level = strings.ToLower(request.Level) + achievement.Level = request.Level } if request.TargetPoint != 0 { achievement.TargetPoint = request.TargetPoint diff --git a/pkg/errors.go b/pkg/errors.go index 5efbf77..0ed94e6 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -74,4 +74,9 @@ var ( // Article ErrArticleNotFound = errors.New("article not found") + + // Error file + ErrFileTooLarge = errors.New("upload image size must less than 2MB") + ErrInvalidFileType = errors.New("invalid file type") + ErrOpenFile = errors.New("failed to open file") ) From 10eb029b0d36cd4fd4dcaebe7f31892a69538ac1 Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Tue, 11 Jun 2024 01:30:56 +0700 Subject: [PATCH 13/72] chore(article): update some --- internal/article/dto.go | 5 +++-- internal/article/entity.go | 16 +++++++++++++--- internal/article/repository/repository.go | 8 ++++---- internal/article/usecase/usecase.go | 6 +++--- internal/database/mysql.go | 6 +++--- 5 files changed, 26 insertions(+), 15 deletions(-) diff --git a/internal/article/dto.go b/internal/article/dto.go index 7e06079..eba4b27 100644 --- a/internal/article/dto.go +++ b/internal/article/dto.go @@ -32,8 +32,9 @@ type ArticleDetail struct { ThumbnailURL string `json:"thumbnail_url"` CreatedAt time.Time `json:"created_at"` - Categories []WasteCategory `json:"categories"` - Sections []ArticleSection `json:"sections"` + WasteCategories []WasteCategory `json:"waste_categories"` + ContentCategories []ContentCategory `json:"content_categories"` + Sections []ArticleSection `json:"sections"` } type ArticleResponsePagination struct { diff --git a/internal/article/entity.go b/internal/article/entity.go index 31e2d48..f59d01b 100644 --- a/internal/article/entity.go +++ b/internal/article/entity.go @@ -31,10 +31,20 @@ type WasteCategory struct { DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` } +type ContentCategory struct { + ID uint `json:"id" gorm:"primaryKey"` + Name string `json:"name" gorm:"type:varchar(50);unique;not null"` + + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +} + type ArticleCategories struct { - ID uint `gorm:"primaryKey"` - ArticleID string `gorm:"type:varchar(7)"` - CategoryID uint + ID uint `gorm:"primaryKey"` + ArticleID string `gorm:"type:varchar(7)"` + WasteCategoryID uint + ContentCategoryID int CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` diff --git a/internal/article/repository/repository.go b/internal/article/repository/repository.go index 4c3773f..ac4d8eb 100644 --- a/internal/article/repository/repository.go +++ b/internal/article/repository/repository.go @@ -94,7 +94,7 @@ func (r *articleRepository) Delete(articleID string) error { func (r *articleRepository) FindCategories(articleID string) (*[]art.WasteCategory, error) { var articleCategories []art.ArticleCategories - var categories []art.WasteCategory + var wasteCategories []art.WasteCategory if err := r.DB.GetDB().Where("article_id = ?", articleID).Find(&articleCategories).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -105,12 +105,12 @@ func (r *articleRepository) FindCategories(articleID string) (*[]art.WasteCatego var categoryIDs []uint for _, ac := range articleCategories { - categoryIDs = append(categoryIDs, ac.CategoryID) + categoryIDs = append(categoryIDs, ac.WasteCategoryID) } - if err := r.DB.GetDB().Where("id IN (?)", categoryIDs).Find(&categories).Error; err != nil { + if err := r.DB.GetDB().Where("id IN (?)", categoryIDs).Find(&wasteCategories).Error; err != nil { return nil, err } - return &categories, nil + return &wasteCategories, nil } diff --git a/internal/article/usecase/usecase.go b/internal/article/usecase/usecase.go index f262842..26dd76a 100644 --- a/internal/article/usecase/usecase.go +++ b/internal/article/usecase/usecase.go @@ -57,7 +57,7 @@ func (uc *articleUsecase) Delete(articleID string) error { func (uc *articleUsecase) GetArticleDetail(article art.Article) *art.ArticleDetail { adminDetail, _ := uc.GetDetailAuthor(article.AuthorID) - categories, _ := uc.articleRepo.FindCategories(article.ID) + // categories, _ := uc.articleRepo.FindCategories(article.ID) articleDetail := art.ArticleDetail{ ID: article.ID, @@ -66,8 +66,8 @@ func (uc *articleUsecase) GetArticleDetail(article art.Article) *art.ArticleDeta Description: article.Description, ThumbnailURL: article.ThumbnailURL, CreatedAt: article.CreatedAt, - Categories: *categories, - Sections: article.Sections, + // Categories: *categories, + Sections: article.Sections, } return &articleDetail diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 670e6d1..f939d1a 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -380,9 +380,9 @@ func (m *mysqlDatabase) InitArticle() { } articleCategories := []article.ArticleCategories{ - {ID: 1, ArticleID: "ART0001", CategoryID: 1}, - {ID: 2, ArticleID: "ART0001", CategoryID: 3}, - {ID: 3, ArticleID: "ART0002", CategoryID: 7}, + {ID: 1, ArticleID: "ART0001", WasteCategoryID: 1}, + {ID: 2, ArticleID: "ART0001", WasteCategoryID: 3}, + {ID: 3, ArticleID: "ART0002", WasteCategoryID: 7}, } for _, article := range articles { From ad478cc400663f3ebd0c41348d356e2dc3224831 Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Tue, 11 Jun 2024 01:32:40 +0700 Subject: [PATCH 14/72] chore: update dummy for video category --- internal/database/mysql.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 670e6d1..700f151 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -250,12 +250,11 @@ func (m *mysqlDatabase) InitAchievements() { func (m *mysqlDatabase) InitVideoCategories() { videoCategories := []video.VideoCategory{ - {Name: "Tips"}, - {Name: "Daur Ulang"}, - {Name: "Tutorial"}, - {Name: "Edukasi"}, - {Name: "Kampanye"}, - {Name: "Lainnya"}, + {Name: "tips"}, + {Name: "daur ulang"}, + {Name: "tutorial"}, + {Name: "edukasi"}, + {Name: "kampanye"}, } for _, videoCategory := range videoCategories { m.GetDB().FirstOrCreate(&videoCategory, videoCategory) From 15416f3010db60fb4034c168cd5c5f26313d3765 Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Tue, 11 Jun 2024 02:04:00 +0700 Subject: [PATCH 15/72] chore(article): update get by id article --- internal/article/dto.go | 6 ++--- internal/article/entity.go | 4 +-- internal/article/repository/repository.go | 31 +++++++++++++++++------ internal/article/usecase/usecase.go | 19 +++++++------- internal/database/mysql.go | 6 ++--- 5 files changed, 41 insertions(+), 25 deletions(-) diff --git a/internal/article/dto.go b/internal/article/dto.go index eba4b27..6c16628 100644 --- a/internal/article/dto.go +++ b/internal/article/dto.go @@ -32,9 +32,9 @@ type ArticleDetail struct { ThumbnailURL string `json:"thumbnail_url"` CreatedAt time.Time `json:"created_at"` - WasteCategories []WasteCategory `json:"waste_categories"` - ContentCategories []ContentCategory `json:"content_categories"` - Sections []ArticleSection `json:"sections"` + WasteCategories []WasteCategory `json:"waste_categories"` + ContentCategories []VideoCategory `json:"content_categories"` + Sections []ArticleSection `json:"sections"` } type ArticleResponsePagination struct { diff --git a/internal/article/entity.go b/internal/article/entity.go index f59d01b..393d70c 100644 --- a/internal/article/entity.go +++ b/internal/article/entity.go @@ -31,7 +31,7 @@ type WasteCategory struct { DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` } -type ContentCategory struct { +type VideoCategory struct { ID uint `json:"id" gorm:"primaryKey"` Name string `json:"name" gorm:"type:varchar(50);unique;not null"` @@ -75,7 +75,7 @@ type ArticleRepository interface { Delete(articleID string) error // Category Repository - FindCategories(articleID string) (*[]WasteCategory, error) + FindCategories(articleID string) (*[]WasteCategory, *[]VideoCategory, error) // Article Section Repository } diff --git a/internal/article/repository/repository.go b/internal/article/repository/repository.go index ac4d8eb..f09c252 100644 --- a/internal/article/repository/repository.go +++ b/internal/article/repository/repository.go @@ -92,25 +92,40 @@ func (r *articleRepository) Delete(articleID string) error { return nil } -func (r *articleRepository) FindCategories(articleID string) (*[]art.WasteCategory, error) { +func (r *articleRepository) FindCategories(articleID string) (*[]art.WasteCategory, *[]art.VideoCategory, error) { var articleCategories []art.ArticleCategories var wasteCategories []art.WasteCategory + var contentCategories []art.VideoCategory if err := r.DB.GetDB().Where("article_id = ?", articleID).Find(&articleCategories).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, pkg.ErrArticleNotFound + return nil, nil, pkg.ErrArticleNotFound } - return nil, err + return nil, nil, err } - var categoryIDs []uint + var wasteCategoryIDs []uint + var contentCategoryIDs []uint for _, ac := range articleCategories { - categoryIDs = append(categoryIDs, ac.WasteCategoryID) + if ac.WasteCategoryID != 0 { + wasteCategoryIDs = append(wasteCategoryIDs, ac.WasteCategoryID) + } + if ac.ContentCategoryID != 0 { + contentCategoryIDs = append(contentCategoryIDs, uint(ac.ContentCategoryID)) + } } - if err := r.DB.GetDB().Where("id IN (?)", categoryIDs).Find(&wasteCategories).Error; err != nil { - return nil, err + if len(wasteCategoryIDs) > 0 { + if err := r.DB.GetDB().Where("id IN (?)", wasteCategoryIDs).Find(&wasteCategories).Error; err != nil { + return nil, nil, err + } + } + + if len(contentCategoryIDs) > 0 { + if err := r.DB.GetDB().Where("id IN (?)", contentCategoryIDs).Find(&contentCategories).Error; err != nil { + return nil, nil, err + } } - return &wasteCategories, nil + return &wasteCategories, &contentCategories, nil } diff --git a/internal/article/usecase/usecase.go b/internal/article/usecase/usecase.go index 26dd76a..f27a9e5 100644 --- a/internal/article/usecase/usecase.go +++ b/internal/article/usecase/usecase.go @@ -57,17 +57,18 @@ func (uc *articleUsecase) Delete(articleID string) error { func (uc *articleUsecase) GetArticleDetail(article art.Article) *art.ArticleDetail { adminDetail, _ := uc.GetDetailAuthor(article.AuthorID) - // categories, _ := uc.articleRepo.FindCategories(article.ID) + wasteCategories, contentCategories, _ := uc.articleRepo.FindCategories(article.ID) articleDetail := art.ArticleDetail{ - ID: article.ID, - AuthorID: *adminDetail, - Title: article.Title, - Description: article.Description, - ThumbnailURL: article.ThumbnailURL, - CreatedAt: article.CreatedAt, - // Categories: *categories, - Sections: article.Sections, + ID: article.ID, + AuthorID: *adminDetail, + Title: article.Title, + Description: article.Description, + ThumbnailURL: article.ThumbnailURL, + CreatedAt: article.CreatedAt, + WasteCategories: *wasteCategories, + ContentCategories: *contentCategories, + Sections: article.Sections, } return &articleDetail diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 1838ecb..2704db4 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -379,9 +379,9 @@ func (m *mysqlDatabase) InitArticle() { } articleCategories := []article.ArticleCategories{ - {ID: 1, ArticleID: "ART0001", WasteCategoryID: 1}, - {ID: 2, ArticleID: "ART0001", WasteCategoryID: 3}, - {ID: 3, ArticleID: "ART0002", WasteCategoryID: 7}, + {ID: 1, ArticleID: "ART0001", WasteCategoryID: 1, ContentCategoryID: 2}, + {ID: 2, ArticleID: "ART0001", WasteCategoryID: 3, ContentCategoryID: 4}, + {ID: 3, ArticleID: "ART0002", WasteCategoryID: 7, ContentCategoryID: 1}, } for _, article := range articles { From eaac6184f6f94897d189ccff47c4c7f5c250b869 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 17:58:07 +0700 Subject: [PATCH 16/72] refactor(manage vide): change method for update video to patch and change some logic on use case and handler --- internal/server/route.go | 2 +- .../manage_video/dto/manage_video_request.go | 8 +-- .../handler/manage_video_handler_impl.go | 7 ++- .../usecase/manage_video_usecase_impl.go | 63 +++++++++++-------- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/internal/server/route.go b/internal/server/route.go index ba8554a..c2d10bb 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -337,7 +337,7 @@ func (s *echoServer) manageVideo() { s.gr.GET("/videos/data/:videoId", handler.GetDetailsDataVideoByIdHandler, SuperAdminOrAdminMiddleware) // update data video - s.gr.PUT("/videos/data/:videoId", handler.UpdateDataVideoHandler, SuperAdminOrAdminMiddleware) + s.gr.PATCH("/videos/data/:videoId", handler.UpdateDataVideoHandler, SuperAdminOrAdminMiddleware) // delete data video s.gr.DELETE("/videos/data/:videoId", handler.DeleteDataVideoHandler, SuperAdminOrAdminMiddleware) diff --git a/internal/video/manage_video/dto/manage_video_request.go b/internal/video/manage_video/dto/manage_video_request.go index 4566819..5c496a7 100644 --- a/internal/video/manage_video/dto/manage_video_request.go +++ b/internal/video/manage_video/dto/manage_video_request.go @@ -15,9 +15,9 @@ type CreateCategoryVideoRequest struct { } type UpdateDataVideoRequest struct { - Title string `json:"title" validate:"required"` - Description string `json:"description" validate:"required"` - LinkVideo string `json:"link_video" validate:"required"` - CategoryId int `json:"category_id" validate:"required"` + Title string `json:"title"` + Description string `json:"description"` + LinkVideo string `json:"link_video"` + CategoryId int `json:"category_id"` Thumbnail *multipart.FileHeader `json:"-"` } diff --git a/internal/video/manage_video/handler/manage_video_handler_impl.go b/internal/video/manage_video/handler/manage_video_handler_impl.go index 392bd3c..3ca95ff 100644 --- a/internal/video/manage_video/handler/manage_video_handler_impl.go +++ b/internal/video/manage_video/handler/manage_video_handler_impl.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "math" + "mime/multipart" "net/http" "strconv" @@ -210,7 +211,11 @@ func (handler *ManageVideoHandlerImpl) UpdateDataVideoHandler(c echo.Context) er if errForm != nil { return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) } - thumbnail := form.File["thumbnail"] + var thumbnail []*multipart.FileHeader + if form != nil { + thumbnail = form.File["thumbnail"] + } + if err := handler.ManageVideoUsecase.UpdateDataVideoUseCase(&request, thumbnail, idInt); err != nil { if errors.Is(err, pkg.ErrVideoNotFound) { return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) diff --git a/internal/video/manage_video/usecase/manage_video_usecase_impl.go b/internal/video/manage_video/usecase/manage_video_usecase_impl.go index b351a41..77ea74d 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase_impl.go +++ b/internal/video/manage_video/usecase/manage_video_usecase_impl.go @@ -107,42 +107,55 @@ func (usecase *ManageVideoUsecaseImpl) GetDetailsDataVideoByIdUseCase(id int) (* } func (usecase *ManageVideoUsecaseImpl) UpdateDataVideoUseCase(request *dto.UpdateDataVideoRequest, thumbnail []*multipart.FileHeader, id int) error { - if len(thumbnail) == 0 { - return pkg.ErrThumbnail - } if len(thumbnail) > 1 { return pkg.ErrThumbnailMaximum } - validImages, errImages := helper.ImagesValidation(thumbnail) - if errImages != nil { - return errImages + var urlThumbnail string + if len(thumbnail) == 1 { + validImages, errImages := helper.ImagesValidation(thumbnail) + if errImages != nil { + return errImages + } + urlThumbnailUpload, errUpload := helper.UploadToCloudinary(validImages[0], "video_thumbnail_update") + if errUpload != nil { + return pkg.ErrUploadCloudinary + } + urlThumbnail = urlThumbnailUpload } - if _, err := usecase.manageVideoRepository.GetDetailsDataVideoById(id); err != nil { + video, err := usecase.manageVideoRepository.GetDetailsDataVideoById(id) + if err != nil { return pkg.ErrVideoNotFound } - if _, err := usecase.manageVideoRepository.GetCategoryVideoById(request.CategoryId); err != nil { - return pkg.ErrVideoCategoryNotFound + + if request.Title != "" { + video.Title = request.Title } - view, errGetView := helper.GetVideoViewCount(request.LinkVideo) - if errGetView != nil { - return errGetView + if request.Description != "" { + video.Description = request.Description } - urlThumbnail, errUpload := helper.UploadToCloudinary(validImages[0], "video_thumbnail_update") - if errUpload != nil { - return pkg.ErrUploadCloudinary + if urlThumbnail != "" { + video.Thumbnail = urlThumbnail } - - intView := int(view) - video := video.Video{ - Title: request.Title, - Description: request.Description, - Thumbnail: urlThumbnail, - Link: request.LinkVideo, - VideoCategoryID: request.CategoryId, - Viewer: intView, + if request.LinkVideo != "" { + view, errGetView := helper.GetVideoViewCount(request.LinkVideo) + if errGetView != nil { + return errGetView + } + if view != 0 { + intView := int(view) + video.Viewer = intView + } + video.Link = request.LinkVideo + } + if request.CategoryId != 0 { + if _, err := usecase.manageVideoRepository.GetCategoryVideoById(request.CategoryId); err != nil { + return pkg.ErrVideoCategoryNotFound + } + video.VideoCategoryID = request.CategoryId } - if err := usecase.manageVideoRepository.UpdateDataVideo(&video, id); err != nil { + + if err := usecase.manageVideoRepository.UpdateDataVideo(video, id); err != nil { return err } return nil From 956f8b9abe8aa96fbe61af1b7c88cd3686902f5c Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 18:06:05 +0700 Subject: [PATCH 17/72] docs: change http method for update data video from put to patch --- docs/swagger.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 94d1046..685b1bf 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -3285,7 +3285,7 @@ paths: message: type: string example: video not found - put: + patch: tags: - manage videos summary: Update Data Video @@ -3302,7 +3302,7 @@ paths: example: 1 description: ID of video requestBody: - required: true + required: false content: multipart/form-data: schema: From 9b866ed5090e572addbdf148be871361e92c453a Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Tue, 11 Jun 2024 22:13:47 +0700 Subject: [PATCH 18/72] feat(article): update feature article: getall, getbycategory, getbykeyword --- internal/article/dto.go | 16 +- internal/article/entity.go | 25 ++- internal/article/handler/handler.go | 42 +++++ internal/article/repository/repository.go | 123 +++++++++++++-- internal/article/usecase/usecase.go | 182 ++++++++++++++++++++-- internal/database/mysql.go | 6 +- internal/server/route.go | 9 ++ pkg/errors.go | 3 +- 8 files changed, 365 insertions(+), 41 deletions(-) diff --git a/internal/article/dto.go b/internal/article/dto.go index 6c16628..8fca1ad 100644 --- a/internal/article/dto.go +++ b/internal/article/dto.go @@ -5,14 +5,16 @@ import ( ) type ArticleInput struct { - Title string `json:"title"` - Description string `json:"description"` - ThumbnailURL string `json:"thumbnail_url"` - Categories []string `json:"categories"` - Sections []ArticleSectionInput `json:"sections"` + Title string `json:"title"` + Description string `json:"description"` + ThumbnailURL string `json:"thumbnail_url"` + WasteCategories []string `json:"waste_categories"` + ContentCategories []string `json:"content_categories"` + Sections []ArticleSection `json:"sections"` } type ArticleSectionInput struct { + ArticleID string `json:"article_id"` Title string `json:"title"` Description string `json:"description"` ImageURL string `json:"image_url"` @@ -39,7 +41,7 @@ type ArticleDetail struct { type ArticleResponsePagination struct { Total int64 `json:"total"` - Page int `json:"page"` - Limit int `json:"limit"` + Page uint `json:"page"` + Limit uint `json:"limit"` Articles []ArticleDetail `json:"articles"` } diff --git a/internal/article/entity.go b/internal/article/entity.go index 393d70c..1bb127f 100644 --- a/internal/article/entity.go +++ b/internal/article/entity.go @@ -67,32 +67,47 @@ type ArticleRepository interface { // Article Repository Create(article Article) (*Article, error) FindByID(articleID string) (*Article, error) - FindAll(page, limit uint) (*[]Article, error) + FindAll(page, limit uint) (*[]Article, int64, error) FindLastID() (string, error) FindByKeyword(keyword string) (*[]Article, error) - FindByCategory(categoryName string) (*[]Article, error) + FindByCategory(categoryName string, categoryType string) (*[]Article, error) Update(article Article) error Delete(articleID string) error // Category Repository FindCategories(articleID string) (*[]WasteCategory, *[]VideoCategory, error) + FindCategoryByName(categoryName, categoryType string) (uint, error) // Article Section Repository + CreateSection(section ArticleSection) error + UpdateSection(section ArticleSection) error + DeleteSection(sectionID uint) error + DeleteAllSection(articleID string) error + + // Article Categories Repository + CreateArticleCategory(categories ArticleCategories) error + UpdateArticleCategory(categories ArticleCategories) error + DeleteAllArticleCategory(articleID string) error } type ArticleUsecase interface { - NewArticle(article ArticleInput) (*ArticleDetail, error) + NewArticle(article ArticleInput, authorId string) (*ArticleDetail, error) GetArticleByID(articleID string) (*ArticleDetail, error) - GetAllArticle(page, limit uint) (*ArticleResponsePagination, error) + GetAllArticle(page, limit int) (*ArticleResponsePagination, error) GetArticleByKeyword(keyword string) (*[]ArticleDetail, error) - GetArticleByCategory(categoryName string) (*[]ArticleDetail, error) + GetArticleByCategory(categoryName string, categoryType string) (*[]ArticleDetail, error) Update(articleID string, article ArticleInput) error Delete(articleID string) error GetArticleDetail(article Article) *ArticleDetail GetDetailAuthor(authorID string) (*AdminDetail, error) + + // add usecase yang belum saya buat dibawah } type ArticleHandler interface { + GetAllArticle(c echo.Context) error + GetArticleByKeyword(c echo.Context) error + GetArticleByCategory(c echo.Context) error GetArticleByID(c echo.Context) error } diff --git a/internal/article/handler/handler.go b/internal/article/handler/handler.go index 9d71d2a..ed300c8 100644 --- a/internal/article/handler/handler.go +++ b/internal/article/handler/handler.go @@ -3,6 +3,7 @@ package article import ( "errors" "net/http" + "strconv" "github.com/labstack/echo/v4" art "github.com/sawalreverr/recything/internal/article" @@ -18,6 +19,47 @@ func NewArticleHandler(uc art.ArticleUsecase) art.ArticleHandler { return &articleHandler{usecase: uc} } +func (h *articleHandler) GetAllArticle(c echo.Context) error { + page, _ := strconv.Atoi(c.QueryParam("page")) + if page == 0 { + page = 1 + } + limit, _ := strconv.Atoi(c.QueryParam("limit")) + if limit == 0 { + limit = 10 + } + + response, err := h.usecase.GetAllArticle(page, limit) + if err != nil { + helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + } + + return helper.ResponseHandler(c, http.StatusOK, "ok", response) +} + +func (h *articleHandler) GetArticleByKeyword(c echo.Context) error { + keyword := c.QueryParam("keyword") + + response, err := h.usecase.GetArticleByKeyword(keyword) + if err != nil { + helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + } + + return helper.ResponseHandler(c, http.StatusOK, "ok", response) +} + +func (h *articleHandler) GetArticleByCategory(c echo.Context) error { + categoryType := c.QueryParam("type") + categoryName := c.QueryParam("name") + + response, err := h.usecase.GetArticleByCategory(categoryName, categoryType) + if err != nil { + helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + } + + return helper.ResponseHandler(c, http.StatusOK, "ok", response) +} + func (h *articleHandler) GetArticleByID(c echo.Context) error { articleId := c.Param("articleId") diff --git a/internal/article/repository/repository.go b/internal/article/repository/repository.go index f09c252..cb64765 100644 --- a/internal/article/repository/repository.go +++ b/internal/article/repository/repository.go @@ -36,13 +36,19 @@ func (r *articleRepository) FindByID(articleID string) (*art.Article, error) { return &article, nil } -func (r *articleRepository) FindAll(page, limit uint) (*[]art.Article, error) { +func (r *articleRepository) FindAll(page, limit uint) (*[]art.Article, int64, error) { var articles []art.Article + var total int64 + + db := r.DB.GetDB().Model(&art.Article{}) + db.Count(&total) + offset := (page - 1) * limit if err := r.DB.GetDB().Preload("Categories").Preload("Sections").Limit(int(limit)).Offset(int(offset)).Find(&articles).Error; err != nil { - return nil, err + return nil, 0, err } - return &articles, nil + + return &articles, total, nil } func (r *articleRepository) FindLastID() (string, error) { @@ -57,23 +63,44 @@ func (r *articleRepository) FindLastID() (string, error) { func (r *articleRepository) FindByKeyword(keyword string) (*[]art.Article, error) { var articles []art.Article query := "%" + keyword + "%" - if err := r.DB.GetDB().Preload("Categories").Preload("Sections"). - Where("title LIKE ? OR description LIKE ?", query, query). + + if err := r.DB.GetDB(). + Preload("Categories"). + Preload("Sections"). + Joins("LEFT JOIN article_categories ON articles.id = article_categories.article_id"). + Joins("LEFT JOIN waste_categories ON article_categories.waste_category_id = waste_categories.id"). + Joins("LEFT JOIN video_categories ON article_categories.content_category_id = video_categories.id"). + Where("articles.title LIKE ? OR articles.description LIKE ? OR waste_categories.name LIKE ? OR video_categories.name LIKE ?", query, query, query, query). Find(&articles).Error; err != nil { return nil, err } + return &articles, nil } -func (r *articleRepository) FindByCategory(categoryName string) (*[]art.Article, error) { +func (r *articleRepository) FindByCategory(categoryName string, categoryType string) (*[]art.Article, error) { var articles []art.Article - if err := r.DB.GetDB().Preload("Categories").Preload("Sections"). - Joins("JOIN article_categories ON articles.id = article_categories.article_id"). - Joins("JOIN categories ON article_categories.category_id = categories.id"). - Where("categories.name = ?", categoryName). - Find(&articles).Error; err != nil { - return nil, err + + if categoryType == "waste" { + if err := r.DB.GetDB().Preload("Categories").Preload("Sections"). + Joins("JOIN article_categories ON articles.id = article_categories.article_id"). + Joins("JOIN waste_categories ON article_categories.waste_category_id = waste_categories.id"). + Where("waste_categories.name = ?", categoryName). + Find(&articles).Error; err != nil { + return nil, err + } + } else if categoryType == "content" { + if err := r.DB.GetDB().Preload("Categories").Preload("Sections"). + Joins("JOIN article_categories ON articles.id = article_categories.article_id"). + Joins("JOIN video_categories ON article_categories.content_category_id = video_categories.id"). + Where("video_categories.name = ?", categoryName). + Find(&articles).Error; err != nil { + return nil, err + } + } else { + return nil, pkg.ErrCategoryArticleNotFound } + return &articles, nil } @@ -129,3 +156,75 @@ func (r *articleRepository) FindCategories(articleID string) (*[]art.WasteCatego return &wasteCategories, &contentCategories, nil } + +func (r *articleRepository) CreateSection(section art.ArticleSection) error { + if err := r.DB.GetDB().Create(§ion).Error; err != nil { + return err + } + return nil +} + +func (r *articleRepository) UpdateSection(section art.ArticleSection) error { + if err := r.DB.GetDB().Save(§ion).Error; err != nil { + return err + } + return nil +} + +func (r *articleRepository) DeleteSection(sectionID uint) error { + var section art.ArticleSection + if err := r.DB.GetDB().Delete(§ion, "id = ?", sectionID).Error; err != nil { + return err + } + return nil +} + +func (r *articleRepository) DeleteAllSection(articleID string) error { + if err := r.DB.GetDB().Where("article_id = ?", articleID).Delete(&art.ArticleSection{}).Error; err != nil { + return err + } + return nil +} + +func (r *articleRepository) CreateArticleCategory(categories art.ArticleCategories) error { + if err := r.DB.GetDB().Create(&categories).Error; err != nil { + return err + } + return nil +} + +func (r *articleRepository) UpdateArticleCategory(categories art.ArticleCategories) error { + if err := r.DB.GetDB().Save(&categories).Error; err != nil { + return err + } + return nil +} + +func (r *articleRepository) DeleteAllArticleCategory(articleID string) error { + if err := r.DB.GetDB().Where("article_id = ?", articleID).Delete(&art.ArticleCategories{}).Error; err != nil { + return err + } + return nil +} + +func (r *articleRepository) FindCategoryByName(categoryName, categoryType string) (uint, error) { + if categoryType == "waste" { + var wasteCategory art.WasteCategory + if err := r.DB.GetDB().Where("name = ?", categoryName).First(&wasteCategory).Error; err != nil { + return 0, err + } + + return wasteCategory.ID, nil + } + + if categoryType == "content" { + var videoCategory art.VideoCategory + if err := r.DB.GetDB().Where("name = ?", categoryName).First(&videoCategory).Error; err != nil { + return 0, err + } + + return videoCategory.ID, nil + } + + return 0, pkg.ErrCategoryArticleNotFound +} diff --git a/internal/article/usecase/usecase.go b/internal/article/usecase/usecase.go index f27a9e5..770f2a3 100644 --- a/internal/article/usecase/usecase.go +++ b/internal/article/usecase/usecase.go @@ -3,6 +3,7 @@ package article import ( admin "github.com/sawalreverr/recything/internal/admin/repository" art "github.com/sawalreverr/recything/internal/article" + "github.com/sawalreverr/recything/internal/helper" ) type articleUsecase struct { @@ -14,9 +15,68 @@ func NewArticleUsecase(articleRepo art.ArticleRepository, adminRepo admin.AdminR return &articleUsecase{articleRepo: articleRepo, adminRepo: adminRepo} } -func (uc *articleUsecase) NewArticle(article art.ArticleInput) (*art.ArticleDetail, error) { +func (u *articleUsecase) NewArticle(article art.ArticleInput, authorId string) (*art.ArticleDetail, error) { + lastID, _ := u.articleRepo.FindLastID() + newID := helper.GenerateCustomID(lastID, "ART") - return nil, nil + newArticle := art.Article{ + ID: newID, + Title: article.Title, + Description: article.Description, + ThumbnailURL: article.ThumbnailURL, + AuthorID: authorId, + } + + createdArticle, err := u.articleRepo.Create(newArticle) + if err != nil { + return nil, err + } + + for _, section := range article.Sections { + section.ArticleID = createdArticle.ID + if err := u.articleRepo.CreateSection(section); err != nil { + _ = u.articleRepo.Delete(createdArticle.ID) + return nil, err + } + } + + for _, categoryName := range article.WasteCategories { + categoryID, err := u.articleRepo.FindCategoryByName(categoryName, "waste") + if err != nil { + _ = u.articleRepo.Delete(createdArticle.ID) + return nil, err + } + + articleCategory := art.ArticleCategories{ + ArticleID: createdArticle.ID, + WasteCategoryID: categoryID, + } + + if err := u.articleRepo.CreateArticleCategory(articleCategory); err != nil { + _ = u.articleRepo.Delete(createdArticle.ID) + return nil, err + } + } + + for _, categoryName := range article.ContentCategories { + categoryID, err := u.articleRepo.FindCategoryByName(categoryName, "content") + if err != nil { + _ = u.articleRepo.Delete(createdArticle.ID) + return nil, err + } + + articleCategory := art.ArticleCategories{ + ArticleID: createdArticle.ID, + ContentCategoryID: int(categoryID), + } + + if err := u.articleRepo.CreateArticleCategory(articleCategory); err != nil { + _ = u.articleRepo.Delete(createdArticle.ID) + return nil, err + } + } + + return u.GetArticleDetail(*createdArticle), nil } func (uc *articleUsecase) GetArticleByID(articleID string) (*art.ArticleDetail, error) { @@ -25,32 +85,128 @@ func (uc *articleUsecase) GetArticleByID(articleID string) (*art.ArticleDetail, return nil, err } - articleDetail := uc.GetArticleDetail(*articleFound) - - return articleDetail, nil + return uc.GetArticleDetail(*articleFound), nil } -func (uc *articleUsecase) GetAllArticle(page, limit uint) (*art.ArticleResponsePagination, error) { +func (u *articleUsecase) GetAllArticle(page, limit int) (*art.ArticleResponsePagination, error) { + articles, total, err := u.articleRepo.FindAll(uint(page), uint(limit)) + if err != nil { + return nil, err + } - return nil, nil + articleDetails := make([]art.ArticleDetail, len(*articles)) + for i, article := range *articles { + articleDetails[i] = *u.GetArticleDetail(article) + } + + return &art.ArticleResponsePagination{ + Total: total, + Articles: articleDetails, + Page: uint(page), + Limit: uint(limit), + }, nil } -func (uc *articleUsecase) GetArticleByKeyword(keyword string) (*[]art.ArticleDetail, error) { +func (u *articleUsecase) GetArticleByKeyword(keyword string) (*[]art.ArticleDetail, error) { + articles, err := u.articleRepo.FindByKeyword(keyword) + if err != nil { + return nil, err + } + + articleDetails := make([]art.ArticleDetail, len(*articles)) + for i, article := range *articles { + articleDetails[i] = *u.GetArticleDetail(article) + } - return nil, nil + return &articleDetails, nil } -func (uc *articleUsecase) GetArticleByCategory(categoryName string) (*[]art.ArticleDetail, error) { +func (u *articleUsecase) GetArticleByCategory(categoryName string, categoryType string) (*[]art.ArticleDetail, error) { + articles, err := u.articleRepo.FindByCategory(categoryName, categoryType) + if err != nil { + return nil, err + } + + articleDetails := make([]art.ArticleDetail, len(*articles)) + for i, article := range *articles { + articleDetails[i] = *u.GetArticleDetail(article) + } - return nil, nil + return &articleDetails, nil } -func (uc *articleUsecase) Update(articleID string, article art.ArticleInput) error { +func (u *articleUsecase) Update(articleID string, article art.ArticleInput) error { + articleFound, err := u.articleRepo.FindByID(articleID) + if err != nil { + return err + } - return nil + articleToUpdate := art.Article{ + ID: articleFound.ID, + Title: article.Title, + Description: article.Description, + ThumbnailURL: article.ThumbnailURL, + AuthorID: articleFound.AuthorID, + } + + if err := u.articleRepo.DeleteAllSection(articleID); err != nil { + return err + } + + for _, section := range article.Sections { + section.ArticleID = articleID + if err := u.articleRepo.CreateSection(section); err != nil { + return err + } + } + + if err := u.articleRepo.DeleteAllArticleCategory(articleID); err != nil { + return err + } + + for _, wasteCategoryName := range article.WasteCategories { + wasteCategoryID, err := u.articleRepo.FindCategoryByName(wasteCategoryName, "waste") + if err != nil { + return err + } + + articleCategory := art.ArticleCategories{ + ArticleID: articleID, + WasteCategoryID: wasteCategoryID, + } + + if err := u.articleRepo.CreateArticleCategory(articleCategory); err != nil { + return err + } + } + + for _, contentCategoryName := range article.ContentCategories { + contentCategoryID, err := u.articleRepo.FindCategoryByName(contentCategoryName, "content") + if err != nil { + return err + } + + articleCategory := art.ArticleCategories{ + ArticleID: articleID, + ContentCategoryID: int(contentCategoryID), + } + + if err := u.articleRepo.CreateArticleCategory(articleCategory); err != nil { + return err + } + } + + return u.articleRepo.Update(articleToUpdate) } func (uc *articleUsecase) Delete(articleID string) error { + _, err := uc.articleRepo.FindByID(articleID) + if err != nil { + return err + } + + _ = uc.articleRepo.DeleteAllSection(articleID) + _ = uc.articleRepo.DeleteAllArticleCategory(articleID) return nil } diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 2704db4..3cf22e5 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -379,9 +379,9 @@ func (m *mysqlDatabase) InitArticle() { } articleCategories := []article.ArticleCategories{ - {ID: 1, ArticleID: "ART0001", WasteCategoryID: 1, ContentCategoryID: 2}, - {ID: 2, ArticleID: "ART0001", WasteCategoryID: 3, ContentCategoryID: 4}, - {ID: 3, ArticleID: "ART0002", WasteCategoryID: 7, ContentCategoryID: 1}, + {ID: 1, ArticleID: "ART0001", WasteCategoryID: 1}, + {ID: 2, ArticleID: "ART0001", ContentCategoryID: 2}, + {ID: 3, ArticleID: "ART0001", ContentCategoryID: 4}, } for _, article := range articles { diff --git a/internal/server/route.go b/internal/server/route.go index ba8554a..f249fd3 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -385,6 +385,15 @@ func (s *echoServer) articleHandler() { usecase := articleUsecase.NewArticleUsecase(repositoryArticle, repositoryAdmin) handler := articleHandler.NewArticleHandler(usecase) + // Get all article + s.gr.GET("/articles", handler.GetAllArticle, AllRoleMiddleware) + + // Get by keyword + s.gr.GET("/article/search", handler.GetArticleByKeyword, AllRoleMiddleware) + + // Get by category + s.gr.GET("/article/category", handler.GetArticleByCategory, AllRoleMiddleware) + // Get article by id s.gr.GET("/article/:articleId", handler.GetArticleByID, AllRoleMiddleware) } diff --git a/pkg/errors.go b/pkg/errors.go index 0ed94e6..9f5a6a4 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -73,7 +73,8 @@ var ( ErrAboutUsCategoryNotFound = errors.New("about us with that category not found") // Article - ErrArticleNotFound = errors.New("article not found") + ErrArticleNotFound = errors.New("article not found") + ErrCategoryArticleNotFound = errors.New("invalid category type") // Error file ErrFileTooLarge = errors.New("upload image size must less than 2MB") From 6b2e73952dc6d537bb016e69de06600af61b6a9b Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Wed, 12 Jun 2024 01:11:43 +0700 Subject: [PATCH 19/72] feat(article): update article: crud for manage article --- internal/article/entity.go | 9 +- internal/article/handler/handler.go | 73 +++++++++++- internal/article/repository/repository.go | 14 ++- internal/article/sample.json | 137 ++++++++++++++++++++++ internal/article/usecase/usecase.go | 22 +++- internal/database/mysql.go | 21 ++-- internal/server/route.go | 9 ++ 7 files changed, 262 insertions(+), 23 deletions(-) create mode 100644 internal/article/sample.json diff --git a/internal/article/entity.go b/internal/article/entity.go index 1bb127f..6bf6858 100644 --- a/internal/article/entity.go +++ b/internal/article/entity.go @@ -67,7 +67,7 @@ type ArticleRepository interface { // Article Repository Create(article Article) (*Article, error) FindByID(articleID string) (*Article, error) - FindAll(page, limit uint) (*[]Article, int64, error) + FindAll(page, limit uint, sortBy string, sortType string) (*[]Article, int64, error) FindLastID() (string, error) FindByKeyword(keyword string) (*[]Article, error) FindByCategory(categoryName string, categoryType string) (*[]Article, error) @@ -93,7 +93,7 @@ type ArticleRepository interface { type ArticleUsecase interface { NewArticle(article ArticleInput, authorId string) (*ArticleDetail, error) GetArticleByID(articleID string) (*ArticleDetail, error) - GetAllArticle(page, limit int) (*ArticleResponsePagination, error) + GetAllArticle(page, limit int, sortBy string, sortType string) (*ArticleResponsePagination, error) GetArticleByKeyword(keyword string) (*[]ArticleDetail, error) GetArticleByCategory(categoryName string, categoryType string) (*[]ArticleDetail, error) Update(articleID string, article ArticleInput) error @@ -101,11 +101,12 @@ type ArticleUsecase interface { GetArticleDetail(article Article) *ArticleDetail GetDetailAuthor(authorID string) (*AdminDetail, error) - - // add usecase yang belum saya buat dibawah } type ArticleHandler interface { + NewArticle(c echo.Context) error + UpdateArticle(c echo.Context) error + DeleteArticle(c echo.Context) error GetAllArticle(c echo.Context) error GetArticleByKeyword(c echo.Context) error GetArticleByCategory(c echo.Context) error diff --git a/internal/article/handler/handler.go b/internal/article/handler/handler.go index ed300c8..c4c7cc8 100644 --- a/internal/article/handler/handler.go +++ b/internal/article/handler/handler.go @@ -19,17 +19,88 @@ func NewArticleHandler(uc art.ArticleUsecase) art.ArticleHandler { return &articleHandler{usecase: uc} } +func (h *articleHandler) NewArticle(c echo.Context) error { + var request art.ArticleInput + + authorID := c.Get("user").(*helper.JwtCustomClaims).UserID + + if err := c.Bind(&request); err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + } + + if err := c.Validate(&request); err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + } + + response, err := h.usecase.NewArticle(request, authorID) + if err != nil { + return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + } + + return helper.ResponseHandler(c, http.StatusCreated, "ok", response) +} + +func (h *articleHandler) UpdateArticle(c echo.Context) error { + var request art.ArticleInput + articleID := c.Param("articleId") + + if err := c.Bind(&request); err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + } + + if err := c.Validate(&request); err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + } + + if err := h.usecase.Update(articleID, request); err != nil { + if errors.Is(pkg.ErrArticleNotFound, err) { + return helper.ErrorHandler(c, http.StatusNotFound, err.Error()) + } + + return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + } + + return helper.ResponseHandler(c, http.StatusOK, "ok", nil) +} + +func (h *articleHandler) DeleteArticle(c echo.Context) error { + articleID := c.Param("articleId") + + if err := h.usecase.Delete(articleID); err != nil { + if errors.Is(pkg.ErrArticleNotFound, err) { + return helper.ErrorHandler(c, http.StatusNotFound, err.Error()) + } + + return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + } + + return helper.ResponseHandler(c, http.StatusOK, "ok", nil) +} + func (h *articleHandler) GetAllArticle(c echo.Context) error { page, _ := strconv.Atoi(c.QueryParam("page")) if page == 0 { page = 1 } + limit, _ := strconv.Atoi(c.QueryParam("limit")) if limit == 0 { limit = 10 } - response, err := h.usecase.GetAllArticle(page, limit) + sortBy := c.QueryParam("sort_by") + sortType := c.QueryParam("sort_type") + + if sortBy == "" { + sortBy = "created_at" + sortType = "asc" + } + + if sortType == "" { + sortType = "asc" + } + + response, err := h.usecase.GetAllArticle(page, limit, sortBy, sortType) if err != nil { helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) } diff --git a/internal/article/repository/repository.go b/internal/article/repository/repository.go index cb64765..a77ee96 100644 --- a/internal/article/repository/repository.go +++ b/internal/article/repository/repository.go @@ -2,6 +2,7 @@ package article import ( "errors" + "fmt" art "github.com/sawalreverr/recything/internal/article" "github.com/sawalreverr/recything/internal/database" @@ -36,15 +37,22 @@ func (r *articleRepository) FindByID(articleID string) (*art.Article, error) { return &article, nil } -func (r *articleRepository) FindAll(page, limit uint) (*[]art.Article, int64, error) { +func (r *articleRepository) FindAll(page, limit uint, sortBy string, sortType string) (*[]art.Article, int64, error) { var articles []art.Article var total int64 db := r.DB.GetDB().Model(&art.Article{}) - db.Count(&total) offset := (page - 1) * limit - if err := r.DB.GetDB().Preload("Categories").Preload("Sections").Limit(int(limit)).Offset(int(offset)).Find(&articles).Error; err != nil { + + if sortBy != "" { + sort := fmt.Sprintf("%s %s", sortBy, sortType) + db = db.Order(sort) + } + + db.Count(&total) + + if err := db.Preload("Categories").Preload("Sections").Limit(int(limit)).Offset(int(offset)).Find(&articles).Error; err != nil { return nil, 0, err } diff --git a/internal/article/sample.json b/internal/article/sample.json new file mode 100644 index 0000000..7cfd47d --- /dev/null +++ b/internal/article/sample.json @@ -0,0 +1,137 @@ +[ + { + "title": "Cara Mendaur Ulang Botol Plastik", + "description": "Panduan langkah demi langkah tentang cara mendaur ulang botol plastik di rumah.", + "thumbnail_url": "https://example.com/daur-ulang-plastik.jpg", + "waste_categories": ["plastik"], + "content_categories": ["tutorial"], + "sections": [ + { + "title": "Membersihkan dan Menyortir", + "description": "Bilas botol, lepaskan label, dan pisahkan berdasarkan jenisnya (PET, HDPE, dll.).", + "image_url": "https://example.com/membersihkan-botol.jpg" + }, + { + "title": "Meremukkan dan Menyimpan", + "description": "Remukkan botol untuk menghemat ruang dan simpan dalam tempat khusus.", + "image_url": "https://example.com/meremukkan-botol.jpg" + }, + { + "title": "Mengantar atau Dijemput", + "description": "Temukan pusat daur ulang terdekat atau jadwalkan layanan penjemputan.", + "image_url": "https://example.com/pusat-daur-ulang.jpg" + } + ] + }, + { + "title": "Bahaya Limbah Elektronik dan Cara Pembuangan yang Bertanggung Jawab", + "description": "Pelajari tentang bahaya lingkungan dan kesehatan dari limbah elektronik dan temukan metode pembuangan yang aman.", + "thumbnail_url": "https://example.com/limbah-elektronik.jpg", + "waste_categories": ["elektronik", "berbahaya"], + "content_categories": ["edukasi", "kampanye"], + "sections": [ + { + "title": "Apa itu Limbah Elektronik?", + "description": "Limbah elektronik mencakup barang elektronik bekas seperti ponsel, komputer, peralatan rumah tangga, dll.", + "image_url": "https://example.com/tumpukan-limbah-elektronik.jpg" + }, + { + "title": "Komponen Beracun", + "description": "Limbah elektronik mengandung zat berbahaya seperti timbal, merkuri, dan kadmium.", + "image_url": "https://example.com/simbol-beracun.jpg" + }, + { + "title": "Pembuangan yang Benar", + "description": "Cari pendaur ulang limbah elektronik bersertifikat atau program pengembalian yang ditawarkan oleh produsen.", + "image_url": "https://example.com/daur-ulang-limbah-elektronik.jpg" + } + ] + }, + { + "title": "5 Tips Mengurangi Sampah Plastik dalam Kehidupan Sehari-hari", + "description": "Lakukan perubahan kecil untuk dampak besar! Temukan cara mudah untuk mengurangi penggunaan plastik sehari-hari.", + "thumbnail_url": "https://example.com/tips-plastik.jpg", + "waste_categories": ["plastik"], + "content_categories": ["tips"], + "sections": [ + { + "title": "Bawa Tas Belanja Sendiri", + "description": "Gunakan tas belanja kain yang dapat digunakan kembali saat berbelanja.", + "image_url": "https://example.com/tas-belanja-kain.jpg" + }, + { + "title": "Hindari Penggunaan Sedotan Plastik", + "description": "Pilih sedotan stainless steel, bambu, atau kertas.", + "image_url": "https://example.com/sedotan-alternatif.jpg" + }, + { + "title": "Bawa Botol Minum Isi Ulang", + "description": "Kurangi sampah botol plastik dengan membawa botol minum sendiri.", + "image_url": "https://example.com/botol-minum.jpg" + }, + { + "title": "Gunakan Wadah Makanan Reusable", + "description": "Bawa bekal makan siang dalam wadah yang dapat digunakan kembali.", + "image_url": "https://example.com/wadah-reusable.jpg" + }, + { + "title": "Pilih Produk dengan Kemasan Ramah Lingkungan", + "description": "Cari produk dengan kemasan minimal atau kemasan yang dapat didaur ulang.", + "image_url": "https://example.com/kemasan-ramah-lingkungan.jpg" + } + ] + }, + { + "title": "Mengenal Lebih Dekat Proses Daur Ulang Kertas", + "description": "Ikuti perjalanan kertas bekas hingga menjadi kertas baru yang siap pakai!", + "thumbnail_url": "https://example.com/daur-ulang-kertas.jpg", + "waste_categories": ["kertas"], + "content_categories": ["daur ulang"], + "sections": [ + { + "title": "Pengumpulan dan Pemilahan", + "description": "Kertas bekas dikumpulkan dan dipilah berdasarkan jenisnya.", + "image_url": "https://example.com/pengumpulan-kertas.jpg" + }, + { + "title": "Penghancuran dan Pembuburan", + "description": "Kertas dihancurkan dan dicampur dengan air untuk membentuk bubur kertas.", + "image_url": "https://example.com/pembuburan-kertas.jpg" + }, + { + "title": "Pembersihan dan Pemutihan", + "description": "Bubur kertas dibersihkan dari tinta dan kotoran, lalu diputihkan.", + "image_url": "https://example.com/pemutihan-kertas.jpg" + }, + { + "title": "Pembentukan dan Pengeringan", + "description": "Bubur kertas dibentuk menjadi lembaran dan dikeringkan.", + "image_url": "https://example.com/pembentukan-kertas.jpg" + } + ] + }, + { + "title": "Kampanye 'Bijak Berplastik' di Sekolah", + "description": "Upaya siswa-siswi untuk mengurangi penggunaan plastik di lingkungan sekolah.", + "thumbnail_url": "https://example.com/kampanye-plastik.jpg", + "waste_categories": ["plastik"], + "content_categories": ["kampanye"], + "sections": [ + { + "title": "Latar Belakang", + "description": "Meningkatnya kesadaran akan bahaya sampah plastik bagi lingkungan.", + "image_url": "https://example.com/sampah-plastik.jpg" + }, + { + "title": "Kegiatan Kampanye", + "description": "Workshop, lomba poster, dan aksi bersih-bersih lingkungan sekolah.", + "image_url": "https://example.com/kegiatan-kampanye.jpg" + }, + { + "title": "Hasil dan Dampak", + "description": "Penurunan signifikan dalam penggunaan plastik di kantin sekolah.", + "image_url": "https://example.com/hasil-kampanye.jpg" + } + ] + } +] diff --git a/internal/article/usecase/usecase.go b/internal/article/usecase/usecase.go index 770f2a3..64b822c 100644 --- a/internal/article/usecase/usecase.go +++ b/internal/article/usecase/usecase.go @@ -76,7 +76,8 @@ func (u *articleUsecase) NewArticle(article art.ArticleInput, authorId string) ( } } - return u.GetArticleDetail(*createdArticle), nil + articleFound, _ := u.articleRepo.FindByID(createdArticle.ID) + return u.GetArticleDetail(*articleFound), nil } func (uc *articleUsecase) GetArticleByID(articleID string) (*art.ArticleDetail, error) { @@ -88,8 +89,8 @@ func (uc *articleUsecase) GetArticleByID(articleID string) (*art.ArticleDetail, return uc.GetArticleDetail(*articleFound), nil } -func (u *articleUsecase) GetAllArticle(page, limit int) (*art.ArticleResponsePagination, error) { - articles, total, err := u.articleRepo.FindAll(uint(page), uint(limit)) +func (u *articleUsecase) GetAllArticle(page, limit int, sortBy string, sortType string) (*art.ArticleResponsePagination, error) { + articles, total, err := u.articleRepo.FindAll(uint(page), uint(limit), sortBy, sortType) if err != nil { return nil, err } @@ -200,13 +201,22 @@ func (u *articleUsecase) Update(articleID string, article art.ArticleInput) erro } func (uc *articleUsecase) Delete(articleID string) error { - _, err := uc.articleRepo.FindByID(articleID) + articleFound, err := uc.articleRepo.FindByID(articleID) if err != nil { return err } - _ = uc.articleRepo.DeleteAllSection(articleID) - _ = uc.articleRepo.DeleteAllArticleCategory(articleID) + if err := uc.articleRepo.Delete(articleFound.ID); err != nil { + return err + } + + if err := uc.articleRepo.DeleteAllSection(articleFound.ID); err != nil { + return err + } + + if err := uc.articleRepo.DeleteAllArticleCategory(articleFound.ID); err != nil { + return err + } return nil } diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 3cf22e5..073fb14 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -367,21 +367,24 @@ func (m *mysqlDatabase) InitArticleCategory() { func (m *mysqlDatabase) InitArticle() { articles := []article.Article{ - {ID: "ART0001", Title: "The Future of AI", Description: "An in-depth look at the future of artificial intelligence.", ThumbnailURL: "http://example.com/ai.jpg", AuthorID: "AD0001"}, - {ID: "ART0002", Title: "Healthy Living Tips", Description: "Tips for a healthier lifestyle.", ThumbnailURL: "http://example.com/health.jpg", AuthorID: "AD0001"}, + {ID: "ART0001", Title: "Cara Mendaur Ulang Botol Plastik", Description: "Panduan langkah demi langkah tentang cara mendaur ulang botol plastik di rumah.", ThumbnailURL: "https://example.com/daur-ulang-plastik.jpg", AuthorID: "AD0001"}, + {ID: "ART0002", Title: "Bahaya Limbah Elektronik dan Cara Pembuangan yang Bertanggung Jawab", Description: "Pelajari tentang bahaya lingkungan dan kesehatan dari limbah elektronik dan temukan metode pembuangan yang aman.", ThumbnailURL: "https://example.com/limbah-elektronik.jpg", AuthorID: "AD0001"}, } articleSections := []article.ArticleSection{ - {ID: 1, ArticleID: "ART0001", Title: "Introduction", Description: "Introduction to AI", ImageURL: "http://example.com/ai.jpg"}, - {ID: 2, ArticleID: "ART0001", Title: "Impact on Society", Description: "How AI will impact society.", ImageURL: "http://example.com/ai.jpg"}, - {ID: 3, ArticleID: "ART0002", Title: "Diet", Description: "Healthy eating habits.", ImageURL: "http://example.com/diet.jpg"}, - {ID: 4, ArticleID: "ART0002", Title: "Exercise", Description: "The importance of regular exercise.", ImageURL: "http://example.com/ai.jpg"}, + {ID: 1, ArticleID: "ART0001", Title: "Membersihkan dan Menyortir", Description: "Bilas botol, lepaskan label, dan pisahkan berdasarkan jenisnya (PET, HDPE, dll.).", ImageURL: "https://example.com/membersihkan-botol.jpg"}, + {ID: 2, ArticleID: "ART0001", Title: "Meremukkan dan Menyimpan", Description: "Remukkan botol untuk menghemat ruang dan simpan dalam tempat khusus.", ImageURL: "https://example.com/meremukkan-botol.jpg"}, + {ID: 3, ArticleID: "ART0001", Title: "Mengantar atau Dijemput", Description: "Temukan pusat daur ulang terdekat atau jadwalkan layanan penjemputan.", ImageURL: "https://example.com/pusat-daur-ulang.jpg"}, + + {ID: 4, ArticleID: "ART0002", Title: "Apa itu Limbah Elektronik?", Description: "Limbah elektronik mencakup barang elektronik bekas seperti ponsel, komputer, peralatan rumah tangga, dll.", ImageURL: "https://example.com/tumpukan-limbah-elektronik.jpg"}, + {ID: 5, ArticleID: "ART0002", Title: "Komponen Beracun", Description: "Limbah elektronik mengandung zat berbahaya seperti timbal, merkuri, dan kadmium.", ImageURL: "https://example.com/simbol-beracun.jpg"}, + {ID: 6, ArticleID: "ART0002", Title: "Pembuangan yang Benar", Description: "Cari pendaur ulang limbah elektronik bersertifikat atau program pengembalian yang ditawarkan oleh produsen.", ImageURL: "https://example.com/daur-ulang-limbah-elektronik.jpg"}, } articleCategories := []article.ArticleCategories{ - {ID: 1, ArticleID: "ART0001", WasteCategoryID: 1}, - {ID: 2, ArticleID: "ART0001", ContentCategoryID: 2}, - {ID: 3, ArticleID: "ART0001", ContentCategoryID: 4}, + {ID: 1, ArticleID: "ART0001", WasteCategoryID: 1, ContentCategoryID: 3}, + {ID: 2, ArticleID: "ART0002", WasteCategoryID: 9, ContentCategoryID: 4}, + {ID: 3, ArticleID: "ART0002", WasteCategoryID: 13, ContentCategoryID: 5}, } for _, article := range articles { diff --git a/internal/server/route.go b/internal/server/route.go index f249fd3..99380ab 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -396,4 +396,13 @@ func (s *echoServer) articleHandler() { // Get article by id s.gr.GET("/article/:articleId", handler.GetArticleByID, AllRoleMiddleware) + + // Create new article by admin + s.gr.POST("/article", handler.NewArticle, SuperAdminOrAdminMiddleware) + + // Update article by admin + s.gr.PUT("/article/:articleId", handler.UpdateArticle, SuperAdminOrAdminMiddleware) + + // Delete article by admin + s.gr.DELETE("/article/:articleId", handler.DeleteArticle, SuperAdminOrAdminMiddleware) } From ed0cf38ab98ae0661ca605c5af066c7f81a6315d Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Mon, 10 Jun 2024 19:57:30 +0700 Subject: [PATCH 20/72] chore(report): update report response --- internal/report/dto.go | 32 +++++++++++++++---------- internal/report/usecase/usecase.go | 33 ++++++++++++++++++++++---- internal/server/route.go | 5 ++-- internal/user/repository/repository.go | 2 +- 4 files changed, 51 insertions(+), 21 deletions(-) diff --git a/internal/report/dto.go b/internal/report/dto.go index 2697e91..e36145e 100644 --- a/internal/report/dto.go +++ b/internal/report/dto.go @@ -24,20 +24,26 @@ type UpdateStatus struct { Reason string `json:"reason"` } +type UserDetail struct { + ID string `json:"id"` + Name string `json:"name"` + ImageURL string `json:"image_url"` +} + type ReportDetail struct { - ID string `json:"id"` - AuthorID string `json:"author_id"` - ReportType string `json:"report_type"` - Title string `json:"title"` - Description string `json:"description"` - WasteType string `json:"waste_type"` - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` - Address string `json:"address"` - City string `json:"city"` - Province string `json:"province"` - Status string `json:"status"` - Reason string `json:"reason"` + ID string `json:"id"` + Author UserDetail `json:"author"` + ReportType string `json:"report_type"` + Title string `json:"title"` + Description string `json:"description"` + WasteType string `json:"waste_type"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Address string `json:"address"` + City string `json:"city"` + Province string `json:"province"` + Status string `json:"status"` + Reason string `json:"reason"` WasteMaterials []WasteMaterial `json:"waste_materials"` ReportImages []string `json:"report_images"` diff --git a/internal/report/usecase/usecase.go b/internal/report/usecase/usecase.go index 2930fff..d221d94 100644 --- a/internal/report/usecase/usecase.go +++ b/internal/report/usecase/usecase.go @@ -6,15 +6,17 @@ import ( "github.com/google/uuid" "github.com/sawalreverr/recything/internal/helper" rpt "github.com/sawalreverr/recything/internal/report" + user "github.com/sawalreverr/recything/internal/user" "github.com/sawalreverr/recything/pkg" ) type reportUsecase struct { reportRepository rpt.ReportRepository + userRepository user.UserRepository } -func NewReportUsecase(repo rpt.ReportRepository) rpt.ReportUsecase { - return &reportUsecase{reportRepository: repo} +func NewReportUsecase(reportRepo rpt.ReportRepository, userRepo user.UserRepository) rpt.ReportUsecase { + return &reportUsecase{reportRepository: reportRepo, userRepository: userRepo} } func (uc *reportUsecase) CreateReport(report rpt.ReportInput, authorID string, imageURLs []string) (*rpt.ReportDetail, error) { @@ -83,9 +85,16 @@ func (uc *reportUsecase) CreateReport(report rpt.ReportInput, authorID string, i return nil, pkg.ErrStatusInternalError } + userFound, _ := uc.userRepository.FindByID(authorID) + author := rpt.UserDetail{ + ID: userFound.ID, + Name: userFound.Name, + ImageURL: userFound.PictureURL, + } + reportDetail := rpt.ReportDetail{ ID: createdReport.ID, - AuthorID: createdReport.AuthorID, + Author: author, ReportType: createdReport.ReportType, Title: createdReport.Title, Description: createdReport.Description, @@ -123,9 +132,16 @@ func (uc *reportUsecase) FindHistoryUserReports(authorID string) (*[]rpt.ReportD return nil, pkg.ErrStatusInternalError } + userFound, _ := uc.userRepository.FindByID(authorID) + author := rpt.UserDetail{ + ID: userFound.ID, + Name: userFound.Name, + ImageURL: userFound.PictureURL, + } + reportDetail := rpt.ReportDetail{ ID: report.ID, - AuthorID: report.AuthorID, + Author: author, ReportType: report.ReportType, Title: report.Title, Description: report.Description, @@ -185,9 +201,16 @@ func (uc *reportUsecase) FindAllReports(page, limit int, reportType, status stri return nil, 0, pkg.ErrStatusInternalError } + userFound, _ := uc.userRepository.FindByID(report.AuthorID) + author := rpt.UserDetail{ + ID: userFound.ID, + Name: userFound.Name, + ImageURL: userFound.PictureURL, + } + reportDetail := rpt.ReportDetail{ ID: report.ID, - AuthorID: report.AuthorID, + Author: author, ReportType: report.ReportType, Title: report.Title, Description: report.Description, diff --git a/internal/server/route.go b/internal/server/route.go index fd8800d..b6a4552 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -150,8 +150,9 @@ func (s *echoServer) supAdminHttpHandler() { } func (s *echoServer) reportHttpHandler() { - repository := reportRepo.NewReportRepository(s.db) - usecase := reportUsecase.NewReportUsecase(repository) + reportRepository := reportRepo.NewReportRepository(s.db) + userRepository := userRepo.NewUserRepository(s.db) + usecase := reportUsecase.NewReportUsecase(reportRepository, userRepository) handler := reportHandler.NewReportHandler(usecase) // User create new report diff --git a/internal/user/repository/repository.go b/internal/user/repository/repository.go index 17f908b..ab3701a 100644 --- a/internal/user/repository/repository.go +++ b/internal/user/repository/repository.go @@ -43,7 +43,7 @@ func (r *userRepository) FindByPhoneNumber(phoneNumber string) (*u.User, error) func (r *userRepository) FindByID(userID string) (*u.User, error) { var user u.User - if err := r.DB.GetDB().Where("id = ?", userID).First(&user).Error; err != nil { + if err := r.DB.GetDB().Unscoped().Where("id = ?", userID).First(&user).Error; err != nil { return nil, err } From f515fe3a33946963299e5442bde2f794c3dec3e1 Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Tue, 11 Jun 2024 00:55:24 +0700 Subject: [PATCH 21/72] feat(article): add some repository, usecase, handler for article --- cmd/api/main.go | 6 ++ internal/article/dto.go | 44 ++++++++ internal/article/entity.go | 88 ++++++++++++++++ internal/article/handler/handler.go | 33 ++++++ internal/article/repository/repository.go | 116 ++++++++++++++++++++++ internal/article/usecase/usecase.go | 89 +++++++++++++++++ internal/database/database.go | 2 + internal/database/migrate.go | 5 + internal/database/mysql.go | 58 +++++++++++ internal/server/echo_server.go | 3 + internal/server/route.go | 13 +++ pkg/errors.go | 3 + 12 files changed, 460 insertions(+) create mode 100644 internal/article/dto.go create mode 100644 internal/article/entity.go create mode 100644 internal/article/handler/handler.go create mode 100644 internal/article/repository/repository.go create mode 100644 internal/article/usecase/usecase.go diff --git a/cmd/api/main.go b/cmd/api/main.go index 8a60d73..f5edf7e 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -45,6 +45,12 @@ func main() { // Init Videos db.InitDataVideos() + // Init Article Categories + db.InitArticleCategory() + + // Init Article + db.InitArticle() + app := server.NewEchoServer(conf, db) c := cron.New() diff --git a/internal/article/dto.go b/internal/article/dto.go new file mode 100644 index 0000000..7e06079 --- /dev/null +++ b/internal/article/dto.go @@ -0,0 +1,44 @@ +package article + +import ( + "time" +) + +type ArticleInput struct { + Title string `json:"title"` + Description string `json:"description"` + ThumbnailURL string `json:"thumbnail_url"` + Categories []string `json:"categories"` + Sections []ArticleSectionInput `json:"sections"` +} + +type ArticleSectionInput struct { + Title string `json:"title"` + Description string `json:"description"` + ImageURL string `json:"image_url"` +} + +type AdminDetail struct { + ID string `json:"id"` + Name string `json:"name"` + ImageURL string `json:"image_url"` +} + +type ArticleDetail struct { + ID string `json:"id"` + AuthorID AdminDetail `json:"author"` + Title string `json:"title"` + Description string `json:"description"` + ThumbnailURL string `json:"thumbnail_url"` + CreatedAt time.Time `json:"created_at"` + + Categories []WasteCategory `json:"categories"` + Sections []ArticleSection `json:"sections"` +} + +type ArticleResponsePagination struct { + Total int64 `json:"total"` + Page int `json:"page"` + Limit int `json:"limit"` + Articles []ArticleDetail `json:"articles"` +} diff --git a/internal/article/entity.go b/internal/article/entity.go new file mode 100644 index 0000000..31e2d48 --- /dev/null +++ b/internal/article/entity.go @@ -0,0 +1,88 @@ +package article + +import ( + "time" + + "github.com/labstack/echo/v4" + "gorm.io/gorm" +) + +type Article struct { + ID string `gorm:"primaryKey;type:varchar(7)"` + Title string `gorm:"type:varchar(255)"` + Description string `gorm:"type:text"` + ThumbnailURL string `gorm:"type:varchar(255)"` + AuthorID string + + Categories []ArticleCategories + Sections []ArticleSection + + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +} + +type WasteCategory struct { + ID uint `json:"id" gorm:"primaryKey"` + Name string `json:"name" gorm:"type:varchar(50);unique;not null"` + + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +} + +type ArticleCategories struct { + ID uint `gorm:"primaryKey"` + ArticleID string `gorm:"type:varchar(7)"` + CategoryID uint + + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +} + +type ArticleSection struct { + ID uint `json:"-" gorm:"primaryKey"` + ArticleID string `json:"-" gorm:"type:varchar(7)"` + Title string `json:"title" gorm:"type:varchar(255)"` + Description string `json:"description" gorm:"type:text"` + ImageURL string `json:"image_url" gorm:"type:varchar(255)"` + + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +} + +type ArticleRepository interface { + // Article Repository + Create(article Article) (*Article, error) + FindByID(articleID string) (*Article, error) + FindAll(page, limit uint) (*[]Article, error) + FindLastID() (string, error) + FindByKeyword(keyword string) (*[]Article, error) + FindByCategory(categoryName string) (*[]Article, error) + Update(article Article) error + Delete(articleID string) error + + // Category Repository + FindCategories(articleID string) (*[]WasteCategory, error) + + // Article Section Repository +} + +type ArticleUsecase interface { + NewArticle(article ArticleInput) (*ArticleDetail, error) + GetArticleByID(articleID string) (*ArticleDetail, error) + GetAllArticle(page, limit uint) (*ArticleResponsePagination, error) + GetArticleByKeyword(keyword string) (*[]ArticleDetail, error) + GetArticleByCategory(categoryName string) (*[]ArticleDetail, error) + Update(articleID string, article ArticleInput) error + Delete(articleID string) error + + GetArticleDetail(article Article) *ArticleDetail + GetDetailAuthor(authorID string) (*AdminDetail, error) +} + +type ArticleHandler interface { + GetArticleByID(c echo.Context) error +} diff --git a/internal/article/handler/handler.go b/internal/article/handler/handler.go new file mode 100644 index 0000000..9d71d2a --- /dev/null +++ b/internal/article/handler/handler.go @@ -0,0 +1,33 @@ +package article + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v4" + art "github.com/sawalreverr/recything/internal/article" + "github.com/sawalreverr/recything/internal/helper" + "github.com/sawalreverr/recything/pkg" +) + +type articleHandler struct { + usecase art.ArticleUsecase +} + +func NewArticleHandler(uc art.ArticleUsecase) art.ArticleHandler { + return &articleHandler{usecase: uc} +} + +func (h *articleHandler) GetArticleByID(c echo.Context) error { + articleId := c.Param("articleId") + + articleFound, err := h.usecase.GetArticleByID(articleId) + if err != nil { + if errors.Is(pkg.ErrArticleNotFound, err) { + return helper.ErrorHandler(c, http.StatusNotFound, err.Error()) + } + return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + } + + return helper.ResponseHandler(c, http.StatusOK, "ok", articleFound) +} diff --git a/internal/article/repository/repository.go b/internal/article/repository/repository.go new file mode 100644 index 0000000..4c3773f --- /dev/null +++ b/internal/article/repository/repository.go @@ -0,0 +1,116 @@ +package article + +import ( + "errors" + + art "github.com/sawalreverr/recything/internal/article" + "github.com/sawalreverr/recything/internal/database" + "github.com/sawalreverr/recything/pkg" + "gorm.io/gorm" +) + +type articleRepository struct { + DB database.Database +} + +func NewArticleRepository(db database.Database) art.ArticleRepository { + return &articleRepository{DB: db} +} + +func (r *articleRepository) Create(article art.Article) (*art.Article, error) { + if err := r.DB.GetDB().Create(&article).Error; err != nil { + return nil, err + } + + return &article, nil +} + +func (r *articleRepository) FindByID(articleID string) (*art.Article, error) { + var article art.Article + if err := r.DB.GetDB().Preload("Categories").Preload("Sections").First(&article, "id = ?", articleID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, pkg.ErrArticleNotFound + } + return nil, err + } + return &article, nil +} + +func (r *articleRepository) FindAll(page, limit uint) (*[]art.Article, error) { + var articles []art.Article + offset := (page - 1) * limit + if err := r.DB.GetDB().Preload("Categories").Preload("Sections").Limit(int(limit)).Offset(int(offset)).Find(&articles).Error; err != nil { + return nil, err + } + return &articles, nil +} + +func (r *articleRepository) FindLastID() (string, error) { + var article art.Article + if err := r.DB.GetDB().Unscoped().Order("id DESC").First(&article).Error; err != nil { + return "ART0000", err + } + + return article.ID, nil +} + +func (r *articleRepository) FindByKeyword(keyword string) (*[]art.Article, error) { + var articles []art.Article + query := "%" + keyword + "%" + if err := r.DB.GetDB().Preload("Categories").Preload("Sections"). + Where("title LIKE ? OR description LIKE ?", query, query). + Find(&articles).Error; err != nil { + return nil, err + } + return &articles, nil +} + +func (r *articleRepository) FindByCategory(categoryName string) (*[]art.Article, error) { + var articles []art.Article + if err := r.DB.GetDB().Preload("Categories").Preload("Sections"). + Joins("JOIN article_categories ON articles.id = article_categories.article_id"). + Joins("JOIN categories ON article_categories.category_id = categories.id"). + Where("categories.name = ?", categoryName). + Find(&articles).Error; err != nil { + return nil, err + } + return &articles, nil +} + +func (r *articleRepository) Update(article art.Article) error { + if err := r.DB.GetDB().Save(&article).Error; err != nil { + return err + } + return nil +} + +func (r *articleRepository) Delete(articleID string) error { + var article art.Article + if err := r.DB.GetDB().Delete(&article, "id = ?", articleID).Error; err != nil { + return err + } + return nil +} + +func (r *articleRepository) FindCategories(articleID string) (*[]art.WasteCategory, error) { + var articleCategories []art.ArticleCategories + var categories []art.WasteCategory + + if err := r.DB.GetDB().Where("article_id = ?", articleID).Find(&articleCategories).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, pkg.ErrArticleNotFound + } + return nil, err + } + + var categoryIDs []uint + for _, ac := range articleCategories { + categoryIDs = append(categoryIDs, ac.CategoryID) + } + + if err := r.DB.GetDB().Where("id IN (?)", categoryIDs).Find(&categories).Error; err != nil { + return nil, err + } + + return &categories, nil +} diff --git a/internal/article/usecase/usecase.go b/internal/article/usecase/usecase.go new file mode 100644 index 0000000..f262842 --- /dev/null +++ b/internal/article/usecase/usecase.go @@ -0,0 +1,89 @@ +package article + +import ( + admin "github.com/sawalreverr/recything/internal/admin/repository" + art "github.com/sawalreverr/recything/internal/article" +) + +type articleUsecase struct { + articleRepo art.ArticleRepository + adminRepo admin.AdminRepository +} + +func NewArticleUsecase(articleRepo art.ArticleRepository, adminRepo admin.AdminRepository) art.ArticleUsecase { + return &articleUsecase{articleRepo: articleRepo, adminRepo: adminRepo} +} + +func (uc *articleUsecase) NewArticle(article art.ArticleInput) (*art.ArticleDetail, error) { + + return nil, nil +} + +func (uc *articleUsecase) GetArticleByID(articleID string) (*art.ArticleDetail, error) { + articleFound, err := uc.articleRepo.FindByID(articleID) + if err != nil { + return nil, err + } + + articleDetail := uc.GetArticleDetail(*articleFound) + + return articleDetail, nil +} + +func (uc *articleUsecase) GetAllArticle(page, limit uint) (*art.ArticleResponsePagination, error) { + + return nil, nil +} + +func (uc *articleUsecase) GetArticleByKeyword(keyword string) (*[]art.ArticleDetail, error) { + + return nil, nil +} + +func (uc *articleUsecase) GetArticleByCategory(categoryName string) (*[]art.ArticleDetail, error) { + + return nil, nil +} + +func (uc *articleUsecase) Update(articleID string, article art.ArticleInput) error { + + return nil +} + +func (uc *articleUsecase) Delete(articleID string) error { + + return nil +} + +func (uc *articleUsecase) GetArticleDetail(article art.Article) *art.ArticleDetail { + adminDetail, _ := uc.GetDetailAuthor(article.AuthorID) + categories, _ := uc.articleRepo.FindCategories(article.ID) + + articleDetail := art.ArticleDetail{ + ID: article.ID, + AuthorID: *adminDetail, + Title: article.Title, + Description: article.Description, + ThumbnailURL: article.ThumbnailURL, + CreatedAt: article.CreatedAt, + Categories: *categories, + Sections: article.Sections, + } + + return &articleDetail +} + +func (uc *articleUsecase) GetDetailAuthor(authorID string) (*art.AdminDetail, error) { + adminFound, err := uc.adminRepo.FindAdminByID(authorID) + if err != nil { + return nil, err + } + + adminDetail := art.AdminDetail{ + ID: adminFound.ID, + Name: adminFound.Name, + ImageURL: adminFound.ImageUrl, + } + + return &adminDetail, nil +} diff --git a/internal/database/database.go b/internal/database/database.go index c593a19..10d2058 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -14,4 +14,6 @@ type Database interface { InitVideoCategories() InitAboutUs() InitDataVideos() + InitArticleCategory() + InitArticle() } diff --git a/internal/database/migrate.go b/internal/database/migrate.go index 91dcbaa..fa598a4 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -6,6 +6,7 @@ import ( aboutus "github.com/sawalreverr/recything/internal/about-us" achievement "github.com/sawalreverr/recything/internal/achievements/manage_achievements/entity" "github.com/sawalreverr/recything/internal/admin/entity" + "github.com/sawalreverr/recything/internal/article" customdata "github.com/sawalreverr/recything/internal/custom-data" "github.com/sawalreverr/recything/internal/faq" "github.com/sawalreverr/recything/internal/report" @@ -35,6 +36,10 @@ func AutoMigrate(db Database) { &video.Comment{}, &aboutus.AboutUs{}, &aboutus.AboutUsImage{}, + &article.WasteCategory{}, + &article.Article{}, + &article.ArticleSection{}, + &article.ArticleCategories{}, ); err != nil { log.Fatal("Database Migration Failed!") } diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 3c61d6f..670e6d1 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -9,6 +9,7 @@ import ( aboutus "github.com/sawalreverr/recything/internal/about-us" achievement "github.com/sawalreverr/recything/internal/achievements/manage_achievements/entity" adminEntity "github.com/sawalreverr/recything/internal/admin/entity" + "github.com/sawalreverr/recything/internal/article" customDataEntity "github.com/sawalreverr/recything/internal/custom-data" faqEntity "github.com/sawalreverr/recything/internal/faq" "github.com/sawalreverr/recything/internal/helper" @@ -342,6 +343,63 @@ func (m *mysqlDatabase) InitDataVideos() { log.Println("Video data added!") } +func (m *mysqlDatabase) InitArticleCategory() { + categories := []article.WasteCategory{ + {ID: 1, Name: "plastik"}, + {ID: 2, Name: "besi"}, + {ID: 3, Name: "kaca"}, + {ID: 4, Name: "organik"}, + {ID: 5, Name: "kayu"}, + {ID: 6, Name: "kertas"}, + {ID: 7, Name: "baterai"}, + {ID: 8, Name: "kaleng"}, + {ID: 9, Name: "elektronik"}, + {ID: 10, Name: "tekstil"}, + {ID: 11, Name: "minyak"}, + {ID: 12, Name: "bola lampu"}, + {ID: 13, Name: "berbahaya"}, + } + + for _, category := range categories { + m.GetDB().FirstOrCreate(&category, category) + } + log.Println("Article categories data added!") +} + +func (m *mysqlDatabase) InitArticle() { + articles := []article.Article{ + {ID: "ART0001", Title: "The Future of AI", Description: "An in-depth look at the future of artificial intelligence.", ThumbnailURL: "http://example.com/ai.jpg", AuthorID: "AD0001"}, + {ID: "ART0002", Title: "Healthy Living Tips", Description: "Tips for a healthier lifestyle.", ThumbnailURL: "http://example.com/health.jpg", AuthorID: "AD0001"}, + } + + articleSections := []article.ArticleSection{ + {ID: 1, ArticleID: "ART0001", Title: "Introduction", Description: "Introduction to AI", ImageURL: "http://example.com/ai.jpg"}, + {ID: 2, ArticleID: "ART0001", Title: "Impact on Society", Description: "How AI will impact society.", ImageURL: "http://example.com/ai.jpg"}, + {ID: 3, ArticleID: "ART0002", Title: "Diet", Description: "Healthy eating habits.", ImageURL: "http://example.com/diet.jpg"}, + {ID: 4, ArticleID: "ART0002", Title: "Exercise", Description: "The importance of regular exercise.", ImageURL: "http://example.com/ai.jpg"}, + } + + articleCategories := []article.ArticleCategories{ + {ID: 1, ArticleID: "ART0001", CategoryID: 1}, + {ID: 2, ArticleID: "ART0001", CategoryID: 3}, + {ID: 3, ArticleID: "ART0002", CategoryID: 7}, + } + + for _, article := range articles { + m.GetDB().FirstOrCreate(&article, article) + } + + for _, articleSection := range articleSections { + m.GetDB().FirstOrCreate(&articleSection, articleSection) + } + + for _, articleCategory := range articleCategories { + m.GetDB().FirstOrCreate(&articleCategory, articleCategory) + } + + log.Println("Article data added!") +} + func (m *mysqlDatabase) GetDB() *gorm.DB { return dbInstance.DB } diff --git a/internal/server/echo_server.go b/internal/server/echo_server.go index e5a899f..2c28281 100644 --- a/internal/server/echo_server.go +++ b/internal/server/echo_server.go @@ -97,6 +97,9 @@ func (s *echoServer) Start() { // leaderboard handler s.leaderboardHandler() + // Article Handler + s.articleHandler() + serverPORT := fmt.Sprintf(":%d", s.conf.Server.Port) s.app.Logger.Fatal(s.app.Start(serverPORT)) } diff --git a/internal/server/route.go b/internal/server/route.go index b6a4552..b2487c3 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -16,6 +16,9 @@ import ( "github.com/sawalreverr/recything/internal/admin/handler" "github.com/sawalreverr/recything/internal/admin/repository" "github.com/sawalreverr/recything/internal/admin/usecase" + articleHandler "github.com/sawalreverr/recything/internal/article/handler" + articleRepository "github.com/sawalreverr/recything/internal/article/repository" + articleUsecase "github.com/sawalreverr/recything/internal/article/usecase" authHandler "github.com/sawalreverr/recything/internal/auth/handler" authUsecase "github.com/sawalreverr/recything/internal/auth/usecase" customDataHandler "github.com/sawalreverr/recything/internal/custom-data/handler" @@ -375,3 +378,13 @@ func (s *echoServer) leaderboardHandler() { // Get leaderboard s.gr.GET("/leaderboard", handler.GetLeaderboardHandler, AllRoleMiddleware) } + +func (s *echoServer) articleHandler() { + repositoryArticle := articleRepository.NewArticleRepository(s.db) + repositoryAdmin := repository.NewAdminRepository(s.db) + usecase := articleUsecase.NewArticleUsecase(repositoryArticle, repositoryAdmin) + handler := articleHandler.NewArticleHandler(usecase) + + // Get article by id + s.gr.GET("/article/:articleId", handler.GetArticleByID, AllRoleMiddleware) +} diff --git a/pkg/errors.go b/pkg/errors.go index 408ecf8..5efbf77 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -71,4 +71,7 @@ var ( // About Us ErrAboutUsCategoryNotFound = errors.New("about us with that category not found") + + // Article + ErrArticleNotFound = errors.New("article not found") ) From c2004eb39474b95e8e1494e41aa03cfb6cae90f0 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 01:22:46 +0700 Subject: [PATCH 22/72] change method for update data achievemnts from put to ptach --- .../dto/manage_achievement_request.go | 4 +-- .../manage_achievement_handler_impl.go | 9 ++--- .../manage_achievement_usecase_impl.go | 35 ++++++++++++------- internal/server/route.go | 2 +- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/internal/achievements/manage_achievements/dto/manage_achievement_request.go b/internal/achievements/manage_achievements/dto/manage_achievement_request.go index b125a2d..8e7bf4f 100644 --- a/internal/achievements/manage_achievements/dto/manage_achievement_request.go +++ b/internal/achievements/manage_achievements/dto/manage_achievement_request.go @@ -3,7 +3,7 @@ package dto import "mime/multipart" type UpdateAchievementRequest struct { - Level string `json:"level" validate:"required"` - TargetPoint int `json:"target_point" validate:"required"` + Level string `json:"level"` + TargetPoint int `json:"target_point"` Badge *multipart.FileHeader `json:"-"` } diff --git a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go index 2a8882a..6345c69 100644 --- a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go +++ b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go @@ -3,6 +3,7 @@ package handler import ( "encoding/json" "errors" + "mime/multipart" "net/http" "strconv" @@ -84,15 +85,15 @@ func (handler ManageAchievementHandlerImpl) UpdateAchievementHandler(c echo.Cont if errForm != nil { return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) } - badge := form.File["badge"] + var badge []*multipart.FileHeader + if form != nil { + badge = form.File["badge"] + } err := handler.usecae.UpdateAchievementUsecase(&request, badge, achievementIdInt) if err != nil { if errors.Is(err, pkg.ErrAchievementNotFound) { return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrAchievementNotFound.Error()) } - if errors.Is(err, pkg.ErrBadge) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrBadge.Error()) - } if errors.Is(err, pkg.ErrBadgeMaximum) { return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrBadgeMaximum.Error()) } diff --git a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go index 0c89d24..f3c72d0 100644 --- a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go +++ b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go @@ -37,28 +37,37 @@ func (repository ManageAchievementUsecaseImpl) GetAchievementByIdUsecase(id int) } func (repository ManageAchievementUsecaseImpl) UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge []*multipart.FileHeader, id int) error { - if len(badge) == 0 { - return pkg.ErrBadge - } if len(badge) > 1 { return pkg.ErrBadgeMaximum } - validImages, errImages := helper.ImagesValidation(badge) - if errImages != nil { - return errImages - } - urlBadge, errUpload := helper.UploadToCloudinary(validImages[0], "achievements_badge") - if errUpload != nil { - return pkg.ErrUploadCloudinary + var urlBadge string + if len(badge) == 1 { + validImages, errImages := helper.ImagesValidation(badge) + if errImages != nil { + return errImages + } + urlBadgeUpload, errUpload := helper.UploadToCloudinary(validImages[0], "achievements_badge") + if errUpload != nil { + return pkg.ErrUploadCloudinary + } + urlBadge = urlBadgeUpload } achievement, err := repository.repository.GetAchievementById(id) if err != nil { return pkg.ErrAchievementNotFound } - achievement.Level = strings.ToLower(request.Level) - achievement.TargetPoint = request.TargetPoint - achievement.BadgeUrl = urlBadge + + if request.Level != "" { + achievement.Level = strings.ToLower(request.Level) + } + if request.TargetPoint != 0 { + achievement.TargetPoint = request.TargetPoint + } + if urlBadge != "" { + achievement.BadgeUrl = urlBadge + } + if err := repository.repository.UpdateAchievement(achievement, id); err != nil { return err } diff --git a/internal/server/route.go b/internal/server/route.go index b2487c3..c2d10bb 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -271,7 +271,7 @@ func (s *echoServer) manageAchievement() { s.gr.GET("/achievements/:achievementId", handler.GetAchievementByIdHandler, SuperAdminOrAdminMiddleware) // update achievement - s.gr.PUT("/achievements/:achievementId", handler.UpdateAchievementHandler, SuperAdminOrAdminMiddleware) + s.gr.PATCH("/achievements/:achievementId", handler.UpdateAchievementHandler, SuperAdminOrAdminMiddleware) // delete achievement s.gr.DELETE("/achievements/:achievementId", handler.DeleteAchievementHandler, SuperAdminOrAdminMiddleware) From 2742dc7350f43afea90ce4cf61dbe4a5a75b2d03 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 12:07:19 +0700 Subject: [PATCH 23/72] docs: channge method for update data schievement from put to patch --- docs/swagger.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index da6e92b..0e2f690 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -1154,11 +1154,11 @@ paths: type: string example: achievement not found - put: + patch: tags: - manage achievements - summary: Update target point of an achievement - description: Endpoint admin to update the target point of an existing achievement/badge. + summary: Update data achievement + description: Endpoint admin for update data achievement. operationId: updateAchievement parameters: - name: achievementId @@ -1171,7 +1171,7 @@ paths: security: - Bearer: [] requestBody: - required: true + required: false content: multipart/form-data:: schema: From faa5402f04fc81c2acbd963e500be873d1cf0b04 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Jun 2024 00:31:20 +0700 Subject: [PATCH 24/72] refactor(manage achievement): change request body and some code on handler and usecase --- docs/swagger.yml | 19 +++++------ .../dto/manage_achievement_request.go | 7 ++-- .../manage_achievement_handler_impl.go | 30 +++++----------- .../usecase/manage_achievement_usecase.go | 2 +- .../manage_achievement_usecase_impl.go | 34 +++++++++++-------- pkg/errors.go | 5 +++ 6 files changed, 44 insertions(+), 53 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 0e2f690..685b1bf 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -1177,17 +1177,14 @@ paths: schema: type: object properties: - json_data: - type: object - properties: - level: - type: string - description: New level for the achievement - example: platinum - target_point: - type: integer - description: New target point for the badge - example: 1200 + level: + type: string + description: New level for the achievement + example: platinum + target_point: + type: integer + description: New target point for the badge + example: 1200 badge: type: string format: binary diff --git a/internal/achievements/manage_achievements/dto/manage_achievement_request.go b/internal/achievements/manage_achievements/dto/manage_achievement_request.go index 8e7bf4f..06a096d 100644 --- a/internal/achievements/manage_achievements/dto/manage_achievement_request.go +++ b/internal/achievements/manage_achievements/dto/manage_achievement_request.go @@ -1,9 +1,6 @@ package dto -import "mime/multipart" - type UpdateAchievementRequest struct { - Level string `json:"level"` - TargetPoint int `json:"target_point"` - Badge *multipart.FileHeader `json:"-"` + Level string `json:"level" form:"level"` + TargetPoint int `json:"target_point" form:"target_point"` } diff --git a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go index 6345c69..8479af5 100644 --- a/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go +++ b/internal/achievements/manage_achievements/handler/manage_achievement_handler_impl.go @@ -1,9 +1,7 @@ package handler import ( - "encoding/json" "errors" - "mime/multipart" "net/http" "strconv" @@ -74,34 +72,24 @@ func (handler ManageAchievementHandlerImpl) UpdateAchievementHandler(c echo.Cont } request := dto.UpdateAchievementRequest{} - json_data := c.FormValue("json_data") - if err := json.Unmarshal([]byte(json_data), &request); err != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) - } - if err := c.Validate(&request); err != nil { + + if err := c.Bind(&request); err != nil { return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) } - form, errForm := c.MultipartForm() - if errForm != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) - } - var badge []*multipart.FileHeader - if form != nil { - badge = form.File["badge"] - } + badge, _ := c.FormFile("badge") err := handler.usecae.UpdateAchievementUsecase(&request, badge, achievementIdInt) if err != nil { if errors.Is(err, pkg.ErrAchievementNotFound) { return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrAchievementNotFound.Error()) } - if errors.Is(err, pkg.ErrBadgeMaximum) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrBadgeMaximum.Error()) + if errors.Is(err, pkg.ErrFileTooLarge) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrFileTooLarge.Error()) } - if errors.Is(err, errors.New("upload image size must less than 2MB")) { - return helper.ErrorHandler(c, http.StatusBadRequest, "upload image size must less than 2MB") + if errors.Is(err, pkg.ErrInvalidFileType) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrInvalidFileType.Error()) } - if errors.Is(err, errors.New("only image allowed")) { - return helper.ErrorHandler(c, http.StatusBadRequest, "only image allowed") + if errors.Is(err, pkg.ErrOpenFile) { + return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrOpenFile.Error()) } if errors.Is(err, pkg.ErrUploadCloudinary) { return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrUploadCloudinary.Error()) diff --git a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go index d5e8803..b762503 100644 --- a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go +++ b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase.go @@ -10,6 +10,6 @@ import ( type ManageAchievementUsecase interface { GetAllArchievementUsecase() ([]*archievement.Achievement, error) GetAchievementByIdUsecase(id int) (*archievement.Achievement, error) - UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge []*multipart.FileHeader, id int) error + UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge *multipart.FileHeader, id int) error DeleteAchievementUsecase(id int) error } diff --git a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go index f3c72d0..cb7cb7d 100644 --- a/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go +++ b/internal/achievements/manage_achievements/usecase/manage_achievement_usecase_impl.go @@ -36,30 +36,34 @@ func (repository ManageAchievementUsecaseImpl) GetAchievementByIdUsecase(id int) return achievement, nil } -func (repository ManageAchievementUsecaseImpl) UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge []*multipart.FileHeader, id int) error { - if len(badge) > 1 { - return pkg.ErrBadgeMaximum +func (repository ManageAchievementUsecaseImpl) UpdateAchievementUsecase(request *dto.UpdateAchievementRequest, badge *multipart.FileHeader, id int) error { + achievement, err := repository.repository.GetAchievementById(id) + if err != nil { + return pkg.ErrAchievementNotFound } var urlBadge string - if len(badge) == 1 { - validImages, errImages := helper.ImagesValidation(badge) - if errImages != nil { - return errImages + if badge != nil { + if badge.Size > 2*1024*1024 { + return pkg.ErrFileTooLarge } - urlBadgeUpload, errUpload := helper.UploadToCloudinary(validImages[0], "achievements_badge") + if !strings.HasPrefix(badge.Header.Get("Content-Type"), "image") { + return pkg.ErrInvalidFileType + } + src, errOpen := badge.Open() + if errOpen != nil { + return pkg.ErrOpenFile + } + + urlBadgeUpload, errUpload := helper.UploadToCloudinary(src, "achievement_badge") if errUpload != nil { - return pkg.ErrUploadCloudinary + return errUpload } urlBadge = urlBadgeUpload - } - - achievement, err := repository.repository.GetAchievementById(id) - if err != nil { - return pkg.ErrAchievementNotFound + defer src.Close() } if request.Level != "" { - achievement.Level = strings.ToLower(request.Level) + achievement.Level = request.Level } if request.TargetPoint != 0 { achievement.TargetPoint = request.TargetPoint diff --git a/pkg/errors.go b/pkg/errors.go index 5efbf77..0ed94e6 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -74,4 +74,9 @@ var ( // Article ErrArticleNotFound = errors.New("article not found") + + // Error file + ErrFileTooLarge = errors.New("upload image size must less than 2MB") + ErrInvalidFileType = errors.New("invalid file type") + ErrOpenFile = errors.New("failed to open file") ) From 472c936ec5c50f45449658d228fe2cdb0dc946c7 Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Tue, 11 Jun 2024 01:32:40 +0700 Subject: [PATCH 25/72] chore: update dummy for video category --- internal/database/mysql.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 670e6d1..700f151 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -250,12 +250,11 @@ func (m *mysqlDatabase) InitAchievements() { func (m *mysqlDatabase) InitVideoCategories() { videoCategories := []video.VideoCategory{ - {Name: "Tips"}, - {Name: "Daur Ulang"}, - {Name: "Tutorial"}, - {Name: "Edukasi"}, - {Name: "Kampanye"}, - {Name: "Lainnya"}, + {Name: "tips"}, + {Name: "daur ulang"}, + {Name: "tutorial"}, + {Name: "edukasi"}, + {Name: "kampanye"}, } for _, videoCategory := range videoCategories { m.GetDB().FirstOrCreate(&videoCategory, videoCategory) From a5458be04f74137a8c34db3adb60cda1127b8133 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 10 Jun 2024 21:43:13 +0700 Subject: [PATCH 26/72] refactor(manage video): add some logic on handler --- .../manage_video/handler/manage_video_handler_impl.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/video/manage_video/handler/manage_video_handler_impl.go b/internal/video/manage_video/handler/manage_video_handler_impl.go index 3ca95ff..95af538 100644 --- a/internal/video/manage_video/handler/manage_video_handler_impl.go +++ b/internal/video/manage_video/handler/manage_video_handler_impl.go @@ -201,12 +201,12 @@ func (handler *ManageVideoHandlerImpl) UpdateDataVideoHandler(c echo.Context) er } var request dto.UpdateDataVideoRequest json_data := c.FormValue("json_data") - if err := json.Unmarshal([]byte(json_data), &request); err != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) - } - if err := c.Validate(&request); err != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + if json_data != "" { + if err := json.Unmarshal([]byte(json_data), &request); err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + } } + form, errForm := c.MultipartForm() if errForm != nil { return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) From 613e13f655ba432997e367d82e83ae4c11cee035 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Jun 2024 12:15:23 +0700 Subject: [PATCH 27/72] refactor(endpoint create data video): change entity, request body, and some logic --- cmd/api/main.go | 3 + internal/database/database.go | 1 + internal/database/migrate.go | 1 + internal/database/mysql.go | 80 +++++--- internal/server/route.go | 4 +- .../manage_video/dto/manage_video_request.go | 19 +- .../entity/manage_video_entity.go | 24 ++- .../handler/manage_video_handler.go | 4 +- .../handler/manage_video_handler_impl.go | 190 +++++++++--------- .../repository/manage_video_repository.go | 1 + .../manage_video_repository_impl.go | 8 + .../usecase/manage_video_usecase.go | 2 +- .../usecase/manage_video_usecase_impl.go | 140 ++++++++----- pkg/errors.go | 5 +- 14 files changed, 289 insertions(+), 193 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index f5edf7e..74b0310 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -39,6 +39,9 @@ func main() { // Init Video Categories db.InitVideoCategories() + // Init Trash Category Video + db.InitTrashCategoryVideo() + // Init About us db.InitAboutUs() diff --git a/internal/database/database.go b/internal/database/database.go index 10d2058..6dd790f 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -11,6 +11,7 @@ type Database interface { InitTasks() InitTaskSteps() InitAchievements() + InitTrashCategoryVideo() InitVideoCategories() InitAboutUs() InitDataVideos() diff --git a/internal/database/migrate.go b/internal/database/migrate.go index fa598a4..656fba1 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -40,6 +40,7 @@ func AutoMigrate(db Database) { &article.Article{}, &article.ArticleSection{}, &article.ArticleCategories{}, + &video.TrashCategory{}, ); err != nil { log.Fatal("Database Migration Failed!") } diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 700f151..2eb4025 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -248,13 +248,36 @@ func (m *mysqlDatabase) InitAchievements() { log.Println("Dummy Achievements added!") } +func (m *mysqlDatabase) InitDataVideos() { + videos := []video.Video{ + { + ID: 1, + Title: "Daur Ulang", + Description: "Tips Daur Ulang", + Link: "https://www.youtube.com/watch?v=MJd3bo_XRaU", + }, + { + ID: 2, + Title: "Tutorial Bernapas", + Description: "Tutorial Bernapas Bagi Pemula", + Link: "https://www.youtube.com/watch?v=jp5uhrdhsKI", + }, + } + + for _, video := range videos { + m.GetDB().FirstOrCreate(&video, video) + } + + log.Println("Video data added!") +} + func (m *mysqlDatabase) InitVideoCategories() { videoCategories := []video.VideoCategory{ - {Name: "tips"}, - {Name: "daur ulang"}, - {Name: "tutorial"}, - {Name: "edukasi"}, - {Name: "kampanye"}, + {Name: "tips", VideoID: 1}, + {Name: "daur ulang", VideoID: 1}, + {Name: "tutorial", VideoID: 1}, + {Name: "edukasi", VideoID: 2}, + {Name: "kampanye", VideoID: 2}, } for _, videoCategory := range videoCategories { m.GetDB().FirstOrCreate(&videoCategory, videoCategory) @@ -318,30 +341,6 @@ func (m *mysqlDatabase) InitAboutUs() { log.Println("About-us data added!") } -func (m *mysqlDatabase) InitDataVideos() { - videos := []video.Video{ - { - ID: 1, - Title: "Daur Ulang", - Description: "Tips Daur Ulang", - Link: "https://www.youtube.com/watch?v=MJd3bo_XRaU", - VideoCategoryID: 1, - }, - { - ID: 2, - Title: "Tutorial Bernapas", - Description: "Tutorial Bernapas Bagi Pemula", - Link: "https://www.youtube.com/watch?v=jp5uhrdhsKI", - VideoCategoryID: 3, - }, - } - - for _, video := range videos { - m.GetDB().FirstOrCreate(&video, video) - } - log.Println("Video data added!") -} - func (m *mysqlDatabase) InitArticleCategory() { categories := []article.WasteCategory{ {ID: 1, Name: "plastik"}, @@ -399,6 +398,29 @@ func (m *mysqlDatabase) InitArticle() { log.Println("Article data added!") } +func (m *mysqlDatabase) InitTrashCategoryVideo() { + trashCategories := []video.TrashCategory{ + {ID: 1, Name: "plastik", VideoID: 1}, + {ID: 2, Name: "besi", VideoID: 1}, + {ID: 3, Name: "kaca", VideoID: 1}, + {ID: 4, Name: "organik", VideoID: 1}, + {ID: 5, Name: "kayu", VideoID: 1}, + {ID: 6, Name: "kertas", VideoID: 1}, + {ID: 7, Name: "baterai", VideoID: 2}, + {ID: 8, Name: "kaleng", VideoID: 2}, + {ID: 9, Name: "elektronik", VideoID: 2}, + {ID: 10, Name: "tekstil", VideoID: 2}, + {ID: 11, Name: "minyak", VideoID: 2}, + {ID: 12, Name: "bola lampu", VideoID: 2}, + {ID: 13, Name: "berbahaya", VideoID: 2}, + } + + for _, category := range trashCategories { + m.GetDB().FirstOrCreate(&category, category) + } + log.Println("Trash categories data added!") +} + func (m *mysqlDatabase) GetDB() *gorm.DB { return dbInstance.DB } diff --git a/internal/server/route.go b/internal/server/route.go index c2d10bb..1429040 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -334,10 +334,10 @@ func (s *echoServer) manageVideo() { s.gr.GET("/videos/data", handler.GetAllDataVideoPaginationHandler, SuperAdminOrAdminMiddleware) // get details data video by id - s.gr.GET("/videos/data/:videoId", handler.GetDetailsDataVideoByIdHandler, SuperAdminOrAdminMiddleware) + // s.gr.GET("/videos/data/:videoId", handler.GetDetailsDataVideoByIdHandler, SuperAdminOrAdminMiddleware) // update data video - s.gr.PATCH("/videos/data/:videoId", handler.UpdateDataVideoHandler, SuperAdminOrAdminMiddleware) + // s.gr.PATCH("/videos/data/:videoId", handler.UpdateDataVideoHandler, SuperAdminOrAdminMiddleware) // delete data video s.gr.DELETE("/videos/data/:videoId", handler.DeleteDataVideoHandler, SuperAdminOrAdminMiddleware) diff --git a/internal/video/manage_video/dto/manage_video_request.go b/internal/video/manage_video/dto/manage_video_request.go index 5c496a7..da6089c 100644 --- a/internal/video/manage_video/dto/manage_video_request.go +++ b/internal/video/manage_video/dto/manage_video_request.go @@ -3,11 +3,20 @@ package dto import "mime/multipart" type CreateDataVideoRequest struct { - Title string `json:"title" validate:"required"` - Description string `json:"description" validate:"required"` - LinkVideo string `json:"link_video" validate:"required"` - CategoryId int `json:"category_id" validate:"required"` - Thumbnail *multipart.FileHeader `json:"-"` + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + LinkVideo string `json:"link_video" validate:"required"` + VideoCategories []DataCategoryVideo `json:"video_categories"` + TrashCategories []DataTrashCategory `json:"trash_categories"` + Thumbnail *multipart.FileHeader `json:"-"` +} + +type DataCategoryVideo struct { + Name string `json:"name"` +} + +type DataTrashCategory struct { + Name string `json:"name"` } type CreateCategoryVideoRequest struct { diff --git a/internal/video/manage_video/entity/manage_video_entity.go b/internal/video/manage_video/entity/manage_video_entity.go index 08a60bb..cc47517 100644 --- a/internal/video/manage_video/entity/manage_video_entity.go +++ b/internal/video/manage_video/entity/manage_video_entity.go @@ -14,16 +14,26 @@ type Video struct { Thumbnail string Link string Viewer int - VideoCategoryID int `gorm:"index"` - Category VideoCategory `gorm:"foreignKey:VideoCategoryID"` - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `gorm:"index"` + VideoCategories []VideoCategory `gorm:"foreignKey:VideoID"` + TrashCategories []TrashCategory `gorm:"foreignKey:VideoID"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index"` +} + +type TrashCategory struct { + ID int `gorm:"primaryKey"` + VideoID int `gorm:"index"` + Name string + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index"` } type VideoCategory struct { - ID int `gorm:"primaryKey"` - Name string `gorm:"unique;not null"` + ID int `gorm:"primaryKey"` + VideoID int `gorm:"index"` + Name string CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` diff --git a/internal/video/manage_video/handler/manage_video_handler.go b/internal/video/manage_video/handler/manage_video_handler.go index 53b6fb5..ad977a7 100644 --- a/internal/video/manage_video/handler/manage_video_handler.go +++ b/internal/video/manage_video/handler/manage_video_handler.go @@ -7,7 +7,7 @@ type ManageVideoHandler interface { CreateCategoryVideoHandler(c echo.Context) error GetAllCategoryVideoHandler(c echo.Context) error GetAllDataVideoPaginationHandler(c echo.Context) error - GetDetailsDataVideoByIdHandler(c echo.Context) error - UpdateDataVideoHandler(c echo.Context) error + // GetDetailsDataVideoByIdHandler(c echo.Context) error + // UpdateDataVideoHandler(c echo.Context) error DeleteDataVideoHandler(c echo.Context) error } diff --git a/internal/video/manage_video/handler/manage_video_handler_impl.go b/internal/video/manage_video/handler/manage_video_handler_impl.go index 95af538..815f282 100644 --- a/internal/video/manage_video/handler/manage_video_handler_impl.go +++ b/internal/video/manage_video/handler/manage_video_handler_impl.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "math" - "mime/multipart" "net/http" "strconv" @@ -42,8 +41,17 @@ func (handler *ManageVideoHandlerImpl) CreateDataVideoHandler(c echo.Context) er if errors.Is(err, pkg.ErrVideoTitleAlreadyExist) { return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoTitleAlreadyExist.Error()) } - if errors.Is(err, pkg.ErrVideoCategoryNotFound) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoCategoryNotFound.Error()) + if errors.Is(err, pkg.ErrVideoCategory) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoCategory.Error()) + } + if errors.Is(err, pkg.ErrVideoTrashCategory) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoTrashCategory.Error()) + } + if errors.Is(err, pkg.ErrNameCategoryVideoNotFound) { + return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrNameCategoryVideoNotFound.Error()) + } + if errors.Is(err, pkg.ErrNameTrashCategoryNotFound) { + return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrNameTrashCategoryNotFound.Error()) } if errors.Is(err, pkg.ErrNoVideoIdFoundOnUrl) { return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrNoVideoIdFoundOnUrl.Error()) @@ -166,97 +174,97 @@ func (handler *ManageVideoHandlerImpl) GetAllDataVideoPaginationHandler(c echo.C return c.JSON(http.StatusOK, data) } -func (handler *ManageVideoHandlerImpl) GetDetailsDataVideoByIdHandler(c echo.Context) error { - id := c.Param("videoId") - idInt, errConvert := strconv.Atoi(id) - if errConvert != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, "invalid id parameter") - } - video, err := handler.ManageVideoUsecase.GetDetailsDataVideoByIdUseCase(idInt) - if err != nil { - if errors.Is(err, pkg.ErrVideoNotFound) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) - } - return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) - } - var dataVideo *dto.GetDetailsDataVideoByIdResponse - dataVideo = &dto.GetDetailsDataVideoByIdResponse{ - Id: video.ID, - Title: video.Title, - Description: video.Description, - UrlThumbnail: video.Thumbnail, - LinkVideo: video.Link, - Viewer: video.Viewer, - Category: dto.DataCategory{Id: video.Category.ID, Name: video.Category.Name}, - } - responseData := helper.ResponseData(http.StatusOK, "success", dataVideo) - return c.JSON(http.StatusOK, responseData) -} +// func (handler *ManageVideoHandlerImpl) GetDetailsDataVideoByIdHandler(c echo.Context) error { +// id := c.Param("videoId") +// idInt, errConvert := strconv.Atoi(id) +// if errConvert != nil { +// return helper.ErrorHandler(c, http.StatusBadRequest, "invalid id parameter") +// } +// video, err := handler.ManageVideoUsecase.GetDetailsDataVideoByIdUseCase(idInt) +// if err != nil { +// if errors.Is(err, pkg.ErrVideoNotFound) { +// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) +// } +// return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) +// } +// var dataVideo *dto.GetDetailsDataVideoByIdResponse +// dataVideo = &dto.GetDetailsDataVideoByIdResponse{ +// Id: video.ID, +// Title: video.Title, +// Description: video.Description, +// UrlThumbnail: video.Thumbnail, +// LinkVideo: video.Link, +// Viewer: video.Viewer, +// Category: dto.DataCategory{Id: video.Category.ID, Name: video.Category.Name}, +// } +// responseData := helper.ResponseData(http.StatusOK, "success", dataVideo) +// return c.JSON(http.StatusOK, responseData) +// } -func (handler *ManageVideoHandlerImpl) UpdateDataVideoHandler(c echo.Context) error { - id := c.Param("videoId") - idInt, errConvert := strconv.Atoi(id) - if errConvert != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, "invalid id parameter") - } - var request dto.UpdateDataVideoRequest - json_data := c.FormValue("json_data") - if json_data != "" { - if err := json.Unmarshal([]byte(json_data), &request); err != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) - } - } +// func (handler *ManageVideoHandlerImpl) UpdateDataVideoHandler(c echo.Context) error { +// id := c.Param("videoId") +// idInt, errConvert := strconv.Atoi(id) +// if errConvert != nil { +// return helper.ErrorHandler(c, http.StatusBadRequest, "invalid id parameter") +// } +// var request dto.UpdateDataVideoRequest +// json_data := c.FormValue("json_data") +// if json_data != "" { +// if err := json.Unmarshal([]byte(json_data), &request); err != nil { +// return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) +// } +// } - form, errForm := c.MultipartForm() - if errForm != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) - } - var thumbnail []*multipart.FileHeader - if form != nil { - thumbnail = form.File["thumbnail"] - } +// form, errForm := c.MultipartForm() +// if errForm != nil { +// return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) +// } +// var thumbnail []*multipart.FileHeader +// if form != nil { +// thumbnail = form.File["thumbnail"] +// } - if err := handler.ManageVideoUsecase.UpdateDataVideoUseCase(&request, thumbnail, idInt); err != nil { - if errors.Is(err, pkg.ErrVideoNotFound) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) - } - if errors.Is(err, pkg.ErrVideoCategoryNotFound) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoCategoryNotFound.Error()) - } - if errors.Is(err, pkg.ErrNoVideoIdFoundOnUrl) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrNoVideoIdFoundOnUrl.Error()) - } - if errors.Is(err, pkg.ErrVideoNotFound) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) - } - if errors.Is(err, pkg.ErrVideoService) { - return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrVideoService.Error()) - } - if errors.Is(err, pkg.ErrApiYouTube) { - return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrApiYouTube.Error()) - } - if errors.Is(err, pkg.ErrParsingUrl) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrParsingUrl.Error()) - } - if errors.Is(err, pkg.ErrThumbnail) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrThumbnail.Error()) - } - if errors.Is(err, pkg.ErrThumbnailMaximum) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrThumbnailMaximum.Error()) - } - if errors.Is(err, errors.New("upload image size must less than 2MB")) { - return helper.ErrorHandler(c, http.StatusBadRequest, "upload image size must less than 2MB") - } - if errors.Is(err, errors.New("only image allowed")) { - return helper.ErrorHandler(c, http.StatusBadRequest, "only image allowed") - } - if errors.Is(err, pkg.ErrUploadCloudinary) { - return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrUploadCloudinary.Error()) - } - return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) - } - return helper.ResponseHandler(c, http.StatusOK, "success update data video", nil) -} +// if err := handler.ManageVideoUsecase.UpdateDataVideoUseCase(&request, thumbnail, idInt); err != nil { +// if errors.Is(err, pkg.ErrVideoNotFound) { +// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) +// } +// if errors.Is(err, pkg.ErrVideoCategoryNotFound) { +// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoCategoryNotFound.Error()) +// } +// if errors.Is(err, pkg.ErrNoVideoIdFoundOnUrl) { +// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrNoVideoIdFoundOnUrl.Error()) +// } +// if errors.Is(err, pkg.ErrVideoNotFound) { +// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) +// } +// if errors.Is(err, pkg.ErrVideoService) { +// return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrVideoService.Error()) +// } +// if errors.Is(err, pkg.ErrApiYouTube) { +// return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrApiYouTube.Error()) +// } +// if errors.Is(err, pkg.ErrParsingUrl) { +// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrParsingUrl.Error()) +// } +// if errors.Is(err, pkg.ErrThumbnail) { +// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrThumbnail.Error()) +// } +// if errors.Is(err, pkg.ErrThumbnailMaximum) { +// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrThumbnailMaximum.Error()) +// } +// if errors.Is(err, errors.New("upload image size must less than 2MB")) { +// return helper.ErrorHandler(c, http.StatusBadRequest, "upload image size must less than 2MB") +// } +// if errors.Is(err, errors.New("only image allowed")) { +// return helper.ErrorHandler(c, http.StatusBadRequest, "only image allowed") +// } +// if errors.Is(err, pkg.ErrUploadCloudinary) { +// return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrUploadCloudinary.Error()) +// } +// return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) +// } +// return helper.ResponseHandler(c, http.StatusOK, "success update data video", nil) +// } func (handler *ManageVideoHandlerImpl) DeleteDataVideoHandler(c echo.Context) error { id := c.Param("videoId") diff --git a/internal/video/manage_video/repository/manage_video_repository.go b/internal/video/manage_video/repository/manage_video_repository.go index 13153d3..4d9a942 100644 --- a/internal/video/manage_video/repository/manage_video_repository.go +++ b/internal/video/manage_video/repository/manage_video_repository.go @@ -9,6 +9,7 @@ type ManageVideoRepository interface { FindTitleVideo(title string) error CreateCategoryVideo(category *video.VideoCategory) error FindNameCategoryVideo(name string) error + FindNamaTrashCategory(name string) error GetAllCategoryVideo() ([]video.VideoCategory, error) GetCategoryVideoById(id int) (*video.VideoCategory, error) GetAllDataVideoPagination(limit int, page int) ([]video.Video, int, error) diff --git a/internal/video/manage_video/repository/manage_video_repository_impl.go b/internal/video/manage_video/repository/manage_video_repository_impl.go index 28c788b..beca140 100644 --- a/internal/video/manage_video/repository/manage_video_repository_impl.go +++ b/internal/video/manage_video/repository/manage_video_repository_impl.go @@ -43,6 +43,14 @@ func (repository *ManageVideoRepositoryImpl) FindNameCategoryVideo(name string) return nil } +func (repository *ManageVideoRepositoryImpl) FindNamaTrashCategory(name string) error { + var category video.TrashCategory + if err := repository.DB.GetDB().Where("name = ?", name).First(&category).Error; err != nil { + return err + } + return nil +} + func (repository *ManageVideoRepositoryImpl) GetAllCategoryVideo() ([]video.VideoCategory, error) { var categories []video.VideoCategory if err := repository.DB.GetDB().Find(&categories).Error; err != nil { diff --git a/internal/video/manage_video/usecase/manage_video_usecase.go b/internal/video/manage_video/usecase/manage_video_usecase.go index d7ddfd3..889232b 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase.go +++ b/internal/video/manage_video/usecase/manage_video_usecase.go @@ -13,6 +13,6 @@ type ManageVideoUsecase interface { GetAllCategoryVideoUseCase() ([]video.VideoCategory, error) GetAllDataVideoPaginationUseCase(limit int, page int) ([]video.Video, int, error) GetDetailsDataVideoByIdUseCase(id int) (*video.Video, error) - UpdateDataVideoUseCase(request *dto.UpdateDataVideoRequest, thumbnail []*multipart.FileHeader, id int) error + // UpdateDataVideoUseCase(request *dto.UpdateDataVideoRequest, thumbnail []*multipart.FileHeader, id int) error DeleteDataVideoUseCase(id int) error } diff --git a/internal/video/manage_video/usecase/manage_video_usecase_impl.go b/internal/video/manage_video/usecase/manage_video_usecase_impl.go index 77ea74d..62e4299 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase_impl.go +++ b/internal/video/manage_video/usecase/manage_video_usecase_impl.go @@ -26,6 +26,12 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat if len(thumbnail) == 0 { return pkg.ErrThumbnail } + if len(request.VideoCategories) == 0 { + return pkg.ErrVideoCategory + } + if len(request.TrashCategories) == 0 { + return pkg.ErrVideoTrashCategory + } if len(thumbnail) > 1 { return pkg.ErrThumbnailMaximum } @@ -37,14 +43,37 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat if err := usecase.manageVideoRepository.FindTitleVideo(request.Title); err == nil { return pkg.ErrVideoTitleAlreadyExist } - if _, err := usecase.manageVideoRepository.GetCategoryVideoById(request.CategoryId); err != nil { - return pkg.ErrVideoCategoryNotFound + + var videoCategories []video.VideoCategory + var trashCategories []video.TrashCategory + + for _, category := range request.VideoCategories { + name := strings.ToLower(category.Name) + if err := usecase.manageVideoRepository.FindNameCategoryVideo(name); err == gorm.ErrRecordNotFound { + return pkg.ErrNameCategoryVideoNotFound + } + videoCategory := video.VideoCategory{ + Name: name, + DeletedAt: gorm.DeletedAt{}, + } + videoCategories = append(videoCategories, videoCategory) + } + for _, category := range request.TrashCategories { + name := strings.ToLower(category.Name) + if err := usecase.manageVideoRepository.FindNamaTrashCategory(name); err == gorm.ErrRecordNotFound { + return pkg.ErrNameTrashCategoryNotFound + } + trashCategory := video.TrashCategory{ + Name: name, + DeletedAt: gorm.DeletedAt{}, + } + trashCategories = append(trashCategories, trashCategory) } view, errGetView := helper.GetVideoViewCount(request.LinkVideo) if errGetView != nil { return errGetView } - urlThumbnail, errUpload := helper.UploadToCloudinary(validImages[0], "video_thumbnail_update") + urlThumbnail, errUpload := helper.UploadToCloudinary(validImages[0], "video_thumbnail") if errUpload != nil { return pkg.ErrUploadCloudinary } @@ -54,8 +83,9 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat Description: request.Description, Thumbnail: urlThumbnail, Link: request.LinkVideo, - VideoCategoryID: request.CategoryId, Viewer: intView, + VideoCategories: videoCategories, + TrashCategories: trashCategories, DeletedAt: gorm.DeletedAt{}, } if err := usecase.manageVideoRepository.CreateDataVideo(&video); err != nil { @@ -106,60 +136,60 @@ func (usecase *ManageVideoUsecaseImpl) GetDetailsDataVideoByIdUseCase(id int) (* return video, nil } -func (usecase *ManageVideoUsecaseImpl) UpdateDataVideoUseCase(request *dto.UpdateDataVideoRequest, thumbnail []*multipart.FileHeader, id int) error { - if len(thumbnail) > 1 { - return pkg.ErrThumbnailMaximum - } - var urlThumbnail string - if len(thumbnail) == 1 { - validImages, errImages := helper.ImagesValidation(thumbnail) - if errImages != nil { - return errImages - } - urlThumbnailUpload, errUpload := helper.UploadToCloudinary(validImages[0], "video_thumbnail_update") - if errUpload != nil { - return pkg.ErrUploadCloudinary - } - urlThumbnail = urlThumbnailUpload - } +// func (usecase *ManageVideoUsecaseImpl) UpdateDataVideoUseCase(request *dto.UpdateDataVideoRequest, thumbnail []*multipart.FileHeader, id int) error { +// if len(thumbnail) > 1 { +// return pkg.ErrThumbnailMaximum +// } +// var urlThumbnail string +// if len(thumbnail) == 1 { +// validImages, errImages := helper.ImagesValidation(thumbnail) +// if errImages != nil { +// return errImages +// } +// urlThumbnailUpload, errUpload := helper.UploadToCloudinary(validImages[0], "video_thumbnail_update") +// if errUpload != nil { +// return pkg.ErrUploadCloudinary +// } +// urlThumbnail = urlThumbnailUpload +// } - video, err := usecase.manageVideoRepository.GetDetailsDataVideoById(id) - if err != nil { - return pkg.ErrVideoNotFound - } +// video, err := usecase.manageVideoRepository.GetDetailsDataVideoById(id) +// if err != nil { +// return pkg.ErrVideoNotFound +// } - if request.Title != "" { - video.Title = request.Title - } - if request.Description != "" { - video.Description = request.Description - } - if urlThumbnail != "" { - video.Thumbnail = urlThumbnail - } - if request.LinkVideo != "" { - view, errGetView := helper.GetVideoViewCount(request.LinkVideo) - if errGetView != nil { - return errGetView - } - if view != 0 { - intView := int(view) - video.Viewer = intView - } - video.Link = request.LinkVideo - } - if request.CategoryId != 0 { - if _, err := usecase.manageVideoRepository.GetCategoryVideoById(request.CategoryId); err != nil { - return pkg.ErrVideoCategoryNotFound - } - video.VideoCategoryID = request.CategoryId - } +// if request.Title != "" { +// video.Title = request.Title +// } +// if request.Description != "" { +// video.Description = request.Description +// } +// if urlThumbnail != "" { +// video.Thumbnail = urlThumbnail +// } +// if request.LinkVideo != "" { +// view, errGetView := helper.GetVideoViewCount(request.LinkVideo) +// if errGetView != nil { +// return errGetView +// } +// if view != 0 { +// intView := int(view) +// video.Viewer = intView +// } +// video.Link = request.LinkVideo +// } +// if request.CategoryId != 0 { +// if _, err := usecase.manageVideoRepository.GetCategoryVideoById(request.CategoryId); err != nil { +// return pkg.ErrVideoCategoryNotFound +// } +// video.VideoCategoryID = request.CategoryId +// } - if err := usecase.manageVideoRepository.UpdateDataVideo(video, id); err != nil { - return err - } - return nil -} +// if err := usecase.manageVideoRepository.UpdateDataVideo(video, id); err != nil { +// return err +// } +// return nil +// } func (usecase *ManageVideoUsecaseImpl) DeleteDataVideoUseCase(id int) error { if _, err := usecase.manageVideoRepository.GetDetailsDataVideoById(id); err != nil { diff --git a/pkg/errors.go b/pkg/errors.go index 0ed94e6..461e087 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -64,7 +64,10 @@ var ( ErrVideoService = errors.New("video service error") ErrApiYouTube = errors.New("api youtube error") ErrParsingUrl = errors.New("parsing url error") - ErrVideoCategoryNotFound = errors.New("video category not found") + ErrVideoCategory = errors.New("video category is required") + ErrVideoTrashCategory = errors.New("video category tash is required") + ErrNameCategoryVideoNotFound = errors.New("name category video not found") + ErrNameTrashCategoryNotFound = errors.New("name trash category not found") // user achievement ErrUserNotHasHistoryPoint = errors.New("user not has history points") From e7bcf159896b5d4ae1ca4e8bd190d25ccc872f93 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Jun 2024 12:38:16 +0700 Subject: [PATCH 28/72] delete endpoint for create data category video --- internal/server/route.go | 3 --- .../handler/manage_video_handler.go | 1 - .../handler/manage_video_handler_impl.go | 18 ------------------ .../repository/manage_video_repository.go | 1 - .../repository/manage_video_repository_impl.go | 7 ------- .../usecase/manage_video_usecase.go | 1 - 6 files changed, 31 deletions(-) diff --git a/internal/server/route.go b/internal/server/route.go index 1429040..111a31a 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -324,9 +324,6 @@ func (s *echoServer) manageVideo() { // create data video s.gr.POST("/videos/data", handler.CreateDataVideoHandler, SuperAdminOrAdminMiddleware) - // create category video - s.gr.POST("/videos/categories", handler.CreateCategoryVideoHandler, SuperAdminOrAdminMiddleware) - // get all category video s.gr.GET("/videos/categories", handler.GetAllCategoryVideoHandler, SuperAdminOrAdminMiddleware) diff --git a/internal/video/manage_video/handler/manage_video_handler.go b/internal/video/manage_video/handler/manage_video_handler.go index ad977a7..daac49c 100644 --- a/internal/video/manage_video/handler/manage_video_handler.go +++ b/internal/video/manage_video/handler/manage_video_handler.go @@ -4,7 +4,6 @@ import "github.com/labstack/echo/v4" type ManageVideoHandler interface { CreateDataVideoHandler(c echo.Context) error - CreateCategoryVideoHandler(c echo.Context) error GetAllCategoryVideoHandler(c echo.Context) error GetAllDataVideoPaginationHandler(c echo.Context) error // GetDetailsDataVideoByIdHandler(c echo.Context) error diff --git a/internal/video/manage_video/handler/manage_video_handler_impl.go b/internal/video/manage_video/handler/manage_video_handler_impl.go index 815f282..5cffe0d 100644 --- a/internal/video/manage_video/handler/manage_video_handler_impl.go +++ b/internal/video/manage_video/handler/manage_video_handler_impl.go @@ -88,24 +88,6 @@ func (handler *ManageVideoHandlerImpl) CreateDataVideoHandler(c echo.Context) er return helper.ResponseHandler(c, http.StatusCreated, "success create data video", nil) } -func (handler *ManageVideoHandlerImpl) CreateCategoryVideoHandler(c echo.Context) error { - var request dto.CreateCategoryVideoRequest - - if err := c.Bind(&request); err != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, "invalid request body, detail+"+err.Error()) - } - if err := c.Validate(&request); err != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) - } - if err := handler.ManageVideoUsecase.CreateCategoryVideoUseCase(&request); err != nil { - if errors.Is(err, pkg.ErrVideoCategoryNameAlreadyExist) { - return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoCategoryNameAlreadyExist.Error()) - } - return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) - } - return helper.ResponseHandler(c, http.StatusCreated, "success create category video", nil) -} - func (handler *ManageVideoHandlerImpl) GetAllCategoryVideoHandler(c echo.Context) error { categories, err := handler.ManageVideoUsecase.GetAllCategoryVideoUseCase() if err != nil { diff --git a/internal/video/manage_video/repository/manage_video_repository.go b/internal/video/manage_video/repository/manage_video_repository.go index 4d9a942..3c5be63 100644 --- a/internal/video/manage_video/repository/manage_video_repository.go +++ b/internal/video/manage_video/repository/manage_video_repository.go @@ -7,7 +7,6 @@ import ( type ManageVideoRepository interface { CreateDataVideo(video *video.Video) error FindTitleVideo(title string) error - CreateCategoryVideo(category *video.VideoCategory) error FindNameCategoryVideo(name string) error FindNamaTrashCategory(name string) error GetAllCategoryVideo() ([]video.VideoCategory, error) diff --git a/internal/video/manage_video/repository/manage_video_repository_impl.go b/internal/video/manage_video/repository/manage_video_repository_impl.go index beca140..0b3b3d4 100644 --- a/internal/video/manage_video/repository/manage_video_repository_impl.go +++ b/internal/video/manage_video/repository/manage_video_repository_impl.go @@ -28,13 +28,6 @@ func (repository *ManageVideoRepositoryImpl) FindTitleVideo(title string) error return nil } -func (repository *ManageVideoRepositoryImpl) CreateCategoryVideo(category *video.VideoCategory) error { - if err := repository.DB.GetDB().Create(&category).Error; err != nil { - return err - } - return nil -} - func (repository *ManageVideoRepositoryImpl) FindNameCategoryVideo(name string) error { var category video.VideoCategory if err := repository.DB.GetDB().Where("name = ?", name).First(&category).Error; err != nil { diff --git a/internal/video/manage_video/usecase/manage_video_usecase.go b/internal/video/manage_video/usecase/manage_video_usecase.go index 889232b..a4fd412 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase.go +++ b/internal/video/manage_video/usecase/manage_video_usecase.go @@ -9,7 +9,6 @@ import ( type ManageVideoUsecase interface { CreateDataVideoUseCase(request *dto.CreateDataVideoRequest, thumbnail []*multipart.FileHeader) error - CreateCategoryVideoUseCase(request *dto.CreateCategoryVideoRequest) error GetAllCategoryVideoUseCase() ([]video.VideoCategory, error) GetAllDataVideoPaginationUseCase(limit int, page int) ([]video.Video, int, error) GetDetailsDataVideoByIdUseCase(id int) (*video.Video, error) From e5a3e483e73d5dcdf56ee064e377bb72afe7592c Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Jun 2024 16:05:24 +0700 Subject: [PATCH 29/72] refactort(get detail data video): change some request body and repository --- cmd/api/main.go | 18 ++-- internal/database/database.go | 4 +- internal/server/route.go | 2 +- .../manage_video/dto/manage_video_response.go | 25 ++++-- .../entity/manage_video_entity.go | 4 +- .../handler/manage_video_handler.go | 2 +- .../handler/manage_video_handler_impl.go | 88 ++++++++++++------- .../manage_video_repository_impl.go | 3 +- .../usecase/manage_video_usecase_impl.go | 15 ---- 9 files changed, 90 insertions(+), 71 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 74b0310..1a6dd0d 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -36,24 +36,24 @@ func main() { // Init Achievements db.InitAchievements() - // Init Video Categories - db.InitVideoCategories() - - // Init Trash Category Video - db.InitTrashCategoryVideo() - // Init About us db.InitAboutUs() - // Init Videos - db.InitDataVideos() - // Init Article Categories db.InitArticleCategory() // Init Article db.InitArticle() + // Init Videos + db.InitDataVideos() + + // Init Video Categories + db.InitVideoCategories() + + // Init Trash Category Video + db.InitTrashCategoryVideo() + app := server.NewEchoServer(conf, db) c := cron.New() diff --git a/internal/database/database.go b/internal/database/database.go index 6dd790f..30cc294 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -11,10 +11,10 @@ type Database interface { InitTasks() InitTaskSteps() InitAchievements() - InitTrashCategoryVideo() - InitVideoCategories() InitAboutUs() InitDataVideos() InitArticleCategory() InitArticle() + InitTrashCategoryVideo() + InitVideoCategories() } diff --git a/internal/server/route.go b/internal/server/route.go index 111a31a..3c9e50f 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -331,7 +331,7 @@ func (s *echoServer) manageVideo() { s.gr.GET("/videos/data", handler.GetAllDataVideoPaginationHandler, SuperAdminOrAdminMiddleware) // get details data video by id - // s.gr.GET("/videos/data/:videoId", handler.GetDetailsDataVideoByIdHandler, SuperAdminOrAdminMiddleware) + s.gr.GET("/videos/data/:videoId", handler.GetDetailsDataVideoByIdHandler, SuperAdminOrAdminMiddleware) // update data video // s.gr.PATCH("/videos/data/:videoId", handler.UpdateDataVideoHandler, SuperAdminOrAdminMiddleware) diff --git a/internal/video/manage_video/dto/manage_video_response.go b/internal/video/manage_video/dto/manage_video_response.go index cf99deb..f544aff 100644 --- a/internal/video/manage_video/dto/manage_video_response.go +++ b/internal/video/manage_video/dto/manage_video_response.go @@ -1,10 +1,16 @@ package dto type GetAllCategoryVideoResponse struct { - Data []*DataCategory + VideoCategory []*DataVideoCategory `json:"video_categories"` + TrashCategory []*DataTrashCategoryResponse `json:"trash_categories"` } -type DataCategory struct { +type DataVideoCategory struct { + Id int `json:"id"` + Name string `json:"name"` +} + +type DataTrashCategoryResponse struct { Id int `json:"id"` Name string `json:"name"` } @@ -27,11 +33,12 @@ type GetAllDataVideoPaginationResponse struct { } type GetDetailsDataVideoByIdResponse struct { - Id int `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - UrlThumbnail string `json:"url_thumbnail"` - LinkVideo string `json:"link_video"` - Viewer int `json:"viewer"` - Category DataCategory `json:"category"` + Id int `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + UrlThumbnail string `json:"url_thumbnail"` + LinkVideo string `json:"link_video"` + Viewer int `json:"viewer"` + VideoCategory []*DataVideoCategory `json:"video_category"` + TrashCategory []*DataTrashCategoryResponse `json:"trash_category"` } diff --git a/internal/video/manage_video/entity/manage_video_entity.go b/internal/video/manage_video/entity/manage_video_entity.go index cc47517..b8cac91 100644 --- a/internal/video/manage_video/entity/manage_video_entity.go +++ b/internal/video/manage_video/entity/manage_video_entity.go @@ -14,8 +14,8 @@ type Video struct { Thumbnail string Link string Viewer int - VideoCategories []VideoCategory `gorm:"foreignKey:VideoID"` - TrashCategories []TrashCategory `gorm:"foreignKey:VideoID"` + VideoCategories []VideoCategory `gorm:"foreignKey:VideoID;references:ID"` + TrashCategories []TrashCategory `gorm:"foreignKey:VideoID;references:ID"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` diff --git a/internal/video/manage_video/handler/manage_video_handler.go b/internal/video/manage_video/handler/manage_video_handler.go index daac49c..f2edc71 100644 --- a/internal/video/manage_video/handler/manage_video_handler.go +++ b/internal/video/manage_video/handler/manage_video_handler.go @@ -6,7 +6,7 @@ type ManageVideoHandler interface { CreateDataVideoHandler(c echo.Context) error GetAllCategoryVideoHandler(c echo.Context) error GetAllDataVideoPaginationHandler(c echo.Context) error - // GetDetailsDataVideoByIdHandler(c echo.Context) error + GetDetailsDataVideoByIdHandler(c echo.Context) error // UpdateDataVideoHandler(c echo.Context) error DeleteDataVideoHandler(c echo.Context) error } diff --git a/internal/video/manage_video/handler/manage_video_handler_impl.go b/internal/video/manage_video/handler/manage_video_handler_impl.go index 5cffe0d..3c742e8 100644 --- a/internal/video/manage_video/handler/manage_video_handler_impl.go +++ b/internal/video/manage_video/handler/manage_video_handler_impl.go @@ -93,19 +93,30 @@ func (handler *ManageVideoHandlerImpl) GetAllCategoryVideoHandler(c echo.Context if err != nil { return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) } - var dataCategories []*dto.DataCategory + var dataVideoCategories []*dto.DataVideoCategory + var dataTrashCategories []*dto.DataTrashCategoryResponse data := &dto.GetAllCategoryVideoResponse{ - Data: []*dto.DataCategory{}, + VideoCategory: dataVideoCategories, + TrashCategory: dataTrashCategories, } for _, category := range categories { - dataCategories = append(dataCategories, &dto.DataCategory{ + dataVideoCategories = append(dataVideoCategories, &dto.DataVideoCategory{ Id: category.ID, Name: category.Name, }) } - data.Data = dataCategories - responseData := helper.ResponseData(http.StatusOK, "success", data.Data) + + for _, category := range categories { + dataTrashCategories = append(dataTrashCategories, &dto.DataTrashCategoryResponse{ + Id: category.ID, + Name: category.Name, + }) + } + + data.VideoCategory = dataVideoCategories + data.TrashCategory = dataTrashCategories + responseData := helper.ResponseData(http.StatusOK, "success", data) return c.JSON(http.StatusOK, responseData) } @@ -156,32 +167,47 @@ func (handler *ManageVideoHandlerImpl) GetAllDataVideoPaginationHandler(c echo.C return c.JSON(http.StatusOK, data) } -// func (handler *ManageVideoHandlerImpl) GetDetailsDataVideoByIdHandler(c echo.Context) error { -// id := c.Param("videoId") -// idInt, errConvert := strconv.Atoi(id) -// if errConvert != nil { -// return helper.ErrorHandler(c, http.StatusBadRequest, "invalid id parameter") -// } -// video, err := handler.ManageVideoUsecase.GetDetailsDataVideoByIdUseCase(idInt) -// if err != nil { -// if errors.Is(err, pkg.ErrVideoNotFound) { -// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) -// } -// return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) -// } -// var dataVideo *dto.GetDetailsDataVideoByIdResponse -// dataVideo = &dto.GetDetailsDataVideoByIdResponse{ -// Id: video.ID, -// Title: video.Title, -// Description: video.Description, -// UrlThumbnail: video.Thumbnail, -// LinkVideo: video.Link, -// Viewer: video.Viewer, -// Category: dto.DataCategory{Id: video.Category.ID, Name: video.Category.Name}, -// } -// responseData := helper.ResponseData(http.StatusOK, "success", dataVideo) -// return c.JSON(http.StatusOK, responseData) -// } +func (handler *ManageVideoHandlerImpl) GetDetailsDataVideoByIdHandler(c echo.Context) error { + id := c.Param("videoId") + idInt, errConvert := strconv.Atoi(id) + if errConvert != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, "invalid id parameter") + } + video, err := handler.ManageVideoUsecase.GetDetailsDataVideoByIdUseCase(idInt) + if err != nil { + if errors.Is(err, pkg.ErrVideoNotFound) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) + } + return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) + } + var dataVideo *dto.GetDetailsDataVideoByIdResponse + var dataVideoCategories []*dto.DataVideoCategory + var dataTrashCategories []*dto.DataTrashCategoryResponse + for _, category := range video.VideoCategories { + dataVideoCategories = append(dataVideoCategories, &dto.DataVideoCategory{ + Id: category.ID, + Name: category.Name, + }) + } + for _, category := range video.TrashCategories { + dataTrashCategories = append(dataTrashCategories, &dto.DataTrashCategoryResponse{ + Id: category.ID, + Name: category.Name, + }) + } + dataVideo = &dto.GetDetailsDataVideoByIdResponse{ + Id: video.ID, + Title: video.Title, + Description: video.Description, + UrlThumbnail: video.Thumbnail, + LinkVideo: video.Link, + Viewer: video.Viewer, + VideoCategory: dataVideoCategories, + TrashCategory: dataTrashCategories, + } + responseData := helper.ResponseData(http.StatusOK, "success", dataVideo) + return c.JSON(http.StatusOK, responseData) +} // func (handler *ManageVideoHandlerImpl) UpdateDataVideoHandler(c echo.Context) error { // id := c.Param("videoId") diff --git a/internal/video/manage_video/repository/manage_video_repository_impl.go b/internal/video/manage_video/repository/manage_video_repository_impl.go index 0b3b3d4..5c68005 100644 --- a/internal/video/manage_video/repository/manage_video_repository_impl.go +++ b/internal/video/manage_video/repository/manage_video_repository_impl.go @@ -77,7 +77,8 @@ func (repository *ManageVideoRepositoryImpl) GetAllDataVideoPagination(limit int func (repository *ManageVideoRepositoryImpl) GetDetailsDataVideoById(id int) (*video.Video, error) { var video video.Video if err := repository.DB.GetDB(). - Preload("Category"). + Preload("VideoCategories"). + Preload("TrashCategories"). Where("id = ?", id). First(&video).Error; err != nil { return nil, err diff --git a/internal/video/manage_video/usecase/manage_video_usecase_impl.go b/internal/video/manage_video/usecase/manage_video_usecase_impl.go index 62e4299..38ab5f7 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase_impl.go +++ b/internal/video/manage_video/usecase/manage_video_usecase_impl.go @@ -94,21 +94,6 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat return nil } -func (usecase *ManageVideoUsecaseImpl) CreateCategoryVideoUseCase(request *dto.CreateCategoryVideoRequest) error { - if err := usecase.manageVideoRepository.FindNameCategoryVideo(request.Name); err == nil { - return pkg.ErrVideoCategoryNameAlreadyExist - } - name := strings.ToLower(request.Name) - category := video.VideoCategory{ - Name: name, - DeletedAt: gorm.DeletedAt{}, - } - if err := usecase.manageVideoRepository.CreateCategoryVideo(&category); err != nil { - return err - } - return nil -} - func (usecase *ManageVideoUsecaseImpl) GetAllCategoryVideoUseCase() ([]video.VideoCategory, error) { categories, err := usecase.manageVideoRepository.GetAllCategoryVideo() if err != nil { From 57bb674b671316e0de668c1a0214b37ccaeabc43 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Jun 2024 16:49:03 +0700 Subject: [PATCH 30/72] refactor(get all category): add trash category to response body and change some repo adn usecase --- .../manage_video/dto/manage_video_response.go | 4 ++-- .../handler/manage_video_handler_impl.go | 21 ++++++++----------- .../repository/manage_video_repository.go | 3 ++- .../manage_video_repository_impl.go | 16 +++++++++++--- .../usecase/manage_video_usecase.go | 2 +- .../usecase/manage_video_usecase_impl.go | 14 ++++++++----- 6 files changed, 36 insertions(+), 24 deletions(-) diff --git a/internal/video/manage_video/dto/manage_video_response.go b/internal/video/manage_video/dto/manage_video_response.go index f544aff..5121269 100644 --- a/internal/video/manage_video/dto/manage_video_response.go +++ b/internal/video/manage_video/dto/manage_video_response.go @@ -1,8 +1,8 @@ package dto type GetAllCategoryVideoResponse struct { - VideoCategory []*DataVideoCategory `json:"video_categories"` - TrashCategory []*DataTrashCategoryResponse `json:"trash_categories"` + VideoCategory []*DataCategoryVideo `json:"video_categories"` + TrashCategory []*DataTrashCategory `json:"trash_categories"` } type DataVideoCategory struct { diff --git a/internal/video/manage_video/handler/manage_video_handler_impl.go b/internal/video/manage_video/handler/manage_video_handler_impl.go index 3c742e8..ae55a57 100644 --- a/internal/video/manage_video/handler/manage_video_handler_impl.go +++ b/internal/video/manage_video/handler/manage_video_handler_impl.go @@ -89,28 +89,25 @@ func (handler *ManageVideoHandlerImpl) CreateDataVideoHandler(c echo.Context) er } func (handler *ManageVideoHandlerImpl) GetAllCategoryVideoHandler(c echo.Context) error { - categories, err := handler.ManageVideoUsecase.GetAllCategoryVideoUseCase() + videoCategories, trashCategories, err := handler.ManageVideoUsecase.GetAllCategoryVideoUseCase() if err != nil { return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) } - var dataVideoCategories []*dto.DataVideoCategory - var dataTrashCategories []*dto.DataTrashCategoryResponse + var dataVideoCategories []*dto.DataCategoryVideo + var dataTrashCategories []*dto.DataTrashCategory data := &dto.GetAllCategoryVideoResponse{ VideoCategory: dataVideoCategories, TrashCategory: dataTrashCategories, } - for _, category := range categories { - dataVideoCategories = append(dataVideoCategories, &dto.DataVideoCategory{ - Id: category.ID, - Name: category.Name, + for _, category := range videoCategories { + dataVideoCategories = append(dataVideoCategories, &dto.DataCategoryVideo{ + Name: category, }) } - - for _, category := range categories { - dataTrashCategories = append(dataTrashCategories, &dto.DataTrashCategoryResponse{ - Id: category.ID, - Name: category.Name, + for _, category := range trashCategories { + dataTrashCategories = append(dataTrashCategories, &dto.DataTrashCategory{ + Name: category, }) } diff --git a/internal/video/manage_video/repository/manage_video_repository.go b/internal/video/manage_video/repository/manage_video_repository.go index 3c5be63..4c5661f 100644 --- a/internal/video/manage_video/repository/manage_video_repository.go +++ b/internal/video/manage_video/repository/manage_video_repository.go @@ -9,7 +9,8 @@ type ManageVideoRepository interface { FindTitleVideo(title string) error FindNameCategoryVideo(name string) error FindNamaTrashCategory(name string) error - GetAllCategoryVideo() ([]video.VideoCategory, error) + GetAllCategoryVideo() ([]string, error) + GetAllTrashCategoryVideo() ([]string, error) GetCategoryVideoById(id int) (*video.VideoCategory, error) GetAllDataVideoPagination(limit int, page int) ([]video.Video, int, error) GetDetailsDataVideoById(id int) (*video.Video, error) diff --git a/internal/video/manage_video/repository/manage_video_repository_impl.go b/internal/video/manage_video/repository/manage_video_repository_impl.go index 5c68005..ed7f3e9 100644 --- a/internal/video/manage_video/repository/manage_video_repository_impl.go +++ b/internal/video/manage_video/repository/manage_video_repository_impl.go @@ -44,9 +44,19 @@ func (repository *ManageVideoRepositoryImpl) FindNamaTrashCategory(name string) return nil } -func (repository *ManageVideoRepositoryImpl) GetAllCategoryVideo() ([]video.VideoCategory, error) { - var categories []video.VideoCategory - if err := repository.DB.GetDB().Find(&categories).Error; err != nil { +func (repository *ManageVideoRepositoryImpl) GetAllCategoryVideo() ([]string, error) { + var categories []string + if err := repository.DB.GetDB().Model(&video.VideoCategory{}).Distinct("name").Pluck("name", &categories). + Error; err != nil { + + } + return categories, nil +} + +func (repository *ManageVideoRepositoryImpl) GetAllTrashCategoryVideo() ([]string, error) { + var categories []string + if err := repository.DB.GetDB().Model(&video.TrashCategory{}).Distinct("name").Pluck("name", &categories). + Error; err != nil { return nil, err } return categories, nil diff --git a/internal/video/manage_video/usecase/manage_video_usecase.go b/internal/video/manage_video/usecase/manage_video_usecase.go index a4fd412..e2df8b2 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase.go +++ b/internal/video/manage_video/usecase/manage_video_usecase.go @@ -9,7 +9,7 @@ import ( type ManageVideoUsecase interface { CreateDataVideoUseCase(request *dto.CreateDataVideoRequest, thumbnail []*multipart.FileHeader) error - GetAllCategoryVideoUseCase() ([]video.VideoCategory, error) + GetAllCategoryVideoUseCase() ([]string, []string, error) GetAllDataVideoPaginationUseCase(limit int, page int) ([]video.Video, int, error) GetDetailsDataVideoByIdUseCase(id int) (*video.Video, error) // UpdateDataVideoUseCase(request *dto.UpdateDataVideoRequest, thumbnail []*multipart.FileHeader, id int) error diff --git a/internal/video/manage_video/usecase/manage_video_usecase_impl.go b/internal/video/manage_video/usecase/manage_video_usecase_impl.go index 38ab5f7..491d146 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase_impl.go +++ b/internal/video/manage_video/usecase/manage_video_usecase_impl.go @@ -94,12 +94,16 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat return nil } -func (usecase *ManageVideoUsecaseImpl) GetAllCategoryVideoUseCase() ([]video.VideoCategory, error) { - categories, err := usecase.manageVideoRepository.GetAllCategoryVideo() - if err != nil { - return nil, err +func (usecase *ManageVideoUsecaseImpl) GetAllCategoryVideoUseCase() ([]string, []string, error) { + videoCategories, errvidCategory := usecase.manageVideoRepository.GetAllCategoryVideo() + if errvidCategory != nil { + return nil, nil, errvidCategory + } + trashCategories, errTrashCategory := usecase.manageVideoRepository.GetAllTrashCategoryVideo() + if errTrashCategory != nil { + return nil, nil, errTrashCategory } - return categories, nil + return videoCategories, trashCategories, nil } func (usecase *ManageVideoUsecaseImpl) GetAllDataVideoPaginationUseCase(limit int, page int) ([]video.Video, int, error) { From ccb48f76e8f9182deae611d6ed32ccc2f13dafaa Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Jun 2024 18:16:09 +0700 Subject: [PATCH 31/72] refactor(update data video): change request body for update and change some logic on usecase, repo --- internal/server/route.go | 2 +- .../manage_video/dto/manage_video_request.go | 11 +- .../handler/manage_video_handler.go | 2 +- .../handler/manage_video_handler_impl.go | 128 ++++++++-------- .../manage_video_repository_impl.go | 36 ++++- .../usecase/manage_video_usecase.go | 2 +- .../usecase/manage_video_usecase_impl.go | 140 +++++++++++------- 7 files changed, 195 insertions(+), 126 deletions(-) diff --git a/internal/server/route.go b/internal/server/route.go index 3c9e50f..1667305 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -334,7 +334,7 @@ func (s *echoServer) manageVideo() { s.gr.GET("/videos/data/:videoId", handler.GetDetailsDataVideoByIdHandler, SuperAdminOrAdminMiddleware) // update data video - // s.gr.PATCH("/videos/data/:videoId", handler.UpdateDataVideoHandler, SuperAdminOrAdminMiddleware) + s.gr.PATCH("/videos/data/:videoId", handler.UpdateDataVideoHandler, SuperAdminOrAdminMiddleware) // delete data video s.gr.DELETE("/videos/data/:videoId", handler.DeleteDataVideoHandler, SuperAdminOrAdminMiddleware) diff --git a/internal/video/manage_video/dto/manage_video_request.go b/internal/video/manage_video/dto/manage_video_request.go index da6089c..fa1a90d 100644 --- a/internal/video/manage_video/dto/manage_video_request.go +++ b/internal/video/manage_video/dto/manage_video_request.go @@ -24,9 +24,10 @@ type CreateCategoryVideoRequest struct { } type UpdateDataVideoRequest struct { - Title string `json:"title"` - Description string `json:"description"` - LinkVideo string `json:"link_video"` - CategoryId int `json:"category_id"` - Thumbnail *multipart.FileHeader `json:"-"` + Title string `json:"title"` + Description string `json:"description"` + LinkVideo string `json:"link_video"` + VideoCategories []DataCategoryVideo `json:"video_categories"` + TrashCategories []DataTrashCategory `json:"trash_categories"` + Thumbnail *multipart.FileHeader `json:"-"` } diff --git a/internal/video/manage_video/handler/manage_video_handler.go b/internal/video/manage_video/handler/manage_video_handler.go index f2edc71..f12057e 100644 --- a/internal/video/manage_video/handler/manage_video_handler.go +++ b/internal/video/manage_video/handler/manage_video_handler.go @@ -7,6 +7,6 @@ type ManageVideoHandler interface { GetAllCategoryVideoHandler(c echo.Context) error GetAllDataVideoPaginationHandler(c echo.Context) error GetDetailsDataVideoByIdHandler(c echo.Context) error - // UpdateDataVideoHandler(c echo.Context) error + UpdateDataVideoHandler(c echo.Context) error DeleteDataVideoHandler(c echo.Context) error } diff --git a/internal/video/manage_video/handler/manage_video_handler_impl.go b/internal/video/manage_video/handler/manage_video_handler_impl.go index ae55a57..3ccaf63 100644 --- a/internal/video/manage_video/handler/manage_video_handler_impl.go +++ b/internal/video/manage_video/handler/manage_video_handler_impl.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "math" + "mime/multipart" "net/http" "strconv" @@ -206,70 +207,73 @@ func (handler *ManageVideoHandlerImpl) GetDetailsDataVideoByIdHandler(c echo.Con return c.JSON(http.StatusOK, responseData) } -// func (handler *ManageVideoHandlerImpl) UpdateDataVideoHandler(c echo.Context) error { -// id := c.Param("videoId") -// idInt, errConvert := strconv.Atoi(id) -// if errConvert != nil { -// return helper.ErrorHandler(c, http.StatusBadRequest, "invalid id parameter") -// } -// var request dto.UpdateDataVideoRequest -// json_data := c.FormValue("json_data") -// if json_data != "" { -// if err := json.Unmarshal([]byte(json_data), &request); err != nil { -// return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) -// } -// } +func (handler *ManageVideoHandlerImpl) UpdateDataVideoHandler(c echo.Context) error { + id := c.Param("videoId") + idInt, errConvert := strconv.Atoi(id) + if errConvert != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, "invalid id parameter") + } + var request dto.UpdateDataVideoRequest + json_data := c.FormValue("json_data") + if json_data != "" { + if err := json.Unmarshal([]byte(json_data), &request); err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + } + } -// form, errForm := c.MultipartForm() -// if errForm != nil { -// return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) -// } -// var thumbnail []*multipart.FileHeader -// if form != nil { -// thumbnail = form.File["thumbnail"] -// } + form, errForm := c.MultipartForm() + if errForm != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, errForm.Error()) + } + var thumbnail []*multipart.FileHeader + if form != nil { + thumbnail = form.File["thumbnail"] + } -// if err := handler.ManageVideoUsecase.UpdateDataVideoUseCase(&request, thumbnail, idInt); err != nil { -// if errors.Is(err, pkg.ErrVideoNotFound) { -// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) -// } -// if errors.Is(err, pkg.ErrVideoCategoryNotFound) { -// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoCategoryNotFound.Error()) -// } -// if errors.Is(err, pkg.ErrNoVideoIdFoundOnUrl) { -// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrNoVideoIdFoundOnUrl.Error()) -// } -// if errors.Is(err, pkg.ErrVideoNotFound) { -// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) -// } -// if errors.Is(err, pkg.ErrVideoService) { -// return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrVideoService.Error()) -// } -// if errors.Is(err, pkg.ErrApiYouTube) { -// return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrApiYouTube.Error()) -// } -// if errors.Is(err, pkg.ErrParsingUrl) { -// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrParsingUrl.Error()) -// } -// if errors.Is(err, pkg.ErrThumbnail) { -// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrThumbnail.Error()) -// } -// if errors.Is(err, pkg.ErrThumbnailMaximum) { -// return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrThumbnailMaximum.Error()) -// } -// if errors.Is(err, errors.New("upload image size must less than 2MB")) { -// return helper.ErrorHandler(c, http.StatusBadRequest, "upload image size must less than 2MB") -// } -// if errors.Is(err, errors.New("only image allowed")) { -// return helper.ErrorHandler(c, http.StatusBadRequest, "only image allowed") -// } -// if errors.Is(err, pkg.ErrUploadCloudinary) { -// return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrUploadCloudinary.Error()) -// } -// return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) -// } -// return helper.ResponseHandler(c, http.StatusOK, "success update data video", nil) -// } + if err := handler.ManageVideoUsecase.UpdateDataVideoUseCase(&request, thumbnail, idInt); err != nil { + if errors.Is(err, pkg.ErrVideoNotFound) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) + } + if errors.Is(err, pkg.ErrNameCategoryVideoNotFound) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrNameCategoryVideoNotFound.Error()) + } + if errors.Is(err, pkg.ErrNameTrashCategoryNotFound) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrNameTrashCategoryNotFound.Error()) + } + if errors.Is(err, pkg.ErrNoVideoIdFoundOnUrl) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrNoVideoIdFoundOnUrl.Error()) + } + if errors.Is(err, pkg.ErrVideoNotFound) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrVideoNotFound.Error()) + } + if errors.Is(err, pkg.ErrVideoService) { + return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrVideoService.Error()) + } + if errors.Is(err, pkg.ErrApiYouTube) { + return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrApiYouTube.Error()) + } + if errors.Is(err, pkg.ErrParsingUrl) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrParsingUrl.Error()) + } + if errors.Is(err, pkg.ErrThumbnail) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrThumbnail.Error()) + } + if errors.Is(err, pkg.ErrThumbnailMaximum) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrThumbnailMaximum.Error()) + } + if errors.Is(err, errors.New("upload image size must less than 2MB")) { + return helper.ErrorHandler(c, http.StatusBadRequest, "upload image size must less than 2MB") + } + if errors.Is(err, errors.New("only image allowed")) { + return helper.ErrorHandler(c, http.StatusBadRequest, "only image allowed") + } + if errors.Is(err, pkg.ErrUploadCloudinary) { + return helper.ErrorHandler(c, http.StatusInternalServerError, pkg.ErrUploadCloudinary.Error()) + } + return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) + } + return helper.ResponseHandler(c, http.StatusOK, "success update data video", nil) +} func (handler *ManageVideoHandlerImpl) DeleteDataVideoHandler(c echo.Context) error { id := c.Param("videoId") diff --git a/internal/video/manage_video/repository/manage_video_repository_impl.go b/internal/video/manage_video/repository/manage_video_repository_impl.go index ed7f3e9..9fcaa16 100644 --- a/internal/video/manage_video/repository/manage_video_repository_impl.go +++ b/internal/video/manage_video/repository/manage_video_repository_impl.go @@ -1,8 +1,11 @@ package repository import ( + "log" + "github.com/sawalreverr/recything/internal/database" video "github.com/sawalreverr/recything/internal/video/manage_video/entity" + "gorm.io/gorm" ) type ManageVideoRepositoryImpl struct { @@ -96,10 +99,39 @@ func (repository *ManageVideoRepositoryImpl) GetDetailsDataVideoById(id int) (*v return &video, nil } -func (repository *ManageVideoRepositoryImpl) UpdateDataVideo(video *video.Video, id int) error { - if err := repository.DB.GetDB().Model(&video).Where("id = ?", id).Updates(&video).Error; err != nil { +func (repository *ManageVideoRepositoryImpl) UpdateDataVideo(videos *video.Video, id int) error { + + tx := repository.DB.GetDB().Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + log.Println("Transaction rollback due to panic:", r) + } + }() + + if len(videos.VideoCategories) > 0 { + if err := tx.Model(&video.VideoCategory{}).Where("video_id = ?", id).Delete(&video.VideoCategory{}).Error; err != nil { + tx.Rollback() + return err + } + } + + if len(videos.TrashCategories) > 0 { + if err := tx.Model(&video.TrashCategory{}).Where("video_id = ?", id).Delete(&video.TrashCategory{}).Error; err != nil { + tx.Rollback() + return err + } + + } + if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Save(&videos).Error; err != nil { + tx.Rollback() + return err + } + if err := tx.Commit().Error; err != nil { + tx.Rollback() return err } + return nil } diff --git a/internal/video/manage_video/usecase/manage_video_usecase.go b/internal/video/manage_video/usecase/manage_video_usecase.go index e2df8b2..6194d6a 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase.go +++ b/internal/video/manage_video/usecase/manage_video_usecase.go @@ -12,6 +12,6 @@ type ManageVideoUsecase interface { GetAllCategoryVideoUseCase() ([]string, []string, error) GetAllDataVideoPaginationUseCase(limit int, page int) ([]video.Video, int, error) GetDetailsDataVideoByIdUseCase(id int) (*video.Video, error) - // UpdateDataVideoUseCase(request *dto.UpdateDataVideoRequest, thumbnail []*multipart.FileHeader, id int) error + UpdateDataVideoUseCase(request *dto.UpdateDataVideoRequest, thumbnail []*multipart.FileHeader, id int) error DeleteDataVideoUseCase(id int) error } diff --git a/internal/video/manage_video/usecase/manage_video_usecase_impl.go b/internal/video/manage_video/usecase/manage_video_usecase_impl.go index 491d146..e813faf 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase_impl.go +++ b/internal/video/manage_video/usecase/manage_video_usecase_impl.go @@ -125,60 +125,92 @@ func (usecase *ManageVideoUsecaseImpl) GetDetailsDataVideoByIdUseCase(id int) (* return video, nil } -// func (usecase *ManageVideoUsecaseImpl) UpdateDataVideoUseCase(request *dto.UpdateDataVideoRequest, thumbnail []*multipart.FileHeader, id int) error { -// if len(thumbnail) > 1 { -// return pkg.ErrThumbnailMaximum -// } -// var urlThumbnail string -// if len(thumbnail) == 1 { -// validImages, errImages := helper.ImagesValidation(thumbnail) -// if errImages != nil { -// return errImages -// } -// urlThumbnailUpload, errUpload := helper.UploadToCloudinary(validImages[0], "video_thumbnail_update") -// if errUpload != nil { -// return pkg.ErrUploadCloudinary -// } -// urlThumbnail = urlThumbnailUpload -// } - -// video, err := usecase.manageVideoRepository.GetDetailsDataVideoById(id) -// if err != nil { -// return pkg.ErrVideoNotFound -// } - -// if request.Title != "" { -// video.Title = request.Title -// } -// if request.Description != "" { -// video.Description = request.Description -// } -// if urlThumbnail != "" { -// video.Thumbnail = urlThumbnail -// } -// if request.LinkVideo != "" { -// view, errGetView := helper.GetVideoViewCount(request.LinkVideo) -// if errGetView != nil { -// return errGetView -// } -// if view != 0 { -// intView := int(view) -// video.Viewer = intView -// } -// video.Link = request.LinkVideo -// } -// if request.CategoryId != 0 { -// if _, err := usecase.manageVideoRepository.GetCategoryVideoById(request.CategoryId); err != nil { -// return pkg.ErrVideoCategoryNotFound -// } -// video.VideoCategoryID = request.CategoryId -// } - -// if err := usecase.manageVideoRepository.UpdateDataVideo(video, id); err != nil { -// return err -// } -// return nil -// } +func (usecase *ManageVideoUsecaseImpl) UpdateDataVideoUseCase(request *dto.UpdateDataVideoRequest, thumbnail []*multipart.FileHeader, id int) error { + if len(thumbnail) > 1 { + return pkg.ErrThumbnailMaximum + } + + dataVideo, err := usecase.manageVideoRepository.GetDetailsDataVideoById(id) + if err != nil { + return pkg.ErrVideoNotFound + } + var videoCategories []video.VideoCategory + var trashCategories []video.TrashCategory + + if request.VideoCategories != nil { + for _, category := range request.VideoCategories { + name := strings.ToLower(category.Name) + if err := usecase.manageVideoRepository.FindNameCategoryVideo(name); err == gorm.ErrRecordNotFound { + return pkg.ErrNameCategoryVideoNotFound + } + videoCategory := video.VideoCategory{ + VideoID: id, + Name: name, + DeletedAt: gorm.DeletedAt{}, + } + videoCategories = append(videoCategories, videoCategory) + } + dataVideo.VideoCategories = videoCategories + } else { + videoCategories = dataVideo.VideoCategories + } + + if request.TrashCategories != nil { + for _, category := range request.TrashCategories { + name := strings.ToLower(category.Name) + if err := usecase.manageVideoRepository.FindNamaTrashCategory(name); err == gorm.ErrRecordNotFound { + return pkg.ErrNameTrashCategoryNotFound + } + trashCategory := video.TrashCategory{ + VideoID: id, + Name: name, + DeletedAt: gorm.DeletedAt{}, + } + trashCategories = append(trashCategories, trashCategory) + } + dataVideo.TrashCategories = trashCategories + } else { + trashCategories = dataVideo.TrashCategories + } + var urlThumbnail string + if len(thumbnail) == 1 { + validImages, errImages := helper.ImagesValidation(thumbnail) + if errImages != nil { + return errImages + } + urlThumbnailUpload, errUpload := helper.UploadToCloudinary(validImages[0], "video_thumbnail_update") + if errUpload != nil { + return pkg.ErrUploadCloudinary + } + urlThumbnail = urlThumbnailUpload + } + + if request.Title != "" { + dataVideo.Title = request.Title + } + if request.Description != "" { + dataVideo.Description = request.Description + } + if urlThumbnail != "" { + dataVideo.Thumbnail = urlThumbnail + } + if request.LinkVideo != "" { + view, errGetView := helper.GetVideoViewCount(request.LinkVideo) + if errGetView != nil { + return errGetView + } + if view != 0 { + intView := int(view) + dataVideo.Viewer = intView + } + dataVideo.Link = request.LinkVideo + } + + if err := usecase.manageVideoRepository.UpdateDataVideo(dataVideo, id); err != nil { + return err + } + return nil +} func (usecase *ManageVideoUsecaseImpl) DeleteDataVideoUseCase(id int) error { if _, err := usecase.manageVideoRepository.GetDetailsDataVideoById(id); err != nil { From dfaf63c91d6ad6abab56a4dc9620566696a0b053 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Jun 2024 21:54:10 +0700 Subject: [PATCH 32/72] feat(vide): user video, add endpoint for search video by category --- internal/server/route.go | 8 +- .../user_video/dto/user_video_response.go | 25 +++++ .../user_video/handler/user_video_handler.go | 2 + .../handler/user_video_handler_impl.go | 92 ++++++++++++++++++- .../repository/user_video_repository.go | 2 + .../repository/user_video_repository_impl.go | 49 ++++++++++ .../user_video/usecase/user_video_usecase.go | 2 + .../usecase/user_video_usecase_impl.go | 28 ++++++ 8 files changed, 206 insertions(+), 2 deletions(-) diff --git a/internal/server/route.go b/internal/server/route.go index 1667305..e98e642 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -325,7 +325,7 @@ func (s *echoServer) manageVideo() { s.gr.POST("/videos/data", handler.CreateDataVideoHandler, SuperAdminOrAdminMiddleware) // get all category video - s.gr.GET("/videos/categories", handler.GetAllCategoryVideoHandler, SuperAdminOrAdminMiddleware) + s.gr.GET("/videos/categories", handler.GetAllCategoryVideoHandler, AllRoleMiddleware) // get all data video pagination s.gr.GET("/videos/data", handler.GetAllDataVideoPaginationHandler, SuperAdminOrAdminMiddleware) @@ -351,6 +351,12 @@ func (s *echoServer) userVideo() { // search video by title s.gr.GET("/videos/search", handler.SearchVideoByTitleHandler, UserMiddleware) + // search video by category video + s.gr.GET("/videos/content-category", handler.SearchVideoByCategoryVideoHandler, UserMiddleware) + + // search video by trash category video + s.gr.GET("/videos/trash-category", handler.SearchVideoByTrashCategoryVideoHandler, UserMiddleware) + // get video detail s.gr.GET("/videos/:videoId", handler.GetVideoDetailHandler, UserMiddleware) diff --git a/internal/video/user_video/dto/user_video_response.go b/internal/video/user_video/dto/user_video_response.go index 4b25711..3d02eed 100644 --- a/internal/video/user_video/dto/user_video_response.go +++ b/internal/video/user_video/dto/user_video_response.go @@ -27,3 +27,28 @@ type GetDetailsDataVideoByIdResponse struct { DataVideo *DataVideo `json:"data_video"` Comments *[]DataComment `json:"comments"` } + +type DataCategoryVideo struct { + Id int `json:"id"` + Name string `json:"name"` +} + +type DataTrashCategoryVideo struct { + Id int `json:"id"` + Name string `json:"name"` +} + +type SearchVideoByCategoryVideoResponse struct { + DataVideo []*DataVideoSearchByCategory `json:"data_video"` +} + +type DataVideoSearchByCategory struct { + Id int `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + UrlThumbnail string `json:"url_thumbnail"` + LinkVideo string `json:"link_video"` + Viewer int `json:"viewer"` + VideoCategory []*DataCategoryVideo `json:"video_categories"` + TrashCategory []*DataTrashCategoryVideo `json:"trash_categories"` +} diff --git a/internal/video/user_video/handler/user_video_handler.go b/internal/video/user_video/handler/user_video_handler.go index 23d39f4..a4197ce 100644 --- a/internal/video/user_video/handler/user_video_handler.go +++ b/internal/video/user_video/handler/user_video_handler.go @@ -5,6 +5,8 @@ import "github.com/labstack/echo/v4" type UserVideoHandler interface { GetAllVideoHandler(c echo.Context) error SearchVideoByTitleHandler(c echo.Context) error + SearchVideoByCategoryVideoHandler(c echo.Context) error + SearchVideoByTrashCategoryVideoHandler(c echo.Context) error GetVideoDetailHandler(c echo.Context) error AddCommentHandler(c echo.Context) error } diff --git a/internal/video/user_video/handler/user_video_handler_impl.go b/internal/video/user_video/handler/user_video_handler_impl.go index 00902e9..a96e7fa 100644 --- a/internal/video/user_video/handler/user_video_handler_impl.go +++ b/internal/video/user_video/handler/user_video_handler_impl.go @@ -67,11 +67,101 @@ func (handler *UserVideoHandlerImpl) SearchVideoByTitleHandler(c echo.Context) e }) } data.DataVideo = dataVideo - responseData := helper.ResponseData(http.StatusOK, "success get all video", data.DataVideo) + responseData := helper.ResponseData(http.StatusOK, "success", data.DataVideo) return c.JSON(http.StatusOK, responseData) } +func (handler *UserVideoHandlerImpl) SearchVideoByCategoryVideoHandler(c echo.Context) error { + categoryVideo := c.QueryParam("query") + videos, err := handler.Usecase.SearchVideoByCategoryVideoUsecase(categoryVideo) + if err != nil { + if errors.Is(err, pkg.ErrVideoNotFound) { + return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrVideoNotFound.Error()) + } + return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) + } + var dataVideo []*dto.DataVideoSearchByCategory + for _, video := range *videos { + videoCategories := make([]*dto.DataCategoryVideo, len(video.VideoCategories)) + for i, vc := range video.VideoCategories { + videoCategories[i] = &dto.DataCategoryVideo{ + Id: vc.ID, + Name: vc.Name, + } + } + + trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.TrashCategories)) + for i, tc := range video.TrashCategories { + trashCategories[i] = &dto.DataTrashCategoryVideo{ + Id: tc.ID, + Name: tc.Name, + } + } + + dataVideo = append(dataVideo, &dto.DataVideoSearchByCategory{ + Id: video.ID, + Title: video.Title, + Description: video.Description, + UrlThumbnail: video.Thumbnail, + LinkVideo: video.Link, + Viewer: video.Viewer, + VideoCategory: videoCategories, + TrashCategory: trashCategories, + }) + } + + responseData := dto.SearchVideoByCategoryVideoResponse{ + DataVideo: dataVideo, + } + return c.JSON(http.StatusOK, helper.ResponseData(http.StatusOK, "success", responseData)) +} + +func (handler *UserVideoHandlerImpl) SearchVideoByTrashCategoryVideoHandler(c echo.Context) error { + trashCategory := c.QueryParam("query") + videos, err := handler.Usecase.SearchVideoByTrashCategoryVideoUsecase(trashCategory) + if err != nil { + if errors.Is(err, pkg.ErrVideoNotFound) { + return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrVideoNotFound.Error()) + } + return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) + } + var dataVideo []*dto.DataVideoSearchByCategory + for _, video := range *videos { + videoCategories := make([]*dto.DataCategoryVideo, len(video.VideoCategories)) + for i, vc := range video.VideoCategories { + videoCategories[i] = &dto.DataCategoryVideo{ + Id: vc.ID, + Name: vc.Name, + } + } + + trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.TrashCategories)) + for i, tc := range video.TrashCategories { + trashCategories[i] = &dto.DataTrashCategoryVideo{ + Id: tc.ID, + Name: tc.Name, + } + } + + dataVideo = append(dataVideo, &dto.DataVideoSearchByCategory{ + Id: video.ID, + Title: video.Title, + Description: video.Description, + UrlThumbnail: video.Thumbnail, + LinkVideo: video.Link, + Viewer: video.Viewer, + VideoCategory: videoCategories, + TrashCategory: trashCategories, + }) + } + + responseData := dto.SearchVideoByCategoryVideoResponse{ + DataVideo: dataVideo, + } + return c.JSON(http.StatusOK, helper.ResponseData(http.StatusOK, "success", responseData)) +} + func (handler *UserVideoHandlerImpl) GetVideoDetailHandler(c echo.Context) error { id := c.Param("videoId") intId, errConvert := strconv.Atoi(id) diff --git a/internal/video/user_video/repository/user_video_repository.go b/internal/video/user_video/repository/user_video_repository.go index 023c41c..bab0e21 100644 --- a/internal/video/user_video/repository/user_video_repository.go +++ b/internal/video/user_video/repository/user_video_repository.go @@ -7,6 +7,8 @@ import ( type UserVideoRepository interface { GetAllVideo() (*[]video.Video, error) SearchVideoByTitle(title string) (*[]video.Video, error) + SearchVideoByCategoryVideo(categoryVideo string) (*[]video.Video, error) + SearchVideoByTrashCategoryVideo(trashCategory string) (*[]video.Video, error) GetVideoDetail(id int) (*video.Video, *[]video.Comment, error) AddComment(comment *video.Comment) error UpdateViewer(view int, id int) error diff --git a/internal/video/user_video/repository/user_video_repository_impl.go b/internal/video/user_video/repository/user_video_repository_impl.go index 7e7ea70..90b5d2e 100644 --- a/internal/video/user_video/repository/user_video_repository_impl.go +++ b/internal/video/user_video/repository/user_video_repository_impl.go @@ -32,6 +32,55 @@ func (repository *UserVideoRepositoryImpl) SearchVideoByTitle(title string) (*[] return &video, nil } +func (repository *UserVideoRepositoryImpl) SearchVideoByCategoryVideo(categoryVideo string) (*[]video.Video, error) { + var videos []video.Video + if err := repository.DB.GetDB(). + Order("created_at desc"). + Joins("JOIN video_categories ON video_categories.video_id = videos.id"). + Where("video_categories.name LIKE ?", "%"+categoryVideo+"%"). + Preload("VideoCategories"). + Preload("TrashCategories"). + Find(&videos).Error; err != nil { + return nil, err + } + + videoMap := make(map[int]video.Video) + for _, v := range videos { + videoMap[v.ID] = v + } + + uniqueVideos := make([]video.Video, 0, len(videoMap)) + for _, v := range videoMap { + uniqueVideos = append(uniqueVideos, v) + } + + return &uniqueVideos, nil +} + +func (repository *UserVideoRepositoryImpl) SearchVideoByTrashCategoryVideo(trashCategory string) (*[]video.Video, error) { + var videos []video.Video + if err := repository.DB.GetDB(). + Order("created_at desc"). + Joins("JOIN trash_categories ON trash_categories.video_id = videos.id"). + Where("trash_categories.name LIKE ?", "%"+trashCategory+"%"). + Preload("VideoCategories"). + Preload("TrashCategories"). + Find(&videos).Error; err != nil { + return nil, err + } + videoMap := make(map[int]video.Video) + for _, v := range videos { + videoMap[v.ID] = v + } + + uniqueVideos := make([]video.Video, 0, len(videoMap)) + for _, v := range videoMap { + uniqueVideos = append(uniqueVideos, v) + } + + return &uniqueVideos, nil +} + func (repository *UserVideoRepositoryImpl) GetVideoDetail(id int) (*video.Video, *[]video.Comment, error) { var videos video.Video var comments []video.Comment diff --git a/internal/video/user_video/usecase/user_video_usecase.go b/internal/video/user_video/usecase/user_video_usecase.go index ed8212b..9b821a3 100644 --- a/internal/video/user_video/usecase/user_video_usecase.go +++ b/internal/video/user_video/usecase/user_video_usecase.go @@ -8,6 +8,8 @@ import ( type UserVideoUsecase interface { GetAllVideoUsecase() (*[]video.Video, error) SearchVideoByTitleUsecase(title string) (*[]video.Video, error) + SearchVideoByCategoryVideoUsecase(categoryVideo string) (*[]video.Video, error) + SearchVideoByTrashCategoryVideoUsecase(trashCategory string) (*[]video.Video, error) GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, error) AddCommentUsecase(request *dto.AddCommentRequest, userId string) error } diff --git a/internal/video/user_video/usecase/user_video_usecase_impl.go b/internal/video/user_video/usecase/user_video_usecase_impl.go index 73cac4b..bdbef04 100644 --- a/internal/video/user_video/usecase/user_video_usecase_impl.go +++ b/internal/video/user_video/usecase/user_video_usecase_impl.go @@ -57,6 +57,34 @@ func (usecase *UserVideoUsecaseImpl) SearchVideoByTitleUsecase(title string) (*[ return videos, nil } +func (usecase *UserVideoUsecaseImpl) SearchVideoByCategoryVideoUsecase(categoryVideo string) (*[]video.Video, error) { + videos, err := usecase.Repository.SearchVideoByCategoryVideo(categoryVideo) + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, pkg.ErrVideoNotFound + } + return nil, err + } + if len(*videos) == 0 { + return nil, pkg.ErrVideoNotFound + } + return videos, nil +} + +func (usecase *UserVideoUsecaseImpl) SearchVideoByTrashCategoryVideoUsecase(trashCategory string) (*[]video.Video, error) { + videos, err := usecase.Repository.SearchVideoByTrashCategoryVideo(trashCategory) + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, pkg.ErrVideoNotFound + } + return nil, err + } + if len(*videos) == 0 { + return nil, pkg.ErrVideoNotFound + } + return videos, nil +} + func (usecase *UserVideoUsecaseImpl) GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, error) { video, comments, err := usecase.Repository.GetVideoDetail(id) if err != nil { From fcf795e080f981cd4284243104630d36c53a4cd1 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Jun 2024 22:06:54 +0700 Subject: [PATCH 33/72] docs: change request body for create,update data vide, and change response body for get all category --- docs/swagger.yml | 87 ++++++++++++++----- .../manage_video/dto/manage_video_response.go | 4 +- 2 files changed, 68 insertions(+), 23 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 685b1bf..b430f61 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -3042,12 +3042,22 @@ paths: items: type: object properties: - id: - type: integer - example: 1 - name: - type: string - example: "Category Name" + video_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "tips" + trash_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "berbahaya" '500': description: Internal server error @@ -3091,9 +3101,22 @@ paths: link_video: type: string example: "https://example.com/video.mp4" - category_id: - type: integer - example: 1 + video_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "tips" + trash_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "berbahaya" thumbnail: type: string format: binary @@ -3263,15 +3286,23 @@ paths: viewer: type: integer example: 1000 - category: - type: object - properties: - id: - type: integer - example: 1 - name: - type: string - example: "Video Category" + video_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "tips" + trash_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "berbahaya" + '404': description: Video not found content: @@ -3320,9 +3351,23 @@ paths: link_video: type: string example: "https://example.com/video.mp4" - category_id: - type: integer - example: 1 + video_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "tips" + trash_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "berbahaya" + thumbnail: type: string format: binary diff --git a/internal/video/manage_video/dto/manage_video_response.go b/internal/video/manage_video/dto/manage_video_response.go index 5121269..d047ece 100644 --- a/internal/video/manage_video/dto/manage_video_response.go +++ b/internal/video/manage_video/dto/manage_video_response.go @@ -39,6 +39,6 @@ type GetDetailsDataVideoByIdResponse struct { UrlThumbnail string `json:"url_thumbnail"` LinkVideo string `json:"link_video"` Viewer int `json:"viewer"` - VideoCategory []*DataVideoCategory `json:"video_category"` - TrashCategory []*DataTrashCategoryResponse `json:"trash_category"` + VideoCategory []*DataVideoCategory `json:"video_categories"` + TrashCategory []*DataTrashCategoryResponse `json:"trash_categories"` } From 35310caca416d907c037fef8fe680c701e2b1fd8 Mon Sep 17 00:00:00 2001 From: Mark Date: Tue, 11 Jun 2024 22:45:43 +0700 Subject: [PATCH 34/72] docs: add api spec for get video by category content and trash category --- docs/swagger.yml | 254 ++++++++++++++++++++++++++++++++++++++- internal/server/route.go | 2 +- 2 files changed, 254 insertions(+), 2 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index b430f61..19fbf40 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -3559,7 +3559,7 @@ paths: message: type: string example: internal server error - /videos/{videoId}: + /video/{videoId}: get: tags: - user videos @@ -3681,6 +3681,258 @@ paths: message: type: string example: video not found + /videos/content-category: + get: + tags: + - user videos + summary: Get all video by content category + description: Enpoint for user get all video by content categories + operationId: getContentCategories + security: + - Bearer: [] + parameters: + - name: query + in: query + required: true + schema: + type: string + example: "tips" + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 200 + message: + type: string + example: success + data: + type: array + items: + type: object + properties: + data_video: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + title: + type: string + example: "Video Title" + description: + type: string + example: "Video Description" + url_thumbnail: + type: string + example: "https://example.com/thumbnail.jpg" + link_video: + type: string + example: "https://example.com/video.mp4" + viewer: + type: integer + example: 1000 + video_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "tips" + trash_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "berbahaya" + '404': + description: Content category not found + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 404 + message: + type: string + example: content category not found + + + /videos/trash-category: + get: + tags: + - user videos + summary: Get all video by trash category + description: Enpoint for user get all video by trash categories + operationId: getTrashCategories + security: + - Bearer: [] + parameters: + - name: query + in: query + required: true + schema: + type: string + example: "berbahaya" + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 200 + message: + type: string + data: + type: array + items: + type: object + properties: + data_video: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + title: + type: string + example: "Video Title" + description: + type: string + example: "Video Description" + url_thumbnail: + type: string + example: "https://example.com/thumbnail.jpg" + link_video: + type: string + example: "https://example.com/video.mp4" + viewer: + type: integer + example: 1000 + video_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "tips" + trash_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "berbahaya" + '404': + description: Trash category not found + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 404 + message: + type: string + example: trash category not found + + /videos/content-categories: + get: + tags: + - user videos + summary: Get all content categories + description: Enpoint for user get all content categories + operationId: getContentCategories + security: + - Bearer: [] + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 200 + message: + type: string + example: success + data: + type: array + items: + type: object + properties: + name: + type: string + example: "tips" + + /videos/trash-categories: + get: + tags: + - user videos + summary: Get all trash categories + description: Enpoint for user get all trash categories + operationId: getTrashCategories + security: + - Bearer: [] + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 200 + message: + type: string + example: success + data: + type: array + items: + type: object + properties: + name: + type: string + example: "berbahaya" + /approval-tasks: get: tags: diff --git a/internal/server/route.go b/internal/server/route.go index e98e642..002094f 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -358,7 +358,7 @@ func (s *echoServer) userVideo() { s.gr.GET("/videos/trash-category", handler.SearchVideoByTrashCategoryVideoHandler, UserMiddleware) // get video detail - s.gr.GET("/videos/:videoId", handler.GetVideoDetailHandler, UserMiddleware) + s.gr.GET("/video/:videoId", handler.GetVideoDetailHandler, UserMiddleware) // add comment s.gr.POST("/videos/comment", handler.AddCommentHandler, UserMiddleware) From 910dd93d18c392cd285c7521ac5a684a60238f45 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Jun 2024 00:24:20 +0700 Subject: [PATCH 35/72] refactor(search video by titile): change to search by title,description, name category content, and waste category --- internal/server/route.go | 2 +- .../user_video/dto/user_video_response.go | 8 +++- .../user_video/handler/user_video_handler.go | 2 +- .../handler/user_video_handler_impl.go | 47 ++++++++++++------- .../repository/user_video_repository.go | 2 +- .../repository/user_video_repository_impl.go | 25 ++++++++-- .../user_video/usecase/user_video_usecase.go | 2 +- .../usecase/user_video_usecase_impl.go | 4 +- 8 files changed, 64 insertions(+), 28 deletions(-) diff --git a/internal/server/route.go b/internal/server/route.go index 002094f..9bca63d 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -349,7 +349,7 @@ func (s *echoServer) userVideo() { s.gr.GET("/videos", handler.GetAllVideoHandler, UserMiddleware) // search video by title - s.gr.GET("/videos/search", handler.SearchVideoByTitleHandler, UserMiddleware) + s.gr.GET("/videos/search", handler.SearchVideoByKeywordHandler, UserMiddleware) // search video by category video s.gr.GET("/videos/content-category", handler.SearchVideoByCategoryVideoHandler, UserMiddleware) diff --git a/internal/video/user_video/dto/user_video_response.go b/internal/video/user_video/dto/user_video_response.go index 3d02eed..090acc6 100644 --- a/internal/video/user_video/dto/user_video_response.go +++ b/internal/video/user_video/dto/user_video_response.go @@ -11,6 +11,10 @@ type DataVideo struct { Viewer int `json:"viewer"` } +type SearchVideoByKeywordResponse struct { + DataVideo *[]DataVideoSearchByCategory `json:"data_video"` +} + type GetAllVideoResponse struct { DataVideo []DataVideo `json:"data_video"` } @@ -49,6 +53,6 @@ type DataVideoSearchByCategory struct { UrlThumbnail string `json:"url_thumbnail"` LinkVideo string `json:"link_video"` Viewer int `json:"viewer"` - VideoCategory []*DataCategoryVideo `json:"video_categories"` - TrashCategory []*DataTrashCategoryVideo `json:"trash_categories"` + VideoCategory []*DataCategoryVideo `json:"content_categories"` + TrashCategory []*DataTrashCategoryVideo `json:"waste_categories"` } diff --git a/internal/video/user_video/handler/user_video_handler.go b/internal/video/user_video/handler/user_video_handler.go index a4197ce..1161c27 100644 --- a/internal/video/user_video/handler/user_video_handler.go +++ b/internal/video/user_video/handler/user_video_handler.go @@ -4,7 +4,7 @@ import "github.com/labstack/echo/v4" type UserVideoHandler interface { GetAllVideoHandler(c echo.Context) error - SearchVideoByTitleHandler(c echo.Context) error + SearchVideoByKeywordHandler(c echo.Context) error SearchVideoByCategoryVideoHandler(c echo.Context) error SearchVideoByTrashCategoryVideoHandler(c echo.Context) error GetVideoDetailHandler(c echo.Context) error diff --git a/internal/video/user_video/handler/user_video_handler_impl.go b/internal/video/user_video/handler/user_video_handler_impl.go index a96e7fa..ef80f28 100644 --- a/internal/video/user_video/handler/user_video_handler_impl.go +++ b/internal/video/user_video/handler/user_video_handler_impl.go @@ -46,28 +46,43 @@ func (handler *UserVideoHandlerImpl) GetAllVideoHandler(c echo.Context) error { return c.JSON(http.StatusOK, responseData) } -func (handler *UserVideoHandlerImpl) SearchVideoByTitleHandler(c echo.Context) error { - title := c.QueryParam("title") - videos, err := handler.Usecase.SearchVideoByTitleUsecase(title) +func (handler *UserVideoHandlerImpl) SearchVideoByKeywordHandler(c echo.Context) error { + keyword := c.QueryParam("keyword") + videos, err := handler.Usecase.SearchVideoByKeywordUsecase(keyword) if err != nil { return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) } - var dataVideo []dto.DataVideo - data := dto.GetAllVideoResponse{ - DataVideo: dataVideo, - } + var dataVideo []*dto.DataVideoSearchByCategory for _, video := range *videos { - dataVideo = append(dataVideo, dto.DataVideo{ - Id: video.ID, - Title: video.Title, - Description: video.Description, - UrlThumbnail: video.Thumbnail, - LinkVideo: video.Link, - Viewer: video.Viewer, + videoCategories := make([]*dto.DataCategoryVideo, len(video.VideoCategories)) + for i, vc := range video.VideoCategories { + videoCategories[i] = &dto.DataCategoryVideo{ + Id: vc.ID, + Name: vc.Name, + } + } + + trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.TrashCategories)) + for i, tc := range video.TrashCategories { + trashCategories[i] = &dto.DataTrashCategoryVideo{ + Id: tc.ID, + Name: tc.Name, + } + } + + dataVideo = append(dataVideo, &dto.DataVideoSearchByCategory{ + Id: video.ID, + Title: video.Title, + Description: video.Description, + UrlThumbnail: video.Thumbnail, + LinkVideo: video.Link, + Viewer: video.Viewer, + VideoCategory: videoCategories, + TrashCategory: trashCategories, }) } - data.DataVideo = dataVideo - responseData := helper.ResponseData(http.StatusOK, "success", data.DataVideo) + + responseData := helper.ResponseData(http.StatusOK, "success", dataVideo) return c.JSON(http.StatusOK, responseData) } diff --git a/internal/video/user_video/repository/user_video_repository.go b/internal/video/user_video/repository/user_video_repository.go index bab0e21..1a76d0e 100644 --- a/internal/video/user_video/repository/user_video_repository.go +++ b/internal/video/user_video/repository/user_video_repository.go @@ -6,7 +6,7 @@ import ( type UserVideoRepository interface { GetAllVideo() (*[]video.Video, error) - SearchVideoByTitle(title string) (*[]video.Video, error) + SearchVideoByKeyword(keyword string) (*[]video.Video, error) SearchVideoByCategoryVideo(categoryVideo string) (*[]video.Video, error) SearchVideoByTrashCategoryVideo(trashCategory string) (*[]video.Video, error) GetVideoDetail(id int) (*video.Video, *[]video.Comment, error) diff --git a/internal/video/user_video/repository/user_video_repository_impl.go b/internal/video/user_video/repository/user_video_repository_impl.go index 90b5d2e..8f6bfb5 100644 --- a/internal/video/user_video/repository/user_video_repository_impl.go +++ b/internal/video/user_video/repository/user_video_repository_impl.go @@ -24,12 +24,29 @@ func (repository *UserVideoRepositoryImpl) GetAllVideo() (*[]video.Video, error) return &videos, nil } -func (repository *UserVideoRepositoryImpl) SearchVideoByTitle(title string) (*[]video.Video, error) { - var video []video.Video - if err := repository.DB.GetDB().Where("title LIKE ?", "%"+title+"%").Find(&video).Error; err != nil { +func (repository *UserVideoRepositoryImpl) SearchVideoByKeyword(keyword string) (*[]video.Video, error) { + var videos []video.Video + if err := repository.DB.GetDB(). + Order("created_at desc"). + Preload("VideoCategories"). + Preload("TrashCategories"). + Joins("JOIN video_categories ON video_categories.video_id = videos.id"). + Joins("JOIN trash_categories ON trash_categories.video_id = videos.id"). + Where("videos.title LIKE ? OR videos.description LIKE ? OR trash_categories.name LIKE ? OR video_categories.name LIKE ?", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%"). + Find(&videos).Error; err != nil { return nil, err } - return &video, nil + videoMap := make(map[int]video.Video) + for _, v := range videos { + videoMap[v.ID] = v + } + + uniqueVideos := make([]video.Video, 0, len(videoMap)) + for _, v := range videoMap { + uniqueVideos = append(uniqueVideos, v) + } + + return &uniqueVideos, nil } func (repository *UserVideoRepositoryImpl) SearchVideoByCategoryVideo(categoryVideo string) (*[]video.Video, error) { diff --git a/internal/video/user_video/usecase/user_video_usecase.go b/internal/video/user_video/usecase/user_video_usecase.go index 9b821a3..2e40649 100644 --- a/internal/video/user_video/usecase/user_video_usecase.go +++ b/internal/video/user_video/usecase/user_video_usecase.go @@ -7,7 +7,7 @@ import ( type UserVideoUsecase interface { GetAllVideoUsecase() (*[]video.Video, error) - SearchVideoByTitleUsecase(title string) (*[]video.Video, error) + SearchVideoByKeywordUsecase(keyword string) (*[]video.Video, error) SearchVideoByCategoryVideoUsecase(categoryVideo string) (*[]video.Video, error) SearchVideoByTrashCategoryVideoUsecase(trashCategory string) (*[]video.Video, error) GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, error) diff --git a/internal/video/user_video/usecase/user_video_usecase_impl.go b/internal/video/user_video/usecase/user_video_usecase_impl.go index bdbef04..09a1d63 100644 --- a/internal/video/user_video/usecase/user_video_usecase_impl.go +++ b/internal/video/user_video/usecase/user_video_usecase_impl.go @@ -46,8 +46,8 @@ func (usecase *UserVideoUsecaseImpl) GetAllVideoUsecase() (*[]video.Video, error return updatedVideos, nil } -func (usecase *UserVideoUsecaseImpl) SearchVideoByTitleUsecase(title string) (*[]video.Video, error) { - videos, err := usecase.Repository.SearchVideoByTitle(title) +func (usecase *UserVideoUsecaseImpl) SearchVideoByKeywordUsecase(keyword string) (*[]video.Video, error) { + videos, err := usecase.Repository.SearchVideoByKeyword(keyword) if err != nil { return nil, err } From 044733a1816aa92db3021e9d812b31f410d71c71 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Jun 2024 00:59:12 +0700 Subject: [PATCH 36/72] refactor(search by category): change search to one enpoint --- internal/server/route.go | 7 +-- .../user_video/handler/user_video_handler.go | 3 +- .../handler/user_video_handler_impl.go | 52 ++--------------- .../repository/user_video_repository.go | 3 +- .../repository/user_video_repository_impl.go | 58 ++++++++----------- .../user_video/usecase/user_video_usecase.go | 3 +- .../usecase/user_video_usecase_impl.go | 18 +----- 7 files changed, 36 insertions(+), 108 deletions(-) diff --git a/internal/server/route.go b/internal/server/route.go index 9bca63d..42ba98c 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -351,11 +351,8 @@ func (s *echoServer) userVideo() { // search video by title s.gr.GET("/videos/search", handler.SearchVideoByKeywordHandler, UserMiddleware) - // search video by category video - s.gr.GET("/videos/content-category", handler.SearchVideoByCategoryVideoHandler, UserMiddleware) - - // search video by trash category video - s.gr.GET("/videos/trash-category", handler.SearchVideoByTrashCategoryVideoHandler, UserMiddleware) + // search video by category + s.gr.GET("/videos/categories", handler.SearchVideoByCategoryHandler, UserMiddleware) // get video detail s.gr.GET("/video/:videoId", handler.GetVideoDetailHandler, UserMiddleware) diff --git a/internal/video/user_video/handler/user_video_handler.go b/internal/video/user_video/handler/user_video_handler.go index 1161c27..1ad51ca 100644 --- a/internal/video/user_video/handler/user_video_handler.go +++ b/internal/video/user_video/handler/user_video_handler.go @@ -5,8 +5,7 @@ import "github.com/labstack/echo/v4" type UserVideoHandler interface { GetAllVideoHandler(c echo.Context) error SearchVideoByKeywordHandler(c echo.Context) error - SearchVideoByCategoryVideoHandler(c echo.Context) error - SearchVideoByTrashCategoryVideoHandler(c echo.Context) error + SearchVideoByCategoryHandler(c echo.Context) error GetVideoDetailHandler(c echo.Context) error AddCommentHandler(c echo.Context) error } diff --git a/internal/video/user_video/handler/user_video_handler_impl.go b/internal/video/user_video/handler/user_video_handler_impl.go index ef80f28..ca97255 100644 --- a/internal/video/user_video/handler/user_video_handler_impl.go +++ b/internal/video/user_video/handler/user_video_handler_impl.go @@ -87,54 +87,10 @@ func (handler *UserVideoHandlerImpl) SearchVideoByKeywordHandler(c echo.Context) return c.JSON(http.StatusOK, responseData) } -func (handler *UserVideoHandlerImpl) SearchVideoByCategoryVideoHandler(c echo.Context) error { - categoryVideo := c.QueryParam("query") - videos, err := handler.Usecase.SearchVideoByCategoryVideoUsecase(categoryVideo) - if err != nil { - if errors.Is(err, pkg.ErrVideoNotFound) { - return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrVideoNotFound.Error()) - } - return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) - } - var dataVideo []*dto.DataVideoSearchByCategory - for _, video := range *videos { - videoCategories := make([]*dto.DataCategoryVideo, len(video.VideoCategories)) - for i, vc := range video.VideoCategories { - videoCategories[i] = &dto.DataCategoryVideo{ - Id: vc.ID, - Name: vc.Name, - } - } - - trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.TrashCategories)) - for i, tc := range video.TrashCategories { - trashCategories[i] = &dto.DataTrashCategoryVideo{ - Id: tc.ID, - Name: tc.Name, - } - } - - dataVideo = append(dataVideo, &dto.DataVideoSearchByCategory{ - Id: video.ID, - Title: video.Title, - Description: video.Description, - UrlThumbnail: video.Thumbnail, - LinkVideo: video.Link, - Viewer: video.Viewer, - VideoCategory: videoCategories, - TrashCategory: trashCategories, - }) - } - - responseData := dto.SearchVideoByCategoryVideoResponse{ - DataVideo: dataVideo, - } - return c.JSON(http.StatusOK, helper.ResponseData(http.StatusOK, "success", responseData)) -} - -func (handler *UserVideoHandlerImpl) SearchVideoByTrashCategoryVideoHandler(c echo.Context) error { - trashCategory := c.QueryParam("query") - videos, err := handler.Usecase.SearchVideoByTrashCategoryVideoUsecase(trashCategory) +func (handler *UserVideoHandlerImpl) SearchVideoByCategoryHandler(c echo.Context) error { + categoryType := c.QueryParam("type") + categoryName := c.QueryParam("name") + videos, err := handler.Usecase.SearchVideoByCategoryUsecase(categoryType, categoryName) if err != nil { if errors.Is(err, pkg.ErrVideoNotFound) { return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrVideoNotFound.Error()) diff --git a/internal/video/user_video/repository/user_video_repository.go b/internal/video/user_video/repository/user_video_repository.go index 1a76d0e..1d3e638 100644 --- a/internal/video/user_video/repository/user_video_repository.go +++ b/internal/video/user_video/repository/user_video_repository.go @@ -7,8 +7,7 @@ import ( type UserVideoRepository interface { GetAllVideo() (*[]video.Video, error) SearchVideoByKeyword(keyword string) (*[]video.Video, error) - SearchVideoByCategoryVideo(categoryVideo string) (*[]video.Video, error) - SearchVideoByTrashCategoryVideo(trashCategory string) (*[]video.Video, error) + SearchVideoByCategory(categoryType string, name string) (*[]video.Video, error) GetVideoDetail(id int) (*video.Video, *[]video.Comment, error) AddComment(comment *video.Comment) error UpdateViewer(view int, id int) error diff --git a/internal/video/user_video/repository/user_video_repository_impl.go b/internal/video/user_video/repository/user_video_repository_impl.go index 8f6bfb5..596d16d 100644 --- a/internal/video/user_video/repository/user_video_repository_impl.go +++ b/internal/video/user_video/repository/user_video_repository_impl.go @@ -3,6 +3,7 @@ package repository import ( "github.com/sawalreverr/recything/internal/database" video "github.com/sawalreverr/recything/internal/video/manage_video/entity" + "github.com/sawalreverr/recything/pkg" ) type UserVideoRepositoryImpl struct { @@ -49,16 +50,31 @@ func (repository *UserVideoRepositoryImpl) SearchVideoByKeyword(keyword string) return &uniqueVideos, nil } -func (repository *UserVideoRepositoryImpl) SearchVideoByCategoryVideo(categoryVideo string) (*[]video.Video, error) { +func (repository *UserVideoRepositoryImpl) SearchVideoByCategory(categoryType string, name string) (*[]video.Video, error) { var videos []video.Video - if err := repository.DB.GetDB(). - Order("created_at desc"). - Joins("JOIN video_categories ON video_categories.video_id = videos.id"). - Where("video_categories.name LIKE ?", "%"+categoryVideo+"%"). - Preload("VideoCategories"). - Preload("TrashCategories"). - Find(&videos).Error; err != nil { - return nil, err + if categoryType == "content" { + if err := repository.DB.GetDB(). + Order("created_at desc"). + Joins("JOIN video_categories ON video_categories.video_id = videos.id"). + Where("video_categories.name LIKE ?", "%"+name+"%"). + Preload("VideoCategories"). + Preload("TrashCategories"). + Find(&videos).Error; err != nil { + return nil, err + } + + } else if categoryType == "waste" { + if err := repository.DB.GetDB(). + Order("created_at desc"). + Joins("JOIN trash_categories ON trash_categories.video_id = videos.id"). + Where("trash_categories.name LIKE ?", "%"+name+"%"). + Preload("VideoCategories"). + Preload("TrashCategories"). + Find(&videos).Error; err != nil { + return nil, err + } + } else { + return nil, pkg.ErrVideoNotFound } videoMap := make(map[int]video.Video) @@ -74,30 +90,6 @@ func (repository *UserVideoRepositoryImpl) SearchVideoByCategoryVideo(categoryVi return &uniqueVideos, nil } -func (repository *UserVideoRepositoryImpl) SearchVideoByTrashCategoryVideo(trashCategory string) (*[]video.Video, error) { - var videos []video.Video - if err := repository.DB.GetDB(). - Order("created_at desc"). - Joins("JOIN trash_categories ON trash_categories.video_id = videos.id"). - Where("trash_categories.name LIKE ?", "%"+trashCategory+"%"). - Preload("VideoCategories"). - Preload("TrashCategories"). - Find(&videos).Error; err != nil { - return nil, err - } - videoMap := make(map[int]video.Video) - for _, v := range videos { - videoMap[v.ID] = v - } - - uniqueVideos := make([]video.Video, 0, len(videoMap)) - for _, v := range videoMap { - uniqueVideos = append(uniqueVideos, v) - } - - return &uniqueVideos, nil -} - func (repository *UserVideoRepositoryImpl) GetVideoDetail(id int) (*video.Video, *[]video.Comment, error) { var videos video.Video var comments []video.Comment diff --git a/internal/video/user_video/usecase/user_video_usecase.go b/internal/video/user_video/usecase/user_video_usecase.go index 2e40649..7d893f2 100644 --- a/internal/video/user_video/usecase/user_video_usecase.go +++ b/internal/video/user_video/usecase/user_video_usecase.go @@ -8,8 +8,7 @@ import ( type UserVideoUsecase interface { GetAllVideoUsecase() (*[]video.Video, error) SearchVideoByKeywordUsecase(keyword string) (*[]video.Video, error) - SearchVideoByCategoryVideoUsecase(categoryVideo string) (*[]video.Video, error) - SearchVideoByTrashCategoryVideoUsecase(trashCategory string) (*[]video.Video, error) + SearchVideoByCategoryUsecase(categoryType string, name string) (*[]video.Video, error) GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, error) AddCommentUsecase(request *dto.AddCommentRequest, userId string) error } diff --git a/internal/video/user_video/usecase/user_video_usecase_impl.go b/internal/video/user_video/usecase/user_video_usecase_impl.go index 09a1d63..0868da1 100644 --- a/internal/video/user_video/usecase/user_video_usecase_impl.go +++ b/internal/video/user_video/usecase/user_video_usecase_impl.go @@ -57,22 +57,8 @@ func (usecase *UserVideoUsecaseImpl) SearchVideoByKeywordUsecase(keyword string) return videos, nil } -func (usecase *UserVideoUsecaseImpl) SearchVideoByCategoryVideoUsecase(categoryVideo string) (*[]video.Video, error) { - videos, err := usecase.Repository.SearchVideoByCategoryVideo(categoryVideo) - if err != nil { - if err == gorm.ErrRecordNotFound { - return nil, pkg.ErrVideoNotFound - } - return nil, err - } - if len(*videos) == 0 { - return nil, pkg.ErrVideoNotFound - } - return videos, nil -} - -func (usecase *UserVideoUsecaseImpl) SearchVideoByTrashCategoryVideoUsecase(trashCategory string) (*[]video.Video, error) { - videos, err := usecase.Repository.SearchVideoByTrashCategoryVideo(trashCategory) +func (usecase *UserVideoUsecaseImpl) SearchVideoByCategoryUsecase(categoryType string, name string) (*[]video.Video, error) { + videos, err := usecase.Repository.SearchVideoByCategory(categoryType, name) if err != nil { if err == gorm.ErrRecordNotFound { return nil, pkg.ErrVideoNotFound From f1b490f2ebb67c3e340ed5c3c8c579cc085e35c0 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Jun 2024 01:05:24 +0700 Subject: [PATCH 37/72] refactor(get all video): change response body and add some code on repository --- .../handler/user_video_handler_impl.go | 47 ++++++++++++------- .../repository/user_video_repository_impl.go | 2 + 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/internal/video/user_video/handler/user_video_handler_impl.go b/internal/video/user_video/handler/user_video_handler_impl.go index ca97255..70a9dbd 100644 --- a/internal/video/user_video/handler/user_video_handler_impl.go +++ b/internal/video/user_video/handler/user_video_handler_impl.go @@ -25,25 +25,40 @@ func (handler *UserVideoHandlerImpl) GetAllVideoHandler(c echo.Context) error { if err != nil { return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) } - var dataVideo []dto.DataVideo - data := dto.GetAllVideoResponse{ - DataVideo: dataVideo, - } - + var dataVideo []*dto.DataVideoSearchByCategory for _, video := range *videos { - dataVideo = append(dataVideo, dto.DataVideo{ - Id: video.ID, - Title: video.Title, - Description: video.Description, - UrlThumbnail: video.Thumbnail, - LinkVideo: video.Link, - Viewer: video.Viewer, + videoCategories := make([]*dto.DataCategoryVideo, len(video.VideoCategories)) + for i, vc := range video.VideoCategories { + videoCategories[i] = &dto.DataCategoryVideo{ + Id: vc.ID, + Name: vc.Name, + } + } + + trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.TrashCategories)) + for i, tc := range video.TrashCategories { + trashCategories[i] = &dto.DataTrashCategoryVideo{ + Id: tc.ID, + Name: tc.Name, + } + } + + dataVideo = append(dataVideo, &dto.DataVideoSearchByCategory{ + Id: video.ID, + Title: video.Title, + Description: video.Description, + UrlThumbnail: video.Thumbnail, + LinkVideo: video.Link, + Viewer: video.Viewer, + VideoCategory: videoCategories, + TrashCategory: trashCategories, }) } - data.DataVideo = dataVideo - responseData := helper.ResponseData(http.StatusOK, "success get all video", data.DataVideo) - return c.JSON(http.StatusOK, responseData) + responseData := dto.SearchVideoByCategoryVideoResponse{ + DataVideo: dataVideo, + } + return c.JSON(http.StatusOK, helper.ResponseData(http.StatusOK, "success", responseData.DataVideo)) } func (handler *UserVideoHandlerImpl) SearchVideoByKeywordHandler(c echo.Context) error { @@ -130,7 +145,7 @@ func (handler *UserVideoHandlerImpl) SearchVideoByCategoryHandler(c echo.Context responseData := dto.SearchVideoByCategoryVideoResponse{ DataVideo: dataVideo, } - return c.JSON(http.StatusOK, helper.ResponseData(http.StatusOK, "success", responseData)) + return c.JSON(http.StatusOK, helper.ResponseData(http.StatusOK, "success", responseData.DataVideo)) } func (handler *UserVideoHandlerImpl) GetVideoDetailHandler(c echo.Context) error { diff --git a/internal/video/user_video/repository/user_video_repository_impl.go b/internal/video/user_video/repository/user_video_repository_impl.go index 596d16d..b3ba6b7 100644 --- a/internal/video/user_video/repository/user_video_repository_impl.go +++ b/internal/video/user_video/repository/user_video_repository_impl.go @@ -18,6 +18,8 @@ func (repository *UserVideoRepositoryImpl) GetAllVideo() (*[]video.Video, error) var videos []video.Video if err := repository.DB.GetDB(). Order("created_at desc"). + Preload("VideoCategories"). + Preload("TrashCategories"). Find(&videos). Error; err != nil { return nil, err From 3fd720c4ab841f5847d0ff333de598e5cea193a8 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Jun 2024 01:59:55 +0700 Subject: [PATCH 38/72] docs: change some request body and response body --- docs/swagger.yml | 200 +++++++----------- internal/server/route.go | 2 +- .../manage_video/dto/manage_video_request.go | 8 +- .../manage_video/dto/manage_video_response.go | 8 +- 4 files changed, 88 insertions(+), 130 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 19fbf40..d2c49b7 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -3042,7 +3042,7 @@ paths: items: type: object properties: - video_categories: + content_categories: type: array items: type: object @@ -3050,7 +3050,7 @@ paths: name: type: string example: "tips" - trash_categories: + waste_categories: type: array items: type: object @@ -3101,7 +3101,7 @@ paths: link_video: type: string example: "https://example.com/video.mp4" - video_categories: + content_categories: type: array items: type: object @@ -3109,7 +3109,7 @@ paths: name: type: string example: "tips" - trash_categories: + waste_categories: type: array items: type: object @@ -3286,7 +3286,7 @@ paths: viewer: type: integer example: 1000 - video_categories: + content_categories: type: array items: type: object @@ -3294,7 +3294,7 @@ paths: name: type: string example: "tips" - trash_categories: + waste_categories:: type: array items: type: object @@ -3351,7 +3351,7 @@ paths: link_video: type: string example: "https://example.com/video.mp4" - video_categories: + content_categories: type: array items: type: object @@ -3359,7 +3359,7 @@ paths: name: type: string example: "tips" - trash_categories: + waste_categories: type: array items: type: object @@ -3490,6 +3490,28 @@ paths: viewer: type: integer example: 1000 + content_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "tips" + waste_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "berbahaya" '500': description: Status internal error' content: @@ -3507,20 +3529,20 @@ paths: get: tags: - user videos - summary: Search Videos by title - description: Searches for videos that match a specific title - operationId: getVideosBytitle + summary: Search Videos by keyword + description: Searches for videos that match a specific keyword + operationId: getVideosBykeyword parameters: - in: query - name: title + name: keyword required: true schema: type: string example: plastik - description: The title to search for videos. + description: The keyword to search for videos. responses: '200': - description: Get all data by title + description: Get all data by keyword content: application/json: schema: @@ -3546,8 +3568,30 @@ paths: viewer: type: integer example: 1000 - '500': - description: Status internal error' + content_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "tips" + waste_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "plastik" + '404': + description: Video not found content: application/json: schema: @@ -3555,10 +3599,12 @@ paths: properties: code: type: integer - example: 500 + example: 404 message: type: string - example: internal server error + example: video not found + + /video/{videoId}: get: tags: @@ -3681,17 +3727,23 @@ paths: message: type: string example: video not found - /videos/content-category: + /videos/category: get: tags: - user videos - summary: Get all video by content category - description: Enpoint for user get all video by content categories + summary: Get all video by content category or waste category + description: Enpoint for user get all video by content category or waste category operationId: getContentCategories security: - Bearer: [] parameters: - - name: query + - name: type + in: query + required: true + schema: + type: string + example: "content" + - name: name in: query required: true schema: @@ -3739,7 +3791,7 @@ paths: viewer: type: integer example: 1000 - video_categories: + content_categories: type: array items: type: object @@ -3750,7 +3802,7 @@ paths: name: type: string example: "tips" - trash_categories: + waste_categories: type: array items: type: object @@ -3762,7 +3814,7 @@ paths: type: string example: "berbahaya" '404': - description: Content category not found + description: video not found content: application/json: schema: @@ -3773,102 +3825,8 @@ paths: example: 404 message: type: string - example: content category not found + example: video not found - - /videos/trash-category: - get: - tags: - - user videos - summary: Get all video by trash category - description: Enpoint for user get all video by trash categories - operationId: getTrashCategories - security: - - Bearer: [] - parameters: - - name: query - in: query - required: true - schema: - type: string - example: "berbahaya" - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: - code: - type: integer - example: 200 - message: - type: string - data: - type: array - items: - type: object - properties: - data_video: - type: array - items: - type: object - properties: - id: - type: integer - example: 1 - title: - type: string - example: "Video Title" - description: - type: string - example: "Video Description" - url_thumbnail: - type: string - example: "https://example.com/thumbnail.jpg" - link_video: - type: string - example: "https://example.com/video.mp4" - viewer: - type: integer - example: 1000 - video_categories: - type: array - items: - type: object - properties: - id: - type: integer - example: 1 - name: - type: string - example: "tips" - trash_categories: - type: array - items: - type: object - properties: - id: - type: integer - example: 1 - name: - type: string - example: "berbahaya" - '404': - description: Trash category not found - content: - application/json: - schema: - type: object - properties: - code: - type: integer - example: 404 - message: - type: string - example: trash category not found - /videos/content-categories: get: tags: @@ -3901,7 +3859,7 @@ paths: type: string example: "tips" - /videos/trash-categories: + /videos/waste-categories: get: tags: - user videos diff --git a/internal/server/route.go b/internal/server/route.go index 42ba98c..9b6e51a 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -352,7 +352,7 @@ func (s *echoServer) userVideo() { s.gr.GET("/videos/search", handler.SearchVideoByKeywordHandler, UserMiddleware) // search video by category - s.gr.GET("/videos/categories", handler.SearchVideoByCategoryHandler, UserMiddleware) + s.gr.GET("/videos/category", handler.SearchVideoByCategoryHandler, UserMiddleware) // get video detail s.gr.GET("/video/:videoId", handler.GetVideoDetailHandler, UserMiddleware) diff --git a/internal/video/manage_video/dto/manage_video_request.go b/internal/video/manage_video/dto/manage_video_request.go index fa1a90d..f21bbc3 100644 --- a/internal/video/manage_video/dto/manage_video_request.go +++ b/internal/video/manage_video/dto/manage_video_request.go @@ -6,8 +6,8 @@ type CreateDataVideoRequest struct { Title string `json:"title" validate:"required"` Description string `json:"description" validate:"required"` LinkVideo string `json:"link_video" validate:"required"` - VideoCategories []DataCategoryVideo `json:"video_categories"` - TrashCategories []DataTrashCategory `json:"trash_categories"` + VideoCategories []DataCategoryVideo `json:"content_categories" validate:"required"` + TrashCategories []DataTrashCategory `json:"waste_categories" validate:"required"` Thumbnail *multipart.FileHeader `json:"-"` } @@ -27,7 +27,7 @@ type UpdateDataVideoRequest struct { Title string `json:"title"` Description string `json:"description"` LinkVideo string `json:"link_video"` - VideoCategories []DataCategoryVideo `json:"video_categories"` - TrashCategories []DataTrashCategory `json:"trash_categories"` + VideoCategories []DataCategoryVideo `json:"content_categories"` + TrashCategories []DataTrashCategory `json:"waste_categories"` Thumbnail *multipart.FileHeader `json:"-"` } diff --git a/internal/video/manage_video/dto/manage_video_response.go b/internal/video/manage_video/dto/manage_video_response.go index d047ece..3e3b6ea 100644 --- a/internal/video/manage_video/dto/manage_video_response.go +++ b/internal/video/manage_video/dto/manage_video_response.go @@ -1,8 +1,8 @@ package dto type GetAllCategoryVideoResponse struct { - VideoCategory []*DataCategoryVideo `json:"video_categories"` - TrashCategory []*DataTrashCategory `json:"trash_categories"` + VideoCategory []*DataCategoryVideo `json:"content_categories"` + TrashCategory []*DataTrashCategory `json:"waste_categories"` } type DataVideoCategory struct { @@ -39,6 +39,6 @@ type GetDetailsDataVideoByIdResponse struct { UrlThumbnail string `json:"url_thumbnail"` LinkVideo string `json:"link_video"` Viewer int `json:"viewer"` - VideoCategory []*DataVideoCategory `json:"video_categories"` - TrashCategory []*DataTrashCategoryResponse `json:"trash_categories"` + VideoCategory []*DataVideoCategory `json:"content_categories"` + TrashCategory []*DataTrashCategoryResponse `json:"waste_categories"` } From 1f2548496fc5aada280fed84f86b8302441c7050 Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Wed, 12 Jun 2024 02:40:12 +0700 Subject: [PATCH 39/72] feat(article): update article: add user comment --- internal/article/dto.go | 23 +++++++- internal/article/entity.go | 24 ++++++++ internal/article/handler/handler.go | 37 +++++++++++- internal/article/repository/repository.go | 25 ++++++-- internal/article/usecase/usecase.go | 72 ++++++++++++++++++++++- internal/database/migrate.go | 1 + internal/server/route.go | 6 +- 7 files changed, 175 insertions(+), 13 deletions(-) diff --git a/internal/article/dto.go b/internal/article/dto.go index 8fca1ad..5e41cab 100644 --- a/internal/article/dto.go +++ b/internal/article/dto.go @@ -26,9 +26,15 @@ type AdminDetail struct { ImageURL string `json:"image_url"` } +type UserDetail struct { + ID string `json:"id"` + Name string `json:"name"` + ImageURL string `json:"image_url"` +} + type ArticleDetail struct { ID string `json:"id"` - AuthorID AdminDetail `json:"author"` + Author AdminDetail `json:"author"` Title string `json:"title"` Description string `json:"description"` ThumbnailURL string `json:"thumbnail_url"` @@ -37,6 +43,21 @@ type ArticleDetail struct { WasteCategories []WasteCategory `json:"waste_categories"` ContentCategories []VideoCategory `json:"content_categories"` Sections []ArticleSection `json:"sections"` + Comments []CommentDetail `json:"comments"` +} + +type CommentInput struct { + UserID string `json:"user_id"` + ArticleID string `json:"article_id"` + Comment string `json:"comment"` +} + +type CommentDetail struct { + ID uint `json:"id"` + User UserDetail `json:"user"` + ArticleID string `json:"article_id"` + Comment string `json:"comment"` + CreatedAt time.Time `json:"created_at"` } type ArticleResponsePagination struct { diff --git a/internal/article/entity.go b/internal/article/entity.go index 6bf6858..6fa348e 100644 --- a/internal/article/entity.go +++ b/internal/article/entity.go @@ -16,6 +16,7 @@ type Article struct { Categories []ArticleCategories Sections []ArticleSection + Comments []ArticleComment CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` @@ -63,6 +64,17 @@ type ArticleSection struct { DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` } +type ArticleComment struct { + ID uint `json:"-" gorm:"primaryKey"` + UserID string `json:"-"` + ArticleID string `json:"-"` + Comment string `json:"comment" gorm:"type:text"` + + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +} + type ArticleRepository interface { // Article Repository Create(article Article) (*Article, error) @@ -88,9 +100,14 @@ type ArticleRepository interface { CreateArticleCategory(categories ArticleCategories) error UpdateArticleCategory(categories ArticleCategories) error DeleteAllArticleCategory(articleID string) error + + // Article Comment Repository + CreateArticleComment(comment ArticleComment) error + DeleteAllArticleComment(articleID string) error } type ArticleUsecase interface { + // Article Usecase NewArticle(article ArticleInput, authorId string) (*ArticleDetail, error) GetArticleByID(articleID string) (*ArticleDetail, error) GetAllArticle(page, limit int, sortBy string, sortType string) (*ArticleResponsePagination, error) @@ -101,6 +118,11 @@ type ArticleUsecase interface { GetArticleDetail(article Article) *ArticleDetail GetDetailAuthor(authorID string) (*AdminDetail, error) + + // Article Comment Usecase + NewArticleComment(comment CommentInput) error + GetDetailUser(userID string) (*UserDetail, error) + GetDetailComments(comments []ArticleComment) (*[]CommentDetail, error) } type ArticleHandler interface { @@ -111,4 +133,6 @@ type ArticleHandler interface { GetArticleByKeyword(c echo.Context) error GetArticleByCategory(c echo.Context) error GetArticleByID(c echo.Context) error + + NewArticleComment(c echo.Context) error } diff --git a/internal/article/handler/handler.go b/internal/article/handler/handler.go index c4c7cc8..358015b 100644 --- a/internal/article/handler/handler.go +++ b/internal/article/handler/handler.go @@ -37,7 +37,7 @@ func (h *articleHandler) NewArticle(c echo.Context) error { return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) } - return helper.ResponseHandler(c, http.StatusCreated, "ok", response) + return helper.ResponseHandler(c, http.StatusCreated, "new article created!", response) } func (h *articleHandler) UpdateArticle(c echo.Context) error { @@ -60,7 +60,7 @@ func (h *articleHandler) UpdateArticle(c echo.Context) error { return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) } - return helper.ResponseHandler(c, http.StatusOK, "ok", nil) + return helper.ResponseHandler(c, http.StatusOK, "article updated!", nil) } func (h *articleHandler) DeleteArticle(c echo.Context) error { @@ -74,7 +74,7 @@ func (h *articleHandler) DeleteArticle(c echo.Context) error { return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) } - return helper.ResponseHandler(c, http.StatusOK, "ok", nil) + return helper.ResponseHandler(c, http.StatusOK, "article deleted!", nil) } func (h *articleHandler) GetAllArticle(c echo.Context) error { @@ -144,3 +144,34 @@ func (h *articleHandler) GetArticleByID(c echo.Context) error { return helper.ResponseHandler(c, http.StatusOK, "ok", articleFound) } + +func (h *articleHandler) NewArticleComment(c echo.Context) error { + var request art.CommentInput + articleID := c.Param("articleId") + userID := c.Get("user").(*helper.JwtCustomClaims).UserID + + if err := c.Bind(&request); err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + } + + if err := c.Validate(&request); err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + } + + request.ArticleID = articleID + request.UserID = userID + + if err := h.usecase.NewArticleComment(request); err != nil { + if errors.Is(pkg.ErrArticleNotFound, err) { + return helper.ErrorHandler(c, http.StatusNotFound, err.Error()) + } + + if errors.Is(pkg.ErrUserNotFound, err) { + return helper.ErrorHandler(c, http.StatusNotFound, err.Error()) + } + + return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + } + + return helper.ResponseHandler(c, http.StatusCreated, "comment added!", nil) +} diff --git a/internal/article/repository/repository.go b/internal/article/repository/repository.go index a77ee96..b73066d 100644 --- a/internal/article/repository/repository.go +++ b/internal/article/repository/repository.go @@ -28,7 +28,7 @@ func (r *articleRepository) Create(article art.Article) (*art.Article, error) { func (r *articleRepository) FindByID(articleID string) (*art.Article, error) { var article art.Article - if err := r.DB.GetDB().Preload("Categories").Preload("Sections").First(&article, "id = ?", articleID).Error; err != nil { + if err := r.DB.GetDB().Preload("Categories").Preload("Sections").Preload("Comments").First(&article, "id = ?", articleID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, pkg.ErrArticleNotFound } @@ -52,7 +52,7 @@ func (r *articleRepository) FindAll(page, limit uint, sortBy string, sortType st db.Count(&total) - if err := db.Preload("Categories").Preload("Sections").Limit(int(limit)).Offset(int(offset)).Find(&articles).Error; err != nil { + if err := db.Preload("Categories").Limit(int(limit)).Offset(int(offset)).Find(&articles).Error; err != nil { return nil, 0, err } @@ -74,7 +74,6 @@ func (r *articleRepository) FindByKeyword(keyword string) (*[]art.Article, error if err := r.DB.GetDB(). Preload("Categories"). - Preload("Sections"). Joins("LEFT JOIN article_categories ON articles.id = article_categories.article_id"). Joins("LEFT JOIN waste_categories ON article_categories.waste_category_id = waste_categories.id"). Joins("LEFT JOIN video_categories ON article_categories.content_category_id = video_categories.id"). @@ -90,7 +89,7 @@ func (r *articleRepository) FindByCategory(categoryName string, categoryType str var articles []art.Article if categoryType == "waste" { - if err := r.DB.GetDB().Preload("Categories").Preload("Sections"). + if err := r.DB.GetDB().Preload("Categories"). Joins("JOIN article_categories ON articles.id = article_categories.article_id"). Joins("JOIN waste_categories ON article_categories.waste_category_id = waste_categories.id"). Where("waste_categories.name = ?", categoryName). @@ -98,7 +97,7 @@ func (r *articleRepository) FindByCategory(categoryName string, categoryType str return nil, err } } else if categoryType == "content" { - if err := r.DB.GetDB().Preload("Categories").Preload("Sections"). + if err := r.DB.GetDB().Preload("Categories"). Joins("JOIN article_categories ON articles.id = article_categories.article_id"). Joins("JOIN video_categories ON article_categories.content_category_id = video_categories.id"). Where("video_categories.name = ?", categoryName). @@ -236,3 +235,19 @@ func (r *articleRepository) FindCategoryByName(categoryName, categoryType string return 0, pkg.ErrCategoryArticleNotFound } + +func (r *articleRepository) CreateArticleComment(comment art.ArticleComment) error { + if err := r.DB.GetDB().Create(&comment).Error; err != nil { + return err + } + + return nil +} + +func (r *articleRepository) DeleteAllArticleComment(articleID string) error { + if err := r.DB.GetDB().Where("article_id = ?", articleID).Delete(&art.ArticleComment{}).Error; err != nil { + return err + } + + return nil +} diff --git a/internal/article/usecase/usecase.go b/internal/article/usecase/usecase.go index 64b822c..a2c0a58 100644 --- a/internal/article/usecase/usecase.go +++ b/internal/article/usecase/usecase.go @@ -4,15 +4,18 @@ import ( admin "github.com/sawalreverr/recything/internal/admin/repository" art "github.com/sawalreverr/recything/internal/article" "github.com/sawalreverr/recything/internal/helper" + user "github.com/sawalreverr/recything/internal/user" + "github.com/sawalreverr/recything/pkg" ) type articleUsecase struct { articleRepo art.ArticleRepository adminRepo admin.AdminRepository + userRepo user.UserRepository } -func NewArticleUsecase(articleRepo art.ArticleRepository, adminRepo admin.AdminRepository) art.ArticleUsecase { - return &articleUsecase{articleRepo: articleRepo, adminRepo: adminRepo} +func NewArticleUsecase(articleRepo art.ArticleRepository, adminRepo admin.AdminRepository, userRepo user.UserRepository) art.ArticleUsecase { + return &articleUsecase{articleRepo: articleRepo, adminRepo: adminRepo, userRepo: userRepo} } func (u *articleUsecase) NewArticle(article art.ArticleInput, authorId string) (*art.ArticleDetail, error) { @@ -218,16 +221,21 @@ func (uc *articleUsecase) Delete(articleID string) error { return err } + if err := uc.articleRepo.DeleteAllArticleComment(articleFound.ID); err != nil { + return err + } + return nil } func (uc *articleUsecase) GetArticleDetail(article art.Article) *art.ArticleDetail { adminDetail, _ := uc.GetDetailAuthor(article.AuthorID) wasteCategories, contentCategories, _ := uc.articleRepo.FindCategories(article.ID) + comments, _ := uc.GetDetailComments(article.Comments) articleDetail := art.ArticleDetail{ ID: article.ID, - AuthorID: *adminDetail, + Author: *adminDetail, Title: article.Title, Description: article.Description, ThumbnailURL: article.ThumbnailURL, @@ -235,6 +243,7 @@ func (uc *articleUsecase) GetArticleDetail(article art.Article) *art.ArticleDeta WasteCategories: *wasteCategories, ContentCategories: *contentCategories, Sections: article.Sections, + Comments: *comments, } return &articleDetail @@ -254,3 +263,60 @@ func (uc *articleUsecase) GetDetailAuthor(authorID string) (*art.AdminDetail, er return &adminDetail, nil } + +func (uc *articleUsecase) NewArticleComment(comment art.CommentInput) error { + articleFound, err := uc.GetArticleByID(comment.ArticleID) + if err != nil { + return pkg.ErrArticleNotFound + } + + userFound, err := uc.userRepo.FindByID(comment.UserID) + if err != nil { + return pkg.ErrUserNotFound + } + + newComment := art.ArticleComment{ + UserID: userFound.ID, + ArticleID: articleFound.ID, + Comment: comment.Comment, + } + + if err := uc.articleRepo.CreateArticleComment(newComment); err != nil { + return err + } + + return nil +} + +func (uc *articleUsecase) GetDetailUser(userID string) (*art.UserDetail, error) { + userFound, err := uc.userRepo.FindByID(userID) + if err != nil { + return nil, err + } + + userDetail := art.UserDetail{ + ID: userFound.ID, + Name: userFound.Name, + ImageURL: userFound.PictureURL, + } + + return &userDetail, nil +} + +func (uc *articleUsecase) GetDetailComments(comments []art.ArticleComment) (*[]art.CommentDetail, error) { + commentDetails := make([]art.CommentDetail, len(comments)) + for i, comment := range comments { + user, _ := uc.GetDetailUser(comment.UserID) + detail := art.CommentDetail{ + ID: comment.ID, + User: *user, + ArticleID: comment.ArticleID, + Comment: comment.Comment, + CreatedAt: comment.CreatedAt, + } + + commentDetails[i] = detail + } + + return &commentDetails, nil +} diff --git a/internal/database/migrate.go b/internal/database/migrate.go index fa598a4..22775fa 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -40,6 +40,7 @@ func AutoMigrate(db Database) { &article.Article{}, &article.ArticleSection{}, &article.ArticleCategories{}, + &article.ArticleComment{}, ); err != nil { log.Fatal("Database Migration Failed!") } diff --git a/internal/server/route.go b/internal/server/route.go index 99380ab..0fdb18f 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -382,7 +382,8 @@ func (s *echoServer) leaderboardHandler() { func (s *echoServer) articleHandler() { repositoryArticle := articleRepository.NewArticleRepository(s.db) repositoryAdmin := repository.NewAdminRepository(s.db) - usecase := articleUsecase.NewArticleUsecase(repositoryArticle, repositoryAdmin) + repositoryUser := userRepo.NewUserRepository(s.db) + usecase := articleUsecase.NewArticleUsecase(repositoryArticle, repositoryAdmin, repositoryUser) handler := articleHandler.NewArticleHandler(usecase) // Get all article @@ -405,4 +406,7 @@ func (s *echoServer) articleHandler() { // Delete article by admin s.gr.DELETE("/article/:articleId", handler.DeleteArticle, SuperAdminOrAdminMiddleware) + + // Add new comment by user + s.gr.POST("/article/:articleId/comment", handler.NewArticleComment, UserMiddleware) } From f8ceee76c5b56c67c44002d3ae03f428d2bf868a Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Jun 2024 02:49:52 +0700 Subject: [PATCH 40/72] add structure project for homepage --- internal/homepage/dto/homepage_response.go | 1 + internal/homepage/handler/homepage_handler.go | 1 + internal/homepage/handler/homepage_hanlder_impl.go | 1 + internal/homepage/repository/homepage_repository.go | 1 + internal/homepage/repository/homepage_repository_impl.go | 1 + internal/homepage/usecase/homepage_usecase.go | 1 + internal/homepage/usecase/homepage_usecase_impl.go | 1 + 7 files changed, 7 insertions(+) create mode 100644 internal/homepage/dto/homepage_response.go create mode 100644 internal/homepage/handler/homepage_handler.go create mode 100644 internal/homepage/handler/homepage_hanlder_impl.go create mode 100644 internal/homepage/repository/homepage_repository.go create mode 100644 internal/homepage/repository/homepage_repository_impl.go create mode 100644 internal/homepage/usecase/homepage_usecase.go create mode 100644 internal/homepage/usecase/homepage_usecase_impl.go diff --git a/internal/homepage/dto/homepage_response.go b/internal/homepage/dto/homepage_response.go new file mode 100644 index 0000000..76d3a17 --- /dev/null +++ b/internal/homepage/dto/homepage_response.go @@ -0,0 +1 @@ +package dto diff --git a/internal/homepage/handler/homepage_handler.go b/internal/homepage/handler/homepage_handler.go new file mode 100644 index 0000000..abeebd1 --- /dev/null +++ b/internal/homepage/handler/homepage_handler.go @@ -0,0 +1 @@ +package handler diff --git a/internal/homepage/handler/homepage_hanlder_impl.go b/internal/homepage/handler/homepage_hanlder_impl.go new file mode 100644 index 0000000..abeebd1 --- /dev/null +++ b/internal/homepage/handler/homepage_hanlder_impl.go @@ -0,0 +1 @@ +package handler diff --git a/internal/homepage/repository/homepage_repository.go b/internal/homepage/repository/homepage_repository.go new file mode 100644 index 0000000..50a4378 --- /dev/null +++ b/internal/homepage/repository/homepage_repository.go @@ -0,0 +1 @@ +package repository diff --git a/internal/homepage/repository/homepage_repository_impl.go b/internal/homepage/repository/homepage_repository_impl.go new file mode 100644 index 0000000..50a4378 --- /dev/null +++ b/internal/homepage/repository/homepage_repository_impl.go @@ -0,0 +1 @@ +package repository diff --git a/internal/homepage/usecase/homepage_usecase.go b/internal/homepage/usecase/homepage_usecase.go new file mode 100644 index 0000000..aed2454 --- /dev/null +++ b/internal/homepage/usecase/homepage_usecase.go @@ -0,0 +1 @@ +package usecase diff --git a/internal/homepage/usecase/homepage_usecase_impl.go b/internal/homepage/usecase/homepage_usecase_impl.go new file mode 100644 index 0000000..aed2454 --- /dev/null +++ b/internal/homepage/usecase/homepage_usecase_impl.go @@ -0,0 +1 @@ +package usecase From 9f85b4df4a6e095985aaa0e977203d5a7506ccef Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Wed, 12 Jun 2024 03:20:03 +0700 Subject: [PATCH 41/72] feat(article): update article: update apispec --- docs/swagger.yml | 389 ++++++++++++++++++++++------ internal/article/handler/handler.go | 2 +- 2 files changed, 316 insertions(+), 75 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 94d1046..bf39c6c 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -20,6 +20,12 @@ tags: description: Endpoint for user asking question to chatbot - name: about us description: Endpoint for user to get data about us by category + - name: user achievements + description: Endpoint for user get achievement + - name: user videos + description: Endpoint for user get video + - name: articles + description: Endpoint for user get data article - name: manage users description: Endpoint for admin manage users account @@ -37,13 +43,10 @@ tags: description: Endpoint for admin manage custom data - name: manage videos description: Endpoint for admin manage videos - - name: user videos - description: Endpoint for user get video - name: approval tasks description: Endpoint for admin approval task - - name: user achievements - description: Endpoint for user get achievement - - name: leaderboard + - name: leaderboards + description: Endpoint for leaderboards paths: /register: @@ -919,35 +922,24 @@ paths: items: $ref: '#/components/schemas/FAQs' - /admin/articles: - get: + /article: + post: tags: - manage articles - summary: Get all articles - description: Endpoint admin to get all articles with pagination. - operationId: viewAllArticles - parameters: - - name: page - in: query - required: false - schema: - type: integer - default: 1 - description: Page number for pagination - example: 1 - - name: limit - in: query - required: false - schema: - type: integer - default: 10 - description: Number of articles per page - example: 10 + summary: Add new article + description: Endpoint admin to add a new article + operationId: addArticle + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Article' security: - Bearer: [] responses: - '200': - description: Successfully get articles + '201': + description: Article successfully created! content: application/json: schema: @@ -955,45 +947,35 @@ paths: properties: code: type: integer - example: 200 + example: 201 message: type: string - example: ok - data: - type: object - properties: - total: - type: integer - example: 50 - page: - type: integer - example: 1 - limit: - type: integer - example: 10 - articles: - type: array - items: - $ref: '#/components/schemas/Article' + example: article created! '400': - description: Invalid request parameters - post: + description: Invalid request data + + /article/{articleId}: + get: tags: + - articles - manage articles - summary: Add new article - description: Endpoint admin to add a new article - operationId: addArticle - requestBody: - required: true - content: - multipart/form-data: - schema: - $ref: '#/components/schemas/Article' + summary: Get article data by id + description: Endpoint user to get article by id + operationId: getArticleById + parameters: + - name: articleId + in: query + required: true + schema: + type: string + # default: ART0001 + description: Article ID + example: ART0001 security: - Bearer: [] responses: - '201': - description: Article successfully created! + '200': + description: Get data article content: application/json: schema: @@ -1001,13 +983,19 @@ paths: properties: code: type: integer - example: 201 - message: + example: 200 + message: type: string - example: article successfully created! + example: ok + data: + type: object + properties: + schema: + $ref: '#/components/schemas/Article' '400': - description: Invalid request data - /admin/articles/{articleId}: + description: Invalid param request + '404': + description: Article not found put: tags: - manage articles @@ -1025,7 +1013,7 @@ paths: requestBody: required: true content: - multipart/form-data: + application/json: schema: $ref: '#/components/schemas/Article' security: @@ -1043,7 +1031,7 @@ paths: example: 200 message: type: string - example: article successfully updated! + example: article updated! '404': description: Article not found '400': @@ -4034,6 +4022,225 @@ paths: badge: type: string example: https://res.cloudinary.com/dymhvau8n/image/upload/v1717758679/achievement_badge/cq2n246e6twuksnia62t.png + /articles: + get: + tags: + - articles + - manage articles + summary: View all articles with pagination + description: Endpoint user or admin to view all articles with pagination. + operationId: viewAllArticles + parameters: + - name: page + in: query + required: false + schema: + type: integer + default: 1 + description: Page number for pagination + example: 1 + - name: limit + in: query + required: false + schema: + type: integer + default: 10 + description: Number of articles per page + example: 10 + - name: sort_by + in: query + required: false + schema: + type: string + default: created_at + description: Sorted by + example: created_at + - name: sort_type + in: query + required: false + schema: + type: string + default: asc + description: Sorted type + example: asc + security: + - Bearer: [] + responses: + '200': + description: Get all articles + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 200 + message: + type: string + example: ok + data: + type: object + properties: + total: + type: integer + example: 100 + page: + type: integer + example: 1 + limit: + type: integer + example: 10 + articles: + type: array + items: + type: object + properties: + schema: + $ref: '#/components/schemas/Article' + '400': + description: Invalid param request + + /article/search: + get: + tags: + - articles + summary: View all articles by keyword + description: Endpoint user to view all articles by keyword. + operationId: viewAllArticlesByKeyword + parameters: + - name: keyword + in: query + required: true + schema: + type: string + # default: plastik + description: Search by keyword + example: plastik + security: + - Bearer: [] + responses: + '200': + description: Get all articles by keyword + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 200 + message: + type: string + example: ok + data: + type: array + items: + type: object + $ref: '#/components/schemas/Article' + '400': + description: Invalid param request + + /article/category: + get: + tags: + - articles + summary: View all articles by category type and category name + description: Endpoint user to view all articles by category type and category name. + operationId: viewAllArticlesByCategory + parameters: + - name: type + in: query + required: true + schema: + type: string + # default: waste + description: Category Type (waste, content) + example: plastik + - name: name + in: query + required: true + schema: + type: string + # default: plastik + description: Category Name, waste(plastik, besi, kaca, organik, kayu, kertas, baterai, kaleng, elektronik, tekstil, minyak, bola lampu, berbahaya), content(tips, daur ulang, tutorial, edukasi, kampanye) + example: plastik + security: + - Bearer: [] + responses: + '200': + description: Get all articles by category + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 200 + message: + type: string + example: ok + data: + type: array + items: + type: object + $ref: '#/components/schemas/Article' + '400': + description: Invalid param request + + /article/{articleId}/comment: + post: + tags: + - articles + summary: Add new comment to article + description: Endpoint user to add comment + operationId: addCommentArticle + parameters: + - name: articleId + in: query + required: true + schema: + type: string + # default: ART0001 + description: Article ID + example: ART0001 + security: + - Bearer: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + comment: + type: string + description: User comment + example: Test comment + responses: + '201': + description: Success + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 200 + message: + type: string + example: comment added! + data: + type: object + properties: + schema: + $ref: '#/components/schemas/Article' + '400': + description: Invalid param request + '404': + description: Article not found components: schemas: @@ -4228,21 +4435,55 @@ components: id: type: string example: ART0001 + author: + type: object + properties: + id: + type: string + example: AD0001 + name: + type: string + example: John Doe + image_url: + type: string + example: http://example.com/image.png title: type: string - example: "The Benefits of Recycling" + example: Bahaya Limbah Elektronik dan Cara Pembuangan yang Bertanggung Jawab description: type: string - example: "Recycling has numerous benefits for the environment." - category: + example: Pelajari tentang bahaya lingkungan dan kesehatan dari limbah elektronik dan temukan metode pembuangan yang aman. + thumbnail_url: + type: string + format: uri + example: http://example.com/image.png + waste_categories: + type: array + items: + type: string + example: ["elektronik", "berbahaya"] + content_categories: type: array items: type: string - enum: [organic, plastic, paper, glass, textile, can, electronic, others] - example: "plastic" - thumbnail: + example: ["edukasi", "kampanye"] + sections: + type: array + items: + $ref: '#/components/schemas/ArticleSection' + ArticleSection: + type: object + properties: + title: + type: string + example: Apa itu Limbah Elektronik? + description: + type: string + example: Limbah elektronik mencakup barang elektronik bekas seperti ponsel, komputer, peralatan rumah tangga, dll. + image_url: type: string - example: "thumbnail.jpg" + format: uri + example: http://example.com/image.png Achievement: type: object properties: diff --git a/internal/article/handler/handler.go b/internal/article/handler/handler.go index 358015b..1f480c0 100644 --- a/internal/article/handler/handler.go +++ b/internal/article/handler/handler.go @@ -37,7 +37,7 @@ func (h *articleHandler) NewArticle(c echo.Context) error { return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) } - return helper.ResponseHandler(c, http.StatusCreated, "new article created!", response) + return helper.ResponseHandler(c, http.StatusCreated, "article created!", response) } func (h *articleHandler) UpdateArticle(c echo.Context) error { From fbe6285e4153a5009bab17f153b676c686abe612 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Jun 2024 14:39:28 +0700 Subject: [PATCH 42/72] feat(homepage): add endpoint for get homepage user --- internal/homepage/dto/homepage_response.go | 40 ++++++++++ internal/homepage/handler/homepage_handler.go | 6 ++ .../homepage/handler/homepage_handler_impl.go | 34 +++++++++ .../homepage/handler/homepage_hanlder_impl.go | 1 - .../repository/homepage_repository.go | 15 ++++ .../repository/homepage_repository_impl.go | 65 ++++++++++++++++ internal/homepage/usecase/homepage_usecase.go | 8 ++ .../homepage/usecase/homepage_usecase_impl.go | 76 +++++++++++++++++++ internal/server/echo_server.go | 3 + internal/server/route.go | 12 +++ .../entity/manage_video_entity.go | 4 +- 11 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 internal/homepage/handler/homepage_handler_impl.go delete mode 100644 internal/homepage/handler/homepage_hanlder_impl.go diff --git a/internal/homepage/dto/homepage_response.go b/internal/homepage/dto/homepage_response.go index 76d3a17..e175774 100644 --- a/internal/homepage/dto/homepage_response.go +++ b/internal/homepage/dto/homepage_response.go @@ -1 +1,41 @@ package dto + +type HomepageResponse struct { + User *DataUser `json:"user"` + Articles []*DataArtcicle `json:"articles"` + Videos []*DataVideo `json:"videos"` + Leaderboard []*DataLeaderboard `json:"leaderboard"` +} + +type DataUser struct { + Id string `json:"id"` + Name string `json:"name"` + Point int `json:"point"` + Badge string `json:"badge"` +} + +type DataArtcicle struct { + Id string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Thumbnail string `json:"thumbnail"` + CreatedAt string `json:"created_at"` + AuthorName string `json:"author_name"` +} + +type DataVideo struct { + Id int `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + UrlThumbnail string `json:"url_thumbnail"` + LinkVideo string `json:"link_video"` + Viewer int `json:"viewer"` +} + +type DataLeaderboard struct { + Id string `json:"id"` + Name string `json:"name"` + PictureURL string `json:"picture_url"` + Point int `json:"point"` + Badge string `json:"badge"` +} diff --git a/internal/homepage/handler/homepage_handler.go b/internal/homepage/handler/homepage_handler.go index abeebd1..945356d 100644 --- a/internal/homepage/handler/homepage_handler.go +++ b/internal/homepage/handler/homepage_handler.go @@ -1 +1,7 @@ package handler + +import "github.com/labstack/echo/v4" + +type HomePageHandler interface { + GetHomepageHandler(c echo.Context) error +} diff --git a/internal/homepage/handler/homepage_handler_impl.go b/internal/homepage/handler/homepage_handler_impl.go new file mode 100644 index 0000000..3d89e69 --- /dev/null +++ b/internal/homepage/handler/homepage_handler_impl.go @@ -0,0 +1,34 @@ +package handler + +import ( + "errors" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/sawalreverr/recything/internal/helper" + "github.com/sawalreverr/recything/internal/homepage/usecase" + "github.com/sawalreverr/recything/pkg" +) + +type HomePageHandlerImpl struct { + homepageUsecase usecase.HomepageUsecase +} + +func NewHomePageHandler(homepageUsecase usecase.HomepageUsecase) HomePageHandler { + return &HomePageHandlerImpl{homepageUsecase: homepageUsecase} +} + +func (handler *HomePageHandlerImpl) GetHomepageHandler(c echo.Context) error { + userId := c.Get("user").(*helper.JwtCustomClaims).UserID + homepageResponse, err := handler.homepageUsecase.GetHomepageUsecase(userId) + if err != nil { + if errors.Is(err, pkg.ErrUserNotFound) { + return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrUserNotFound.Error()) + } + return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) + } + + responseData := helper.ResponseData(http.StatusOK, "ok", &homepageResponse) + return c.JSON(http.StatusOK, responseData) + +} diff --git a/internal/homepage/handler/homepage_hanlder_impl.go b/internal/homepage/handler/homepage_hanlder_impl.go deleted file mode 100644 index abeebd1..0000000 --- a/internal/homepage/handler/homepage_hanlder_impl.go +++ /dev/null @@ -1 +0,0 @@ -package handler diff --git a/internal/homepage/repository/homepage_repository.go b/internal/homepage/repository/homepage_repository.go index 50a4378..cf64b95 100644 --- a/internal/homepage/repository/homepage_repository.go +++ b/internal/homepage/repository/homepage_repository.go @@ -1 +1,16 @@ package repository + +import ( + admin "github.com/sawalreverr/recything/internal/admin/entity" + "github.com/sawalreverr/recything/internal/article" + user "github.com/sawalreverr/recything/internal/user" + video "github.com/sawalreverr/recything/internal/video/manage_video/entity" +) + +type HomepageRepository interface { + GetArcticle() (*[]article.Article, error) + GetVideo() (*[]video.Video, error) + GetLeaderboard() (*[]user.User, error) + GetUserData(userId string) (*user.User, error) + FindAdminByID(adminId string) (*admin.Admin, error) +} diff --git a/internal/homepage/repository/homepage_repository_impl.go b/internal/homepage/repository/homepage_repository_impl.go index 50a4378..92087b6 100644 --- a/internal/homepage/repository/homepage_repository_impl.go +++ b/internal/homepage/repository/homepage_repository_impl.go @@ -1 +1,66 @@ package repository + +import ( + admin "github.com/sawalreverr/recything/internal/admin/entity" + "github.com/sawalreverr/recything/internal/article" + "github.com/sawalreverr/recything/internal/database" + "github.com/sawalreverr/recything/internal/user" + video "github.com/sawalreverr/recything/internal/video/manage_video/entity" +) + +type HomepageRepositoryImpl struct { + DB database.Database +} + +func NewHomepageRepository(db database.Database) HomepageRepository { + return &HomepageRepositoryImpl{DB: db} +} + +func (repository *HomepageRepositoryImpl) GetArcticle() (*[]article.Article, error) { + var articles []article.Article + if err := repository.DB.GetDB(). + Order("created_at desc"). + Limit(2). + Find(&articles).Error; err != nil { + return nil, err + } + return &articles, nil +} + +func (repository *HomepageRepositoryImpl) GetVideo() (*[]video.Video, error) { + var videos []video.Video + if err := repository.DB.GetDB(). + Order("created_at desc"). + Limit(3). + Find(&videos).Error; err != nil { + return nil, err + } + return &videos, nil +} + +func (repository *HomepageRepositoryImpl) GetLeaderboard() (*[]user.User, error) { + var users []user.User + if err := repository.DB.GetDB(). + Order("point desc"). + Limit(3). + Find(&users).Error; err != nil { + return nil, err + } + return &users, nil +} + +func (repository *HomepageRepositoryImpl) GetUserData(userId string) (*user.User, error) { + var user user.User + if err := repository.DB.GetDB().First(&user, "id = ?", userId).Error; err != nil { + return nil, err + } + return &user, nil +} + +func (repository *HomepageRepositoryImpl) FindAdminByID(adminId string) (*admin.Admin, error) { + var admin *admin.Admin + if err := repository.DB.GetDB().First(&admin, "id = ?", adminId).Error; err != nil { + return nil, err + } + return admin, nil +} diff --git a/internal/homepage/usecase/homepage_usecase.go b/internal/homepage/usecase/homepage_usecase.go index aed2454..66dd91a 100644 --- a/internal/homepage/usecase/homepage_usecase.go +++ b/internal/homepage/usecase/homepage_usecase.go @@ -1 +1,9 @@ package usecase + +import ( + "github.com/sawalreverr/recything/internal/homepage/dto" +) + +type HomepageUsecase interface { + GetHomepageUsecase(userId string) (*dto.HomepageResponse, error) +} diff --git a/internal/homepage/usecase/homepage_usecase_impl.go b/internal/homepage/usecase/homepage_usecase_impl.go index aed2454..dd48d2b 100644 --- a/internal/homepage/usecase/homepage_usecase_impl.go +++ b/internal/homepage/usecase/homepage_usecase_impl.go @@ -1 +1,77 @@ package usecase + +import ( + "github.com/sawalreverr/recything/internal/homepage/dto" + "github.com/sawalreverr/recything/internal/homepage/repository" + "github.com/sawalreverr/recything/pkg" +) + +type HomepageUsecaseImpl struct { + HomepageRepository repository.HomepageRepository +} + +func NewHomepageUsecase(homepageRepository repository.HomepageRepository) HomepageUsecase { + return &HomepageUsecaseImpl{HomepageRepository: homepageRepository} +} + +func (usecase *HomepageUsecaseImpl) GetHomepageUsecase(userId string) (*dto.HomepageResponse, error) { + videos, err := usecase.HomepageRepository.GetVideo() + if err != nil { + return nil, err + } + var dataVideo []*dto.DataVideo + for _, video := range *videos { + dataVideo = append(dataVideo, &dto.DataVideo{ + Id: video.ID, + Title: video.Title, + Description: video.Description, + UrlThumbnail: video.Thumbnail, + LinkVideo: video.Link, + Viewer: video.Viewer, + }) + } + + leaderboard, err := usecase.HomepageRepository.GetLeaderboard() + if err != nil { + return nil, err + } + var dataLeaderboard []*dto.DataLeaderboard + for _, user := range *leaderboard { + dataLeaderboard = append(dataLeaderboard, &dto.DataLeaderboard{ + Id: user.ID, + Name: user.Name, + PictureURL: user.PictureURL, + Point: int(user.Point), + Badge: user.Badge, + }) + } + user, err := usecase.HomepageRepository.GetUserData(userId) + if err != nil { + return nil, pkg.ErrUserNotFound + } + articles, errArticle := usecase.HomepageRepository.GetArcticle() + if errArticle != nil { + return nil, err + } + var dataArticle []*dto.DataArtcicle + for _, article := range *articles { + admin, erradmin := usecase.HomepageRepository.FindAdminByID(article.AuthorID) + if erradmin != nil { + return nil, erradmin + } + dataArticle = append(dataArticle, &dto.DataArtcicle{ + Id: article.ID, + Title: article.Title, + Description: article.Description, + Thumbnail: article.ThumbnailURL, + CreatedAt: article.CreatedAt.String(), + AuthorName: admin.Name, + }) + } + return &dto.HomepageResponse{ + User: &dto.DataUser{Id: user.ID, Name: user.Name, Point: int(user.Point), Badge: user.Badge}, + Articles: dataArticle, + Videos: dataVideo, + Leaderboard: dataLeaderboard, + }, nil +} diff --git a/internal/server/echo_server.go b/internal/server/echo_server.go index 2c28281..ca8f668 100644 --- a/internal/server/echo_server.go +++ b/internal/server/echo_server.go @@ -100,6 +100,9 @@ func (s *echoServer) Start() { // Article Handler s.articleHandler() + // homepage handler + s.homepageHandler() + serverPORT := fmt.Sprintf(":%d", s.conf.Server.Port) s.app.Logger.Fatal(s.app.Start(serverPORT)) } diff --git a/internal/server/route.go b/internal/server/route.go index c2d10bb..13a5fea 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -27,6 +27,9 @@ import ( faqHandler "github.com/sawalreverr/recything/internal/faq/handler" faqRepo "github.com/sawalreverr/recything/internal/faq/repository" faqUsecase "github.com/sawalreverr/recything/internal/faq/usecase" + homepageHandler "github.com/sawalreverr/recything/internal/homepage/handler" + homepageRepo "github.com/sawalreverr/recything/internal/homepage/repository" + homepageUsecase "github.com/sawalreverr/recything/internal/homepage/usecase" leaderboardHandler "github.com/sawalreverr/recything/internal/leaderboard/handler" leaderboardRepo "github.com/sawalreverr/recything/internal/leaderboard/repository" leaderboardUsecase "github.com/sawalreverr/recything/internal/leaderboard/usecase" @@ -388,3 +391,12 @@ func (s *echoServer) articleHandler() { // Get article by id s.gr.GET("/article/:articleId", handler.GetArticleByID, AllRoleMiddleware) } + +func (s *echoServer) homepageHandler() { + repository := homepageRepo.NewHomepageRepository(s.db) + usecase := homepageUsecase.NewHomepageUsecase(repository) + handler := homepageHandler.NewHomePageHandler(usecase) + + // Get homepage + s.gr.GET("/homepage", handler.GetHomepageHandler, UserMiddleware) +} diff --git a/internal/video/manage_video/entity/manage_video_entity.go b/internal/video/manage_video/entity/manage_video_entity.go index 08a60bb..9815496 100644 --- a/internal/video/manage_video/entity/manage_video_entity.go +++ b/internal/video/manage_video/entity/manage_video_entity.go @@ -22,8 +22,8 @@ type Video struct { } type VideoCategory struct { - ID int `gorm:"primaryKey"` - Name string `gorm:"unique;not null"` + ID int `gorm:"primaryKey"` + Name string CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` From d42fcfa8d5ce5a0e87bdf86b33938069674c66f2 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Jun 2024 17:55:44 +0700 Subject: [PATCH 43/72] add property for save badge user on entity and add to dummy data --- .../entity/manage_archievement_entity.go | 15 +++++----- .../repository/user_achievement_repository.go | 2 +- .../user_achievement_repository_impl.go | 2 +- .../usecase/user_achievement_usecase_impl.go | 2 +- internal/database/mysql.go | 28 +++++++++++-------- internal/helper/bonus_task.go | 8 +++--- 6 files changed, 31 insertions(+), 26 deletions(-) diff --git a/internal/achievements/manage_achievements/entity/manage_archievement_entity.go b/internal/achievements/manage_achievements/entity/manage_archievement_entity.go index 9867d15..91cc843 100644 --- a/internal/achievements/manage_achievements/entity/manage_archievement_entity.go +++ b/internal/achievements/manage_achievements/entity/manage_archievement_entity.go @@ -7,11 +7,12 @@ import ( ) type Achievement struct { - ID int `json:"id" gorm:"primaryKey"` - Level string - TargetPoint int - BadgeUrl string - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `gorm:"index"` + ID int `json:"id" gorm:"primaryKey"` + Level string `gorm:"unique"` + TargetPoint int + BadgeUrl string + BadgeUrlUser string + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index"` } diff --git a/internal/achievements/user_achievements/repository/user_achievement_repository.go b/internal/achievements/user_achievements/repository/user_achievement_repository.go index e41895d..bb22e71 100644 --- a/internal/achievements/user_achievements/repository/user_achievement_repository.go +++ b/internal/achievements/user_achievements/repository/user_achievement_repository.go @@ -8,6 +8,6 @@ import ( type UserAchievementRepository interface { GetAvhievementsByUser() (*[]archievement.Achievement, error) - GetHostoryUserPoint(userId string) (*[]user_task.UserTaskChallenge, error) + GetHistoryUserPoint(userId string) (*[]user_task.UserTaskChallenge, error) GetPoinUser(userId string) (*user.User, error) } diff --git a/internal/achievements/user_achievements/repository/user_achievement_repository_impl.go b/internal/achievements/user_achievements/repository/user_achievement_repository_impl.go index 6d47580..1c8928c 100644 --- a/internal/achievements/user_achievements/repository/user_achievement_repository_impl.go +++ b/internal/achievements/user_achievements/repository/user_achievement_repository_impl.go @@ -23,7 +23,7 @@ func (repository UserAchievementRepositoryImpl) GetAvhievementsByUser() (*[]arch return &achievements, nil } -func (repository UserAchievementRepositoryImpl) GetHostoryUserPoint(userId string) (*[]user_task.UserTaskChallenge, error) { +func (repository UserAchievementRepositoryImpl) GetHistoryUserPoint(userId string) (*[]user_task.UserTaskChallenge, error) { var userTasks []user_task.UserTaskChallenge if err := repository.DB.GetDB(). Where("user_id = ?", userId). diff --git a/internal/achievements/user_achievements/usecase/user_achievement_usecase_impl.go b/internal/achievements/user_achievements/usecase/user_achievement_usecase_impl.go index d8e8f19..be19bc4 100644 --- a/internal/achievements/user_achievements/usecase/user_achievement_usecase_impl.go +++ b/internal/achievements/user_achievements/usecase/user_achievement_usecase_impl.go @@ -22,7 +22,7 @@ func (usecase *UserAchievementUsecaseImpl) GetAvhievementsByUser(userId string) return nil, err } - historyUserPoint, err := usecase.userAchievementRepository.GetHostoryUserPoint(userId) + historyUserPoint, err := usecase.userAchievementRepository.GetHistoryUserPoint(userId) if err != nil { if err == gorm.ErrRecordNotFound { return nil, pkg.ErrUserNotHasHistoryPoint diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 3c61d6f..d0e7158 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -219,24 +219,28 @@ func (m *mysqlDatabase) InitTaskSteps() { func (m *mysqlDatabase) InitAchievements() { dumyData := []achievement.Achievement{ { - Level: "classic", - TargetPoint: 0, - BadgeUrl: "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758679/achievement_badge/cq2n246e6twuksnia62t.png", + Level: "classic", + TargetPoint: 0, + BadgeUrl: "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758679/achievement_badge/cq2n246e6twuksnia62t.png", + BadgeUrlUser: "https://res.cloudinary.com/dymhvau8n/image/upload/v1718189121/user_badge/htaemsjtlhfof7ww01ss.png", }, { - Level: "silver", - TargetPoint: 50000, - BadgeUrl: "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758731/achievement_badge/b8igluyain8bwyjusfpk.png", + Level: "silver", + TargetPoint: 50000, + BadgeUrl: "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758731/achievement_badge/b8igluyain8bwyjusfpk.png", + BadgeUrlUser: "https://res.cloudinary.com/dymhvau8n/image/upload/v1718189221/user_badge/oespnjdgoynkairlutbk.png", }, { - Level: "gold", - TargetPoint: 150000, - BadgeUrl: "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758761/achievement_badge/lazzyh9tytvb4rophbc3.png", + Level: "gold", + TargetPoint: 150000, + BadgeUrl: "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758761/achievement_badge/lazzyh9tytvb4rophbc3.png", + BadgeUrlUser: "https://res.cloudinary.com/dymhvau8n/image/upload/v1718189184/user_badge/jshs1s2fwevahgtvjkgj.png", }, { - Level: "platinum", - TargetPoint: 300000, - BadgeUrl: "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758798/achievement_badge/xc8msr6agowzhfq8ss8a.png", + Level: "platinum", + TargetPoint: 300000, + BadgeUrl: "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758798/achievement_badge/xc8msr6agowzhfq8ss8a.png", + BadgeUrlUser: "https://res.cloudinary.com/dymhvau8n/image/upload/v1718188250/user_badge/icureiapdvtzyu5b99zu.png", }, } diff --git a/internal/helper/bonus_task.go b/internal/helper/bonus_task.go index ee3b618..19b9323 100644 --- a/internal/helper/bonus_task.go +++ b/internal/helper/bonus_task.go @@ -2,13 +2,13 @@ package helper func BonusTask(badegUser string, userPoint int) int { switch badegUser { - case "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758679/achievement_badge/cq2n246e6twuksnia62t.png": + case "https://res.cloudinary.com/dymhvau8n/image/upload/v1718189121/user_badge/htaemsjtlhfof7ww01ss.png": return userPoint + userPoint*10/100 - case "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758731/achievement_badge/b8igluyain8bwyjusfpk.png": + case "https://res.cloudinary.com/dymhvau8n/image/upload/v1718189221/user_badge/oespnjdgoynkairlutbk.png": return userPoint + userPoint*15/100 - case "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758761/achievement_badge/lazzyh9tytvb4rophbc3.png": + case "https://res.cloudinary.com/dymhvau8n/image/upload/v1718189184/user_badge/jshs1s2fwevahgtvjkgj.png": return userPoint + userPoint*20/100 - case "https://res.cloudinary.com/dymhvau8n/image/upload/v1717758798/achievement_badge/xc8msr6agowzhfq8ss8a.png": + case "https://res.cloudinary.com/dymhvau8n/image/upload/v1718188250/user_badge/icureiapdvtzyu5b99zu.png": return userPoint + userPoint*25/100 default: return userPoint From 8906154242567664c095cd421a87623bf708c14c Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Jun 2024 18:24:09 +0700 Subject: [PATCH 44/72] refactor(approval task): change update badge for user --- .../approval_task/repository/approval_task_repository_impl.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/task/approval_task/repository/approval_task_repository_impl.go b/internal/task/approval_task/repository/approval_task_repository_impl.go index c1dd349..0c37c2f 100644 --- a/internal/task/approval_task/repository/approval_task_repository_impl.go +++ b/internal/task/approval_task/repository/approval_task_repository_impl.go @@ -52,7 +52,6 @@ func (repository *ApprovalTaskRepositoryImpl) ApproveUserTask(userTaskId string) var userTask user_task.UserTaskChallenge tx := repository.DB.GetDB().Begin() - // Load the user task first to ensure we have all its fields if err := tx.Where("id = ?", userTaskId).First(&userTask).Error; err != nil { tx.Rollback() return err @@ -93,7 +92,7 @@ func (repository *ApprovalTaskRepositoryImpl) ApproveUserTask(userTaskId string) var badge string for _, ach := range achievements { if pointUpdate >= ach.TargetPoint { - badge = ach.BadgeUrl + badge = ach.BadgeUrlUser break } } From 5ae96b02dc9f47a28b286797501371cbaa4cc3b6 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Jun 2024 01:59:55 +0700 Subject: [PATCH 45/72] docs: change some request body and response body --- docs/swagger.yml | 63 ------------------------------------------------ 1 file changed, 63 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index d2c49b7..a1ee498 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -3827,69 +3827,6 @@ paths: type: string example: video not found - /videos/content-categories: - get: - tags: - - user videos - summary: Get all content categories - description: Enpoint for user get all content categories - operationId: getContentCategories - security: - - Bearer: [] - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: - code: - type: integer - example: 200 - message: - type: string - example: success - data: - type: array - items: - type: object - properties: - name: - type: string - example: "tips" - - /videos/waste-categories: - get: - tags: - - user videos - summary: Get all trash categories - description: Enpoint for user get all trash categories - operationId: getTrashCategories - security: - - Bearer: [] - responses: - '200': - description: Success - content: - application/json: - schema: - type: object - properties: - code: - type: integer - example: 200 - message: - type: string - example: success - data: - type: array - items: - type: object - properties: - name: - type: string - example: "berbahaya" /approval-tasks: get: From 4e538565d116afd677e6dc7780b215a397067e86 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Jun 2024 20:52:48 +0700 Subject: [PATCH 46/72] docs: add api spec for homepage --- docs/swagger.yml | 131 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 7cad6ea..960f837 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -44,6 +44,9 @@ tags: - name: user achievements description: Endpoint for user get achievement - name: leaderboard + description: Endpoint for user get leaderboard + - name: homepage + description: Endpoint for user get homepage paths: /register: @@ -4188,7 +4191,7 @@ paths: /leaderboard: get: tags: - - leaderboards + - leaderboard summary: Get leaderboard description: Endpoint for user or admin get leaderboard operationId: getLeaderboard @@ -4226,7 +4229,131 @@ paths: badge: type: string example: https://res.cloudinary.com/dymhvau8n/image/upload/v1717758679/achievement_badge/cq2n246e6twuksnia62t.png - + /homepage: + get: + tags: + - homepage + summary: Get homepage + description: Endpoint for user get homepage + operationId: getHomepage + security: + - Bearer: [] + responses: + '200': + description: ok + content: + application/json: + schema: + properties: + code: + type: integer + example: 200 + message: + type: string + example: ok + data: + type: array + items: + type: object + properties: + user: + type: object + properties: + id: + type: string + example: USR0001 + name: + type: string + example: John Doe + picture_url: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.png + point: + type: integer + example: 1000 + badge: + type: string + articles: + type: array + items: + type: object + properties: + id: + type: string + example: ART0001 + title: + type: string + example: RecyThing + description: + type: string + example: RecyThing adalah pemimpin di industri daur ulang sampah yang berkomitmen untuk menjaga lingkungan hidup yang lebih bersih dan lebih berkelanjutan. + thumbnail: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.png + description: Thumbnail article + author_name: + type: string + example: John Doe + description: Name of the author + profile_author: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.png + description: Profile picture of the author + created_at: + type: string + format: date + example: "2020-01-01" + description: Date and time when the article was created + videos: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + title: + type: string + example: RecyThing + description: + type: string + example: RecyThing adalah pemimpin di industri daur ulang sampah yang berkomitmen untuk menjaga lingkungan hidup yang lebih bersih dan lebih berkelanjutan. + url_thumbnail: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.png + description: Thumbnail video + link_video: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/video/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.mp4 + description: Link video Youtube + viewer: + type: integer + example: 1000 + leaderboard: + type: array + items: + type: object + properties: + id: + type: string + example: USR0001 + description: ID of the user + name: + type: string + example: John Doe + picture_url: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.png + description: Profile picture of the user + point: + type: integer + example: 1000 + description: Point of the user + badge: + type: string + example: https://res.cloudinary.com/dymhvau8n/image/upload/v1717758679/achievement_badge/cq2n246e6twuksnia62t.png + description: Badge of the user + components: schemas: User: From 125fea5dbbbc946a7205fd334a242b4475623bb8 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 12 Jun 2024 21:03:12 +0700 Subject: [PATCH 47/72] refactor(response data): add profile author, and profile user on response body --- internal/homepage/dto/homepage_response.go | 22 ++++++++++--------- .../homepage/usecase/homepage_usecase_impl.go | 15 +++++++------ 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/internal/homepage/dto/homepage_response.go b/internal/homepage/dto/homepage_response.go index e175774..cac2666 100644 --- a/internal/homepage/dto/homepage_response.go +++ b/internal/homepage/dto/homepage_response.go @@ -8,19 +8,21 @@ type HomepageResponse struct { } type DataUser struct { - Id string `json:"id"` - Name string `json:"name"` - Point int `json:"point"` - Badge string `json:"badge"` + Id string `json:"id"` + Name string `json:"name"` + PictureURL string `json:"picture_url"` + Point int `json:"point"` + Badge string `json:"badge"` } type DataArtcicle struct { - Id string `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - Thumbnail string `json:"thumbnail"` - CreatedAt string `json:"created_at"` - AuthorName string `json:"author_name"` + Id string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Thumbnail string `json:"thumbnail"` + AuthorName string `json:"author_name"` + Author_Profile string `json:"author_profile"` + CreatedAt string `json:"created_at"` } type DataVideo struct { diff --git a/internal/homepage/usecase/homepage_usecase_impl.go b/internal/homepage/usecase/homepage_usecase_impl.go index dd48d2b..62281f6 100644 --- a/internal/homepage/usecase/homepage_usecase_impl.go +++ b/internal/homepage/usecase/homepage_usecase_impl.go @@ -60,16 +60,17 @@ func (usecase *HomepageUsecaseImpl) GetHomepageUsecase(userId string) (*dto.Home return nil, erradmin } dataArticle = append(dataArticle, &dto.DataArtcicle{ - Id: article.ID, - Title: article.Title, - Description: article.Description, - Thumbnail: article.ThumbnailURL, - CreatedAt: article.CreatedAt.String(), - AuthorName: admin.Name, + Id: article.ID, + Title: article.Title, + Description: article.Description, + Thumbnail: article.ThumbnailURL, + CreatedAt: article.CreatedAt.String(), + AuthorName: admin.Name, + Author_Profile: admin.ImageUrl, }) } return &dto.HomepageResponse{ - User: &dto.DataUser{Id: user.ID, Name: user.Name, Point: int(user.Point), Badge: user.Badge}, + User: &dto.DataUser{Id: user.ID, Name: user.Name, Point: int(user.Point), Badge: user.Badge, PictureURL: user.PictureURL}, Articles: dataArticle, Videos: dataVideo, Leaderboard: dataLeaderboard, From 93f9e5ce03aeb40f1680c2d4f3606bc81acdf4ea Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Wed, 12 Jun 2024 21:35:30 +0700 Subject: [PATCH 48/72] docs: merge apispec --- docs/swagger.yml | 408 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 364 insertions(+), 44 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index bf39c6c..d66c34c 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -6,6 +6,8 @@ info: servers: - url: http://localhost:8080/api/v1 tags: + - name: homepage + description: Endpoint for user get homepage - name: authentication description: Endpoint for user register/login - name: user @@ -20,9 +22,9 @@ tags: description: Endpoint for user asking question to chatbot - name: about us description: Endpoint for user to get data about us by category - - name: user achievements + - name: achievements description: Endpoint for user get achievement - - name: user videos + - name: videos description: Endpoint for user get video - name: articles description: Endpoint for user get data article @@ -45,9 +47,10 @@ tags: description: Endpoint for admin manage videos - name: approval tasks description: Endpoint for admin approval task + - name: leaderboards description: Endpoint for leaderboards - + paths: /register: post: @@ -1799,7 +1802,7 @@ paths: message: type: string example: task not found - put: + patch: tags: - manage tasks summary: Update task @@ -1816,7 +1819,7 @@ paths: description: ID of the task to be updated example: TM0001 requestBody: - required: true + required: false content: multipart/form-data:: schema: @@ -3030,12 +3033,22 @@ paths: items: type: object properties: - id: - type: integer - example: 1 - name: - type: string - example: "Category Name" + content_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "tips" + waste_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "berbahaya" '500': description: Internal server error @@ -3079,9 +3092,22 @@ paths: link_video: type: string example: "https://example.com/video.mp4" - category_id: - type: integer - example: 1 + content_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "tips" + waste_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "berbahaya" thumbnail: type: string format: binary @@ -3251,15 +3277,23 @@ paths: viewer: type: integer example: 1000 - category: - type: object - properties: - id: - type: integer - example: 1 - name: - type: string - example: "Video Category" + content_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "tips" + waste_categories:: + type: array + items: + type: object + properties: + name: + type: string + example: "berbahaya" + '404': description: Video not found content: @@ -3273,7 +3307,7 @@ paths: message: type: string example: video not found - put: + patch: tags: - manage videos summary: Update Data Video @@ -3290,7 +3324,7 @@ paths: example: 1 description: ID of video requestBody: - required: true + required: false content: multipart/form-data: schema: @@ -3308,9 +3342,23 @@ paths: link_video: type: string example: "https://example.com/video.mp4" - category_id: - type: integer - example: 1 + content_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "tips" + waste_categories: + type: array + items: + type: object + properties: + name: + type: string + example: "berbahaya" + thumbnail: type: string format: binary @@ -3390,7 +3438,7 @@ paths: /videos: get: tags: - - user videos + - videos summary: Get All Data Video description: Endpoint user for get all data video operationId: getAllDataVideo @@ -3433,6 +3481,28 @@ paths: viewer: type: integer example: 1000 + content_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "tips" + waste_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "berbahaya" '500': description: Status internal error' content: @@ -3449,21 +3519,21 @@ paths: /videos/search: get: tags: - - user videos - summary: Search Videos by title - description: Searches for videos that match a specific title - operationId: getVideosBytitle + - videos + summary: Search Videos by keyword + description: Searches for videos that match a specific keyword + operationId: getVideosBykeyword parameters: - in: query - name: title + name: keyword required: true schema: type: string example: plastik - description: The title to search for videos. + description: The keyword to search for videos. responses: '200': - description: Get all data by title + description: Get all data by keyword content: application/json: schema: @@ -3489,8 +3559,30 @@ paths: viewer: type: integer example: 1000 - '500': - description: Status internal error' + content_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "tips" + waste_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "plastik" + '404': + description: Video not found content: application/json: schema: @@ -3498,14 +3590,16 @@ paths: properties: code: type: integer - example: 500 + example: 404 message: type: string - example: internal server error - /videos/{videoId}: + example: video not found + + + /video/{videoId}: get: tags: - - user videos + - videos summary: Get Data Video by id description: Get Data Video details by id operationId: getDataVideo @@ -3578,7 +3672,7 @@ paths: /videos/comment: post: tags: - - user videos + - videos summary: Create Comment description: Endpoint user for create comment operationId: createComment @@ -3624,6 +3718,107 @@ paths: message: type: string example: video not found + /videos/category: + get: + tags: + - videos + summary: Get all video by content category or waste category + description: Enpoint for user get all video by content category or waste category + operationId: getContentCategories + security: + - Bearer: [] + parameters: + - name: type + in: query + required: true + schema: + type: string + example: "content" + - name: name + in: query + required: true + schema: + type: string + example: "tips" + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 200 + message: + type: string + example: success + data: + type: array + items: + type: object + properties: + data_video: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + title: + type: string + example: "Video Title" + description: + type: string + example: "Video Description" + url_thumbnail: + type: string + example: "https://example.com/thumbnail.jpg" + link_video: + type: string + example: "https://example.com/video.mp4" + viewer: + type: integer + example: 1000 + content_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "tips" + waste_categories: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + name: + type: string + example: "berbahaya" + '404': + description: video not found + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 404 + message: + type: string + example: video not found + + /approval-tasks: get: tags: @@ -3908,7 +4103,7 @@ paths: /user/achievements: get: tags: - - user achievements + - achievements summary: Get user achievements description: Endpoint for user get list achievements operationId: getUserAchievements @@ -4022,6 +4217,131 @@ paths: badge: type: string example: https://res.cloudinary.com/dymhvau8n/image/upload/v1717758679/achievement_badge/cq2n246e6twuksnia62t.png + /homepage: + get: + tags: + - homepage + summary: Get homepage + description: Endpoint for user get homepage + operationId: getHomepage + security: + - Bearer: [] + responses: + '200': + description: ok + content: + application/json: + schema: + properties: + code: + type: integer + example: 200 + message: + type: string + example: ok + data: + type: array + items: + type: object + properties: + user: + type: object + properties: + id: + type: string + example: USR0001 + name: + type: string + example: John Doe + picture_url: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.png + point: + type: integer + example: 1000 + badge: + type: string + articles: + type: array + items: + type: object + properties: + id: + type: string + example: ART0001 + title: + type: string + example: RecyThing + description: + type: string + example: RecyThing adalah pemimpin di industri daur ulang sampah yang berkomitmen untuk menjaga lingkungan hidup yang lebih bersih dan lebih berkelanjutan. + thumbnail: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.png + description: Thumbnail article + author_name: + type: string + example: John Doe + description: Name of the author + author_profile: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.png + description: Profile picture of the author + created_at: + type: string + format: date + example: "2020-01-01" + description: Date and time when the article was created + videos: + type: array + items: + type: object + properties: + id: + type: integer + example: 1 + title: + type: string + example: RecyThing + description: + type: string + example: RecyThing adalah pemimpin di industri daur ulang sampah yang berkomitmen untuk menjaga lingkungan hidup yang lebih bersih dan lebih berkelanjutan. + url_thumbnail: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.png + description: Thumbnail video + link_video: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/video/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.mp4 + description: Link video Youtube + viewer: + type: integer + example: 1000 + leaderboard: + type: array + items: + type: object + properties: + id: + type: string + example: USR0001 + description: ID of the user + name: + type: string + example: John Doe + picture_url: + type: string + example: https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/kan9fdnp7h6o4hfclghm.png + description: Profile picture of the user + point: + type: integer + example: 1000 + description: Point of the user + badge: + type: string + example: https://res.cloudinary.com/dymhvau8n/image/upload/v1717758679/achievement_badge/cq2n246e6twuksnia62t.png + description: Badge of the user + /articles: get: tags: From 4f41812f009056990efe3592d4a66a3c9175f681 Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Wed, 12 Jun 2024 21:43:15 +0700 Subject: [PATCH 49/72] docs: merge apispec --- docs/swagger.yml | 127 +---------------------------------------------- 1 file changed, 1 insertion(+), 126 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index b6f1e15..4a27a63 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -1802,7 +1802,6 @@ paths: message: type: string example: task not found - patch: patch: tags: - manage tasks @@ -1820,7 +1819,6 @@ paths: description: ID of the task to be updated example: TM0001 requestBody: - required: false required: false content: multipart/form-data:: @@ -3051,22 +3049,6 @@ paths: name: type: string example: "berbahaya" - content_categories: - type: array - items: - type: object - properties: - name: - type: string - example: "tips" - waste_categories: - type: array - items: - type: object - properties: - name: - type: string - example: "berbahaya" '500': description: Internal server error @@ -3126,22 +3108,6 @@ paths: name: type: string example: "berbahaya" - content_categories: - type: array - items: - type: object - properties: - name: - type: string - example: "tips" - waste_categories: - type: array - items: - type: object - properties: - name: - type: string - example: "berbahaya" thumbnail: type: string format: binary @@ -3319,24 +3285,7 @@ paths: name: type: string example: "tips" - waste_categories:: - type: array - items: - type: object - properties: - name: - type: string - example: "berbahaya" - - content_categories: - type: array - items: - type: object - properties: - name: - type: string - example: "tips" - waste_categories:: + waste_categories: type: array items: type: object @@ -3358,7 +3307,6 @@ paths: message: type: string example: video not found - patch: patch: tags: - manage videos @@ -3376,7 +3324,6 @@ paths: example: 1 description: ID of video requestBody: - required: false required: false content: multipart/form-data: @@ -3412,23 +3359,6 @@ paths: type: string example: "berbahaya" - content_categories: - type: array - items: - type: object - properties: - name: - type: string - example: "tips" - waste_categories: - type: array - items: - type: object - properties: - name: - type: string - example: "berbahaya" - thumbnail: type: string format: binary @@ -3573,28 +3503,6 @@ paths: name: type: string example: "berbahaya" - content_categories: - type: array - items: - type: object - properties: - id: - type: integer - example: 1 - name: - type: string - example: "tips" - waste_categories: - type: array - items: - type: object - properties: - id: - type: integer - example: 1 - name: - type: string - example: "berbahaya" '500': description: Status internal error' content: @@ -3617,17 +3525,14 @@ paths: operationId: getVideosBykeyword parameters: - in: query - name: keyword name: keyword required: true schema: type: string example: plastik description: The keyword to search for videos. - description: The keyword to search for videos. responses: '200': - description: Get all data by keyword description: Get all data by keyword content: application/json: @@ -3676,30 +3581,6 @@ paths: name: type: string example: "plastik" - '404': - description: Video not found - content_categories: - type: array - items: - type: object - properties: - id: - type: integer - example: 1 - name: - type: string - example: "tips" - waste_categories: - type: array - items: - type: object - properties: - id: - type: integer - example: 1 - name: - type: string - example: "plastik" '404': description: Video not found content: @@ -3710,16 +3591,10 @@ paths: code: type: integer example: 404 - example: 404 message: type: string example: video not found - - /video/{videoId}: - example: video not found - - /video/{videoId}: get: tags: From 6e82f5d96ba89ae9aa6bd0bbf35d15a9f592da46 Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Wed, 12 Jun 2024 21:44:23 +0700 Subject: [PATCH 50/72] docs: merge apispec --- docs/swagger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index 4a27a63..f121b90 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -4178,7 +4178,7 @@ paths: /leaderboard: get: tags: - - leaderboard + - leaderboards summary: Get leaderboard description: Endpoint for user or admin get leaderboard operationId: getLeaderboard From c6b7b35125701df5cbabbd04ad10be92e9c47b2e Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Wed, 12 Jun 2024 22:27:25 +0700 Subject: [PATCH 51/72] feat(article): update feature: upload image --- docs/swagger.yml | 46 +++++++++++++++++++++++++++++ internal/article/entity.go | 1 + internal/article/handler/handler.go | 29 ++++++++++++++++++ internal/server/route.go | 3 ++ 4 files changed, 79 insertions(+) diff --git a/docs/swagger.yml b/docs/swagger.yml index f121b90..f8b0513 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -1071,6 +1071,52 @@ paths: example: article successfully deleted! '404': description: Article not found + + /article/upload: + post: + tags: + - manage articles + summary: Upload image for article + description: Endpoint admin to upload an image + operationId: uploadArticleImage + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: + - image + properties: + image: + type: string + format: image + description: Article image anda + security: + - Bearer: [] + responses: + '200': + description: Upload successfully + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 200 + message: + type: string + example: article updated! + data: + type: object + properties: + image_url: + type: string + example: http://example.com/image.png + '400': + description: Invalid request data + /achievements: get: diff --git a/internal/article/entity.go b/internal/article/entity.go index 6fa348e..7cfbc93 100644 --- a/internal/article/entity.go +++ b/internal/article/entity.go @@ -135,4 +135,5 @@ type ArticleHandler interface { GetArticleByID(c echo.Context) error NewArticleComment(c echo.Context) error + ArticleUploadImage(c echo.Context) error } diff --git a/internal/article/handler/handler.go b/internal/article/handler/handler.go index 1f480c0..283e4bd 100644 --- a/internal/article/handler/handler.go +++ b/internal/article/handler/handler.go @@ -4,6 +4,7 @@ import ( "errors" "net/http" "strconv" + "strings" "github.com/labstack/echo/v4" art "github.com/sawalreverr/recything/internal/article" @@ -175,3 +176,31 @@ func (h *articleHandler) NewArticleComment(c echo.Context) error { return helper.ResponseHandler(c, http.StatusCreated, "comment added!", nil) } + +func (h *articleHandler) ArticleUploadImage(c echo.Context) error { + file, err := c.FormFile("image") + if err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, "please upload your image!") + } + + if file.Size > 2*1024*1024 { + return helper.ErrorHandler(c, http.StatusBadRequest, "upload image size must less than 2MB!") + } + + fileType := file.Header.Get("Content-Type") + if !strings.HasPrefix(fileType, "image/") { + return helper.ErrorHandler(c, http.StatusBadRequest, "only image allowed!") + } + + src, _ := file.Open() + defer src.Close() + + resp, err := helper.UploadToCloudinary(src, "recything/article/") + if err != nil { + return helper.ErrorHandler(c, http.StatusInternalServerError, "upload failed, cloudinary server error!") + } + + return helper.ResponseHandler(c, http.StatusOK, "upload successfully!", echo.Map{ + "image_url": resp, + }) +} diff --git a/internal/server/route.go b/internal/server/route.go index 52e1632..153a734 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -412,6 +412,9 @@ func (s *echoServer) articleHandler() { // Add new comment by user s.gr.POST("/article/:articleId/comment", handler.NewArticleComment, UserMiddleware) + + // Upload image + s.gr.POST("/article/upload", handler.ArticleUploadImage, SuperAdminOrAdminMiddleware) } func (s *echoServer) homepageHandler() { From 2160f8d3ab615f2f56783fc33934bbb97cb893d4 Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Wed, 12 Jun 2024 23:07:47 +0700 Subject: [PATCH 52/72] chore(categories): update categories for waste and content --- cmd/api/main.go | 10 +++++----- internal/article/dto.go | 8 ++++---- internal/article/entity.go | 13 +++++++++++-- internal/article/repository/repository.go | 18 +++++++++--------- internal/database/database.go | 3 ++- internal/database/migrate.go | 1 + internal/database/mysql.go | 19 +++++++++++++++++-- 7 files changed, 49 insertions(+), 23 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 3a7116d..60b2d88 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -39,8 +39,11 @@ func main() { // Init About us db.InitAboutUs() - // Init Article Categories - db.InitArticleCategory() + // Init Waste Categories + db.InitWasteCategories() + + // Init Content Categories + db.InitContentCategories() // Init Article db.InitArticle() @@ -48,9 +51,6 @@ func main() { // Init Videos db.InitDataVideos() - // Init Article Categories - db.InitArticleCategory() - // Init Article db.InitArticle() diff --git a/internal/article/dto.go b/internal/article/dto.go index 5e41cab..72f7a4b 100644 --- a/internal/article/dto.go +++ b/internal/article/dto.go @@ -40,10 +40,10 @@ type ArticleDetail struct { ThumbnailURL string `json:"thumbnail_url"` CreatedAt time.Time `json:"created_at"` - WasteCategories []WasteCategory `json:"waste_categories"` - ContentCategories []VideoCategory `json:"content_categories"` - Sections []ArticleSection `json:"sections"` - Comments []CommentDetail `json:"comments"` + WasteCategories []WasteCategory `json:"waste_categories"` + ContentCategories []ContentCategory `json:"content_categories"` + Sections []ArticleSection `json:"sections"` + Comments []CommentDetail `json:"comments"` } type CommentInput struct { diff --git a/internal/article/entity.go b/internal/article/entity.go index 7cfbc93..17a9e1b 100644 --- a/internal/article/entity.go +++ b/internal/article/entity.go @@ -32,7 +32,7 @@ type WasteCategory struct { DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` } -type VideoCategory struct { +type ContentCategory struct { ID uint `json:"id" gorm:"primaryKey"` Name string `json:"name" gorm:"type:varchar(50);unique;not null"` @@ -41,6 +41,15 @@ type VideoCategory struct { DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` } +// type VideoCategory struct { +// ID uint `json:"id" gorm:"primaryKey"` +// Name string `json:"name" gorm:"type:varchar(50);unique;not null"` + +// CreatedAt time.Time `json:"-"` +// UpdatedAt time.Time `json:"-"` +// DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +// } + type ArticleCategories struct { ID uint `gorm:"primaryKey"` ArticleID string `gorm:"type:varchar(7)"` @@ -87,7 +96,7 @@ type ArticleRepository interface { Delete(articleID string) error // Category Repository - FindCategories(articleID string) (*[]WasteCategory, *[]VideoCategory, error) + FindCategories(articleID string) (*[]WasteCategory, *[]ContentCategory, error) FindCategoryByName(categoryName, categoryType string) (uint, error) // Article Section Repository diff --git a/internal/article/repository/repository.go b/internal/article/repository/repository.go index b73066d..213f2d4 100644 --- a/internal/article/repository/repository.go +++ b/internal/article/repository/repository.go @@ -76,8 +76,8 @@ func (r *articleRepository) FindByKeyword(keyword string) (*[]art.Article, error Preload("Categories"). Joins("LEFT JOIN article_categories ON articles.id = article_categories.article_id"). Joins("LEFT JOIN waste_categories ON article_categories.waste_category_id = waste_categories.id"). - Joins("LEFT JOIN video_categories ON article_categories.content_category_id = video_categories.id"). - Where("articles.title LIKE ? OR articles.description LIKE ? OR waste_categories.name LIKE ? OR video_categories.name LIKE ?", query, query, query, query). + Joins("LEFT JOIN content_categories ON article_categories.content_category_id = content_categories.id"). + Where("articles.title LIKE ? OR articles.description LIKE ? OR waste_categories.name LIKE ? OR content_categories.name LIKE ?", query, query, query, query). Find(&articles).Error; err != nil { return nil, err } @@ -99,8 +99,8 @@ func (r *articleRepository) FindByCategory(categoryName string, categoryType str } else if categoryType == "content" { if err := r.DB.GetDB().Preload("Categories"). Joins("JOIN article_categories ON articles.id = article_categories.article_id"). - Joins("JOIN video_categories ON article_categories.content_category_id = video_categories.id"). - Where("video_categories.name = ?", categoryName). + Joins("JOIN content_categories ON article_categories.content_category_id = content_categories.id"). + Where("content_categories.name = ?", categoryName). Find(&articles).Error; err != nil { return nil, err } @@ -126,10 +126,10 @@ func (r *articleRepository) Delete(articleID string) error { return nil } -func (r *articleRepository) FindCategories(articleID string) (*[]art.WasteCategory, *[]art.VideoCategory, error) { +func (r *articleRepository) FindCategories(articleID string) (*[]art.WasteCategory, *[]art.ContentCategory, error) { var articleCategories []art.ArticleCategories var wasteCategories []art.WasteCategory - var contentCategories []art.VideoCategory + var contentCategories []art.ContentCategory if err := r.DB.GetDB().Where("article_id = ?", articleID).Find(&articleCategories).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -225,12 +225,12 @@ func (r *articleRepository) FindCategoryByName(categoryName, categoryType string } if categoryType == "content" { - var videoCategory art.VideoCategory - if err := r.DB.GetDB().Where("name = ?", categoryName).First(&videoCategory).Error; err != nil { + var contentCategory art.ContentCategory + if err := r.DB.GetDB().Where("name = ?", categoryName).First(&contentCategory).Error; err != nil { return 0, err } - return videoCategory.ID, nil + return contentCategory.ID, nil } return 0, pkg.ErrCategoryArticleNotFound diff --git a/internal/database/database.go b/internal/database/database.go index 30cc294..84bda65 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -13,8 +13,9 @@ type Database interface { InitAchievements() InitAboutUs() InitDataVideos() - InitArticleCategory() InitArticle() InitTrashCategoryVideo() InitVideoCategories() + InitWasteCategories() + InitContentCategories() } diff --git a/internal/database/migrate.go b/internal/database/migrate.go index 60d7b49..04578b2 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -37,6 +37,7 @@ func AutoMigrate(db Database) { &aboutus.AboutUs{}, &aboutus.AboutUsImage{}, &article.WasteCategory{}, + &article.ContentCategory{}, &article.Article{}, &article.ArticleSection{}, &article.ArticleCategories{}, diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 0c38f2b..26eb0e6 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -345,7 +345,7 @@ func (m *mysqlDatabase) InitAboutUs() { log.Println("About-us data added!") } -func (m *mysqlDatabase) InitArticleCategory() { +func (m *mysqlDatabase) InitWasteCategories() { categories := []article.WasteCategory{ {ID: 1, Name: "plastik"}, {ID: 2, Name: "besi"}, @@ -365,7 +365,22 @@ func (m *mysqlDatabase) InitArticleCategory() { for _, category := range categories { m.GetDB().FirstOrCreate(&category, category) } - log.Println("Article categories data added!") + log.Println("Waste categories data added!") +} + +func (m *mysqlDatabase) InitContentCategories() { + categories := []article.ContentCategory{ + {ID: 1, Name: "tips"}, + {ID: 2, Name: "daur ulang"}, + {ID: 3, Name: "tutorial"}, + {ID: 4, Name: "edukasi"}, + {ID: 5, Name: "kampanye"}, + } + + for _, category := range categories { + m.GetDB().FirstOrCreate(&category, category) + } + log.Println("Content categories data added!") } func (m *mysqlDatabase) InitTrashCategoryVideo() { From f0a432844a291cf8edf9b3b338008455e920065b Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Wed, 12 Jun 2024 23:50:19 +0700 Subject: [PATCH 53/72] chore(about-us): update dummy data for about-us --- internal/about-us/dto.go | 1 + internal/about-us/entity.go | 1 + internal/about-us/usecase/usecase.go | 1 + internal/database/mysql.go | 58 +++++++++++++++++----------- 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/internal/about-us/dto.go b/internal/about-us/dto.go index 0128b5c..3574d69 100644 --- a/internal/about-us/dto.go +++ b/internal/about-us/dto.go @@ -10,5 +10,6 @@ type AboutUsResponse struct { type AboutUsImageResponse struct { AboutUsID string `json:"about_us_id"` + Name string `json:"name"` ImageURL string `json:"image_url"` } diff --git a/internal/about-us/entity.go b/internal/about-us/entity.go index d296300..a83e5d8 100644 --- a/internal/about-us/entity.go +++ b/internal/about-us/entity.go @@ -23,6 +23,7 @@ type AboutUs struct { type AboutUsImage struct { ID string `json:"id" gorm:"primaryKey"` AboutUsID string `json:"about_us_id"` + Name string `json:"name"` ImageURL string `json:"image_url"` CreatedAt time.Time `json:"-"` diff --git a/internal/about-us/usecase/usecase.go b/internal/about-us/usecase/usecase.go index bc43a89..27a86f3 100644 --- a/internal/about-us/usecase/usecase.go +++ b/internal/about-us/usecase/usecase.go @@ -28,6 +28,7 @@ func (uc *aboutUsUsecase) GetAboutUsByCategory(categoryName string) (*[]au.About for _, image := range *images { respImage := au.AboutUsImageResponse{ AboutUsID: image.AboutUsID, + Name: image.Name, ImageURL: image.ImageURL, } diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 26eb0e6..a1abd71 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -297,16 +297,18 @@ func (m *mysqlDatabase) InitAboutUs() { {ID: "ABS04", Category: "perusahaan", Title: "Pelayanan Pelanggan Unggul", Description: "Tim ahli yang berpengalaman memberikan solusi tepat dan responsif sesuai dengan kebutuhan klien."}, {ID: "ABS05", Category: "perusahaan", Title: "Pendidikan Masyarakat", Description: "Berperan aktif dalam mendidik masyarakat tentang pentingnya daur ulang dan pengelolaan limbah yang berkelanjutan."}, - {ID: "ABS06", Category: "tim", Title: "Tim Manajemen", Description: "Lorem ipsum dolor sit amet consectetur. Faucibus ultricies neque pellentesque tempus eros nulla ultrices laoreet. Posuere placerat cras fames egestas. Turpis odio molestie nec viverra nam justo risus. Suspendisse eget id hac diam faucibus adipiscing."}, - {ID: "ABS07", Category: "tim", Title: "Tim Manajemen", Description: "Lorem ipsum dolor sit amet consectetur. Faucibus ultricies neque pellentesque tempus eros nulla ultrices laoreet. Posuere placerat cras fames egestas. Turpis odio molestie nec viverra nam justo risus. Suspendisse eget id hac diam faucibus adipiscing."}, - {ID: "ABS08", Category: "tim", Title: "Tim Manajemen", Description: "Lorem ipsum dolor sit amet consectetur. Faucibus ultricies neque pellentesque tempus eros nulla ultrices laoreet. Posuere placerat cras fames egestas. Turpis odio molestie nec viverra nam justo risus. Suspendisse eget id hac diam faucibus adipiscing."}, - {ID: "ABS09", Category: "tim", Title: "Tim Manajemen", Description: "Lorem ipsum dolor sit amet consectetur. Faucibus ultricies neque pellentesque tempus eros nulla ultrices laoreet. Posuere placerat cras fames egestas. Turpis odio molestie nec viverra nam justo risus. Suspendisse eget id hac diam faucibus adipiscing."}, + {ID: "ABS06", Category: "tim", Title: "Tim UI/UX", Description: "Tim UI/UX kami adalah kekuatan kreatif yang memastikan RecyThing intuitif dan ramah pengguna. Mereka berdedikasi untuk merancang interaksi yang mulus dan interface yang menarik secara visual untuk meningkatkan pengalaman pengguna. Komitmen mereka dalam memahami kebutuhan pengguna mendorong perbaikan berkelanjutan dari desain aplikasi kami."}, + {ID: "ABS07", Category: "tim", Title: "Tim Mobile", Description: "Tim Mobile Development kami membawa RecyThing ke genggaman Anda. Mereka berspesialisasi dalam menciptakan aplikasi mobile yang mulus dan responsif. Keahlian mereka memastikan bahwa aplikasi kami berfungsi dengan sempurna di berbagai perangkat, memberikan pengguna alat yang andal dan efisien untuk kebutuhan daur ulang mereka."}, + {ID: "ABS08", Category: "tim", Title: "Tim Front End", Description: "Tim Front-End Development kami bertanggung jawab untuk menerjemahkan desain kami menjadi interface yang fungsional. Mereka bekerja dengan teknologi web terbaru untuk menciptakan pengalaman pengguna yang cepat dan menarik. Perhatian mereka terhadap detail memastikan bahwa setiap aspek aplikasi konsisten secara visual dan beroperasi dengan lancar."}, + {ID: "ABS09", Category: "tim", Title: "Tim Back End", Description: "Tim Back-End kami membangun infrastruktur kuat yang mendukung RecyThing. Mereka mengembangkan dan memelihara logika sisi server, basis data, dan integrasi yang memastikan aplikasi kami berjalan efisien dan aman. Pekerjaan mereka menjamin bahwa data pengguna ditangani dengan sangat hati-hati dan bahwa kinerja aplikasi tetap optimal."}, + {ID: "ABS10", Category: "tim", Title: "Tim Data Engineer", Description: "Tim Data Engineer di RecyThing memanfaatkan kekuatan data untuk mendorong pengambilan keputusan yang terinformasi. Mereka mengelola pengumpulan, pemrosesan, dan analisis data untuk mengoptimalkan solusi daur ulang kami. Wawasan mereka membantu kami memahami perilaku pengguna, meningkatkan layanan kami, dan berkontribusi pada masa depan yang lebih berkelanjutan."}, + {ID: "ABS11", Category: "tim", Title: "Tim Quality Engineer", Description: "Tim Quality Engineer kami berkomitmen untuk menjaga standar tertinggi dalam hal keandalan dan kinerja. Mereka melakukan pengujian yang ketat untuk mengidentifikasi dan menyelesaikan masalah apa pun, memastikan bahwa aplikasi kami kuat dan aman. Dedikasi mereka terhadap jaminan kualitas memastikan bahwa RecyThing memberikan pengalaman yang mulus dan bebas hambatan bagi semua pengguna."}, - {ID: "ABS10", Category: "contact_us", Title: "Hubungi Kami", Description: "Jika Anda memiliki pertanyaan, masukan, atau ingin bermitra dengan kami, jangan ragu untuk menghubungi tim kami. Kami siap membantu Anda dengan segala kebutuhan terkait daur ulang dan pengelolaan limbah."}, - {ID: "ABS11", Category: "contact_us", Title: "Alamat Kantor", Description: "Recything\nJalan Mangga Dua\nJakarta Pusat, 20012\nIndonesia"}, - {ID: "ABS12", Category: "contact_us", Title: "Jam Operasional", Description: "Senin-Jumat: 08.00 - 17.00 WIB"}, - {ID: "ABS13", Category: "contact_us", Title: "Telepon", Description: "+6289511223344"}, - {ID: "ABS14", Category: "contact_us", Title: "Social Media", Description: "Facebook: https://facebook.com/recything\nTwitter: https://x.com/recything\nInstagram: https://instagram.com/recything\nLinkedin: https://linkedin.com/recything"}, + {ID: "ABS12", Category: "contact_us", Title: "Hubungi Kami", Description: "Jika Anda memiliki pertanyaan, masukan, atau ingin bermitra dengan kami, jangan ragu untuk menghubungi tim kami. Kami siap membantu Anda dengan segala kebutuhan terkait daur ulang dan pengelolaan limbah."}, + {ID: "ABS13", Category: "contact_us", Title: "Alamat Kantor", Description: "Recything\nJalan Mangga Dua\nJakarta Pusat, 20012\nIndonesia"}, + {ID: "ABS14", Category: "contact_us", Title: "Jam Operasional", Description: "Senin-Jumat: 08.00 - 17.00 WIB"}, + {ID: "ABS15", Category: "contact_us", Title: "Telepon", Description: "+6289511223344"}, + {ID: "ABS16", Category: "contact_us", Title: "Social Media", Description: "Facebook: https://facebook.com/recything\nTwitter: https://x.com/recything\nInstagram: https://instagram.com/recything\nLinkedin: https://linkedin.com/recything"}, } aboutUsImages := []aboutus.AboutUsImage{ @@ -315,23 +317,35 @@ func (m *mysqlDatabase) InitAboutUs() { {ID: "ABSI02", AboutUsID: "ABS05", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758301/recything/about-us/spgrokvm9un0yq5zsycn.png"}, {ID: "ABSI03", AboutUsID: "ABS05", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758301/recything/about-us/tynymzulgmkwiqu4a7mb.png"}, - {ID: "ABSI04", AboutUsID: "ABS06", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, - {ID: "ABSI05", AboutUsID: "ABS06", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, - {ID: "ABSI06", AboutUsID: "ABS06", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, + {ID: "ABSI04", AboutUsID: "ABS06", Name: "Hadyan Alhafizh", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/uiux-1.png"}, + {ID: "ABSI05", AboutUsID: "ABS06", Name: "Leonita Puteri Kurniawan", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/uiux-2.png"}, + {ID: "ABSI06", AboutUsID: "ABS06", Name: "Afni Kurnia Herawati", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/uiux-3.png"}, + {ID: "ABSI07", AboutUsID: "ABS06", Name: "Adillah Bulan Suci", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/uiux-4.png"}, + {ID: "ABSI08", AboutUsID: "ABS06", Name: "Ana Nestania", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/uiux-5.png"}, + {ID: "ABSI09", AboutUsID: "ABS06", Name: "Addina Khairinisa", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/uiux-6.png"}, - {ID: "ABSI07", AboutUsID: "ABS07", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, - {ID: "ABSI08", AboutUsID: "ABS07", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, - {ID: "ABSI09", AboutUsID: "ABS07", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, + {ID: "ABSI10", AboutUsID: "ABS07", Name: "Aulia Heppy Cahya S.", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/mobile-1.png"}, + {ID: "ABSI11", AboutUsID: "ABS07", Name: "Fadhl Al-Hafizh", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/mobile-2.png"}, + {ID: "ABSI12", AboutUsID: "ABS07", Name: "Zulfan Faizun Nazib", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/mobile-3.png"}, + {ID: "ABSI13", AboutUsID: "ABS07", Name: "Muhammad Maulana Givari", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/mobile-4.png"}, + {ID: "ABSI14", AboutUsID: "ABS07", Name: "Aflah Alifuna M. R.", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/mobile-5.png"}, - {ID: "ABSI10", AboutUsID: "ABS08", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, - {ID: "ABSI11", AboutUsID: "ABS08", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, - {ID: "ABSI12", AboutUsID: "ABS08", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, + {ID: "ABSI15", AboutUsID: "ABS08", Name: "Nauval Fahreza Attamimi", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/frontend-1.png"}, + {ID: "ABSI16", AboutUsID: "ABS08", Name: "Yohannes Rahul Rafael", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/frontend-2.png"}, + {ID: "ABSI17", AboutUsID: "ABS08", Name: "Naufal Yusuf Fauzan", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/frontend-3.png"}, + {ID: "ABSI18", AboutUsID: "ABS08", Name: "Novia Dwi Lestari", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/frontend-4.png"}, - {ID: "ABSI13", AboutUsID: "ABS09", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, - {ID: "ABSI14", AboutUsID: "ABS09", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, - {ID: "ABSI15", AboutUsID: "ABS09", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/qwck5a9jwqht6rdjmxwd.png"}, + {ID: "ABSI19", AboutUsID: "ABS09", Name: "Muhammad Shahwal R. B", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/backend-1.png"}, + {ID: "ABSI20", AboutUsID: "ABS09", Name: "Markus Rabin Ronaldo", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/backend-2.png"}, - {ID: "ABSI16", AboutUsID: "ABS10", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/mfi5xij2xssmztqwaybz.png"}, + {ID: "ABSI21", AboutUsID: "ABS10", Name: "Yazid Ahmad Hisyam", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/data-1.png"}, + {ID: "ABSI22", AboutUsID: "ABS10", Name: "Daffa Alfahryan Syuja Syaehu", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/data-2.png"}, + {ID: "ABSI23", AboutUsID: "ABS10", Name: "Afril Istihawa", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/data-3.png"}, + + {ID: "ABSI24", AboutUsID: "ABS11", Name: "Ismy Fana Fillah", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/quality-1.png"}, + {ID: "ABSI25", AboutUsID: "ABS11", Name: "Ardelia Syahira Yudiva", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1718207346/recything/about-us/tim/quality-2.png"}, + + {ID: "ABSI26", AboutUsID: "ABS12", ImageURL: "https://res.cloudinary.com/dlbbsdd3a/image/upload/v1717758300/recything/about-us/mfi5xij2xssmztqwaybz.png"}, } for _, about := range aboutUs { From ad210d81751ceb914517699942fd8f1df69d96bc Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Thu, 13 Jun 2024 00:47:30 +0700 Subject: [PATCH 54/72] chore(categories): add endpoint for get all categories --- docs/swagger.yml | 47 +++++++++++++++++++++++ internal/article/dto.go | 5 +++ internal/article/entity.go | 8 ++++ internal/article/handler/handler.go | 15 ++++++-- internal/article/repository/repository.go | 15 ++++++++ internal/article/usecase/usecase.go | 12 ++++++ internal/server/route.go | 3 ++ 7 files changed, 102 insertions(+), 3 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index f8b0513..f6f713e 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -50,6 +50,9 @@ tags: - name: leaderboards description: Endpoint for leaderboards + - name: categories + description: Endpoint for categories + paths: /register: @@ -4607,6 +4610,50 @@ paths: '404': description: Article not found + /categories: + get: + tags: + - categories + summary: Get all categories for content + description: Endpoint user/admin to get data categories + operationId: getCategories + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + code: + type: integer + example: 200 + message: + type: string + example: ok + data: + type: object + properties: + waste_categories: + type: array + items: + properties: + id: + type: integer + example: 1 + name: + type: string + example: plastik + content_categories: + type: array + items: + properties: + id: + type: integer + example: 1 + name: + type: string + example: tips components: schemas: User: diff --git a/internal/article/dto.go b/internal/article/dto.go index 72f7a4b..1348d4b 100644 --- a/internal/article/dto.go +++ b/internal/article/dto.go @@ -66,3 +66,8 @@ type ArticleResponsePagination struct { Limit uint `json:"limit"` Articles []ArticleDetail `json:"articles"` } + +type CategoriesResponse struct { + WasteCategories []WasteCategory `json:"waste_categories"` + ContentCategories []ContentCategory `json:"content_categories"` +} diff --git a/internal/article/entity.go b/internal/article/entity.go index 17a9e1b..f3ab5c5 100644 --- a/internal/article/entity.go +++ b/internal/article/entity.go @@ -113,6 +113,9 @@ type ArticleRepository interface { // Article Comment Repository CreateArticleComment(comment ArticleComment) error DeleteAllArticleComment(articleID string) error + + // Waste Category & Content Category + FindAllCategories() (*[]WasteCategory, *[]ContentCategory, error) } type ArticleUsecase interface { @@ -132,6 +135,9 @@ type ArticleUsecase interface { NewArticleComment(comment CommentInput) error GetDetailUser(userID string) (*UserDetail, error) GetDetailComments(comments []ArticleComment) (*[]CommentDetail, error) + + // Waste Category & Content Category + GetAllCategories() (*CategoriesResponse, error) } type ArticleHandler interface { @@ -145,4 +151,6 @@ type ArticleHandler interface { NewArticleComment(c echo.Context) error ArticleUploadImage(c echo.Context) error + + GetAllCategories(c echo.Context) error } diff --git a/internal/article/handler/handler.go b/internal/article/handler/handler.go index 283e4bd..e377f3d 100644 --- a/internal/article/handler/handler.go +++ b/internal/article/handler/handler.go @@ -103,7 +103,7 @@ func (h *articleHandler) GetAllArticle(c echo.Context) error { response, err := h.usecase.GetAllArticle(page, limit, sortBy, sortType) if err != nil { - helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) } return helper.ResponseHandler(c, http.StatusOK, "ok", response) @@ -114,7 +114,7 @@ func (h *articleHandler) GetArticleByKeyword(c echo.Context) error { response, err := h.usecase.GetArticleByKeyword(keyword) if err != nil { - helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) } return helper.ResponseHandler(c, http.StatusOK, "ok", response) @@ -126,7 +126,7 @@ func (h *articleHandler) GetArticleByCategory(c echo.Context) error { response, err := h.usecase.GetArticleByCategory(categoryName, categoryType) if err != nil { - helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) } return helper.ResponseHandler(c, http.StatusOK, "ok", response) @@ -204,3 +204,12 @@ func (h *articleHandler) ArticleUploadImage(c echo.Context) error { "image_url": resp, }) } + +func (h *articleHandler) GetAllCategories(c echo.Context) error { + response, err := h.usecase.GetAllCategories() + if err != nil { + return helper.ErrorHandler(c, http.StatusInternalServerError, err.Error()) + } + + return helper.ResponseHandler(c, http.StatusOK, "ok", response) +} diff --git a/internal/article/repository/repository.go b/internal/article/repository/repository.go index 213f2d4..dbfbb0d 100644 --- a/internal/article/repository/repository.go +++ b/internal/article/repository/repository.go @@ -251,3 +251,18 @@ func (r *articleRepository) DeleteAllArticleComment(articleID string) error { return nil } + +func (r *articleRepository) FindAllCategories() (*[]art.WasteCategory, *[]art.ContentCategory, error) { + var wasteCategories []art.WasteCategory + var contentCategories []art.ContentCategory + + if err := r.DB.GetDB().Model(&art.WasteCategory{}).Find(&wasteCategories).Error; err != nil { + return nil, nil, err + } + + if err := r.DB.GetDB().Model(&art.ContentCategory{}).Find(&contentCategories).Error; err != nil { + return nil, nil, err + } + + return &wasteCategories, &contentCategories, nil +} diff --git a/internal/article/usecase/usecase.go b/internal/article/usecase/usecase.go index a2c0a58..e34868a 100644 --- a/internal/article/usecase/usecase.go +++ b/internal/article/usecase/usecase.go @@ -320,3 +320,15 @@ func (uc *articleUsecase) GetDetailComments(comments []art.ArticleComment) (*[]a return &commentDetails, nil } + +func (uc *articleUsecase) GetAllCategories() (*art.CategoriesResponse, error) { + wasteCategories, contentCategories, err := uc.articleRepo.FindAllCategories() + if err != nil { + return nil, err + } + + return &art.CategoriesResponse{ + WasteCategories: *wasteCategories, + ContentCategories: *contentCategories, + }, nil +} diff --git a/internal/server/route.go b/internal/server/route.go index 153a734..e96af86 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -415,6 +415,9 @@ func (s *echoServer) articleHandler() { // Upload image s.gr.POST("/article/upload", handler.ArticleUploadImage, SuperAdminOrAdminMiddleware) + + // Get all categories + s.gr.GET("/categories", handler.GetAllCategories) } func (s *echoServer) homepageHandler() { From 1da4d383862e42c11a3f8f3786ed1d16e29ab05a Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 13:30:18 +0700 Subject: [PATCH 55/72] refactor(manage video): change entity and change some code on create and update video --- cmd/api/main.go | 6 - internal/database/database.go | 2 - internal/database/migrate.go | 1 - internal/database/mysql.go | 100 ++++++--------- .../manage_video/dto/manage_video_request.go | 16 +-- .../manage_video/dto/manage_video_response.go | 17 ++- .../entity/manage_video_entity.go | 49 ++++--- .../handler/manage_video_handler_impl.go | 40 +++--- .../repository/manage_video_repository.go | 8 +- .../manage_video_repository_impl.go | 47 +++---- .../usecase/manage_video_usecase_impl.go | 120 ++++++++++-------- .../handler/user_video_handler_impl.go | 70 ++++++---- .../repository/user_video_repository_impl.go | 55 ++++++-- 13 files changed, 285 insertions(+), 246 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 60b2d88..328a7b8 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -54,12 +54,6 @@ func main() { // Init Article db.InitArticle() - // Init Video Categories - db.InitVideoCategories() - - // Init Trash Category Video - db.InitTrashCategoryVideo() - app := server.NewEchoServer(conf, db) c := cron.New() diff --git a/internal/database/database.go b/internal/database/database.go index 84bda65..cde34f8 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -14,8 +14,6 @@ type Database interface { InitAboutUs() InitDataVideos() InitArticle() - InitTrashCategoryVideo() - InitVideoCategories() InitWasteCategories() InitContentCategories() } diff --git a/internal/database/migrate.go b/internal/database/migrate.go index 04578b2..080f2a1 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -41,7 +41,6 @@ func AutoMigrate(db Database) { &article.Article{}, &article.ArticleSection{}, &article.ArticleCategories{}, - &video.TrashCategory{}, &article.ArticleComment{}, ); err != nil { log.Fatal("Database Migration Failed!") diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 26eb0e6..7c8b7f5 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -252,43 +252,6 @@ func (m *mysqlDatabase) InitAchievements() { log.Println("Dummy Achievements added!") } -func (m *mysqlDatabase) InitDataVideos() { - videos := []video.Video{ - { - ID: 1, - Title: "Daur Ulang", - Description: "Tips Daur Ulang", - Link: "https://www.youtube.com/watch?v=MJd3bo_XRaU", - }, - { - ID: 2, - Title: "Tutorial Bernapas", - Description: "Tutorial Bernapas Bagi Pemula", - Link: "https://www.youtube.com/watch?v=jp5uhrdhsKI", - }, - } - - for _, video := range videos { - m.GetDB().FirstOrCreate(&video, video) - } - - log.Println("Video data added!") -} - -func (m *mysqlDatabase) InitVideoCategories() { - videoCategories := []video.VideoCategory{ - {Name: "tips", VideoID: 1}, - {Name: "daur ulang", VideoID: 1}, - {Name: "tutorial", VideoID: 1}, - {Name: "edukasi", VideoID: 2}, - {Name: "kampanye", VideoID: 2}, - } - for _, videoCategory := range videoCategories { - m.GetDB().FirstOrCreate(&videoCategory, videoCategory) - } - log.Println("Video categories data added!") -} - func (m *mysqlDatabase) InitAboutUs() { aboutUs := []aboutus.AboutUs{ {ID: "ABS01", Category: "perusahaan", Title: "Tentang siapa kami", Description: "RecyThing adalah pemimpin di industri daur ulang sampah yang berkomitmen untuk menjaga lingkungan hidup yang lebih bersih dan lebih berkelanjutan."}, @@ -383,29 +346,6 @@ func (m *mysqlDatabase) InitContentCategories() { log.Println("Content categories data added!") } -func (m *mysqlDatabase) InitTrashCategoryVideo() { - trashCategories := []video.TrashCategory{ - {ID: 1, Name: "plastik", VideoID: 1}, - {ID: 2, Name: "besi", VideoID: 1}, - {ID: 3, Name: "kaca", VideoID: 1}, - {ID: 4, Name: "organik", VideoID: 1}, - {ID: 5, Name: "kayu", VideoID: 1}, - {ID: 6, Name: "kertas", VideoID: 1}, - {ID: 7, Name: "baterai", VideoID: 2}, - {ID: 8, Name: "kaleng", VideoID: 2}, - {ID: 9, Name: "elektronik", VideoID: 2}, - {ID: 10, Name: "tekstil", VideoID: 2}, - {ID: 11, Name: "minyak", VideoID: 2}, - {ID: 12, Name: "bola lampu", VideoID: 2}, - {ID: 13, Name: "berbahaya", VideoID: 2}, - } - - for _, category := range trashCategories { - m.GetDB().FirstOrCreate(&category, category) - } - log.Println("Trash categories data added!") -} - func (m *mysqlDatabase) InitArticle() { articles := []article.Article{ {ID: "ART0001", Title: "Cara Mendaur Ulang Botol Plastik", Description: "Panduan langkah demi langkah tentang cara mendaur ulang botol plastik di rumah.", ThumbnailURL: "https://example.com/daur-ulang-plastik.jpg", AuthorID: "AD0001"}, @@ -443,6 +383,46 @@ func (m *mysqlDatabase) InitArticle() { log.Println("Article data added!") } +func (m *mysqlDatabase) InitDataVideos() { + videos := []video.Video{ + { + ID: 1, + Title: "Daur Ulang", + Description: "Tips Daur Ulang", + Link: "https://www.youtube.com/watch?v=MJd3bo_XRaU", + }, + { + ID: 2, + Title: "Tutorial Bernapas", + Description: "Tutorial Bernapas Bagi Pemula", + Link: "https://www.youtube.com/watch?v=jp5uhrdhsKI", + }, + } + + videoCategory := []video.VideoCategory{ + { + VideoID: 1, + ContentCategoryID: 1, + WasteCategoryID: 1, + }, + { + VideoID: 2, + ContentCategoryID: 2, + WasteCategoryID: 2, + }, + } + + for _, video := range videoCategory { + m.GetDB().FirstOrCreate(&video, video) + } + + for _, video := range videos { + m.GetDB().FirstOrCreate(&video, video) + } + + log.Println("Video data added!") +} + func (m *mysqlDatabase) GetDB() *gorm.DB { return dbInstance.DB } diff --git a/internal/video/manage_video/dto/manage_video_request.go b/internal/video/manage_video/dto/manage_video_request.go index f21bbc3..356cfc4 100644 --- a/internal/video/manage_video/dto/manage_video_request.go +++ b/internal/video/manage_video/dto/manage_video_request.go @@ -6,8 +6,8 @@ type CreateDataVideoRequest struct { Title string `json:"title" validate:"required"` Description string `json:"description" validate:"required"` LinkVideo string `json:"link_video" validate:"required"` - VideoCategories []DataCategoryVideo `json:"content_categories" validate:"required"` - TrashCategories []DataTrashCategory `json:"waste_categories" validate:"required"` + ContentCategory []DataCategoryVideo `json:"content_categories" validate:"required"` + WasteCategory []DataTrashCategory `json:"waste_categories" validate:"required"` Thumbnail *multipart.FileHeader `json:"-"` } @@ -24,10 +24,10 @@ type CreateCategoryVideoRequest struct { } type UpdateDataVideoRequest struct { - Title string `json:"title"` - Description string `json:"description"` - LinkVideo string `json:"link_video"` - VideoCategories []DataCategoryVideo `json:"content_categories"` - TrashCategories []DataTrashCategory `json:"waste_categories"` - Thumbnail *multipart.FileHeader `json:"-"` + Title string `json:"title"` + Description string `json:"description"` + LinkVideo string `json:"link_video"` + ContentCategories []DataCategoryVideo `json:"content_categories"` + WasteCategories []DataTrashCategory `json:"waste_categories"` + Thumbnail *multipart.FileHeader `json:"-"` } diff --git a/internal/video/manage_video/dto/manage_video_response.go b/internal/video/manage_video/dto/manage_video_response.go index 3e3b6ea..7fa663f 100644 --- a/internal/video/manage_video/dto/manage_video_response.go +++ b/internal/video/manage_video/dto/manage_video_response.go @@ -10,11 +10,6 @@ type DataVideoCategory struct { Name string `json:"name"` } -type DataTrashCategoryResponse struct { - Id int `json:"id"` - Name string `json:"name"` -} - type DataVideo struct { Id int `json:"id"` Title string `json:"title"` @@ -32,6 +27,16 @@ type GetAllDataVideoPaginationResponse struct { TotalPage int `json:"total_page"` } +type DataCategoryVideoResponse struct { + Id int `json:"id"` + Name string `json:"name"` +} + +type DataTrashCategoryResponse struct { + Id int `json:"id"` + Name string `json:"name"` +} + type GetDetailsDataVideoByIdResponse struct { Id int `json:"id"` Title string `json:"title"` @@ -39,6 +44,6 @@ type GetDetailsDataVideoByIdResponse struct { UrlThumbnail string `json:"url_thumbnail"` LinkVideo string `json:"link_video"` Viewer int `json:"viewer"` - VideoCategory []*DataVideoCategory `json:"content_categories"` + VideoCategory []*DataCategoryVideoResponse `json:"content_categories"` TrashCategory []*DataTrashCategoryResponse `json:"waste_categories"` } diff --git a/internal/video/manage_video/entity/manage_video_entity.go b/internal/video/manage_video/entity/manage_video_entity.go index b8cac91..633019b 100644 --- a/internal/video/manage_video/entity/manage_video_entity.go +++ b/internal/video/manage_video/entity/manage_video_entity.go @@ -3,40 +3,37 @@ package entity import ( "time" + art "github.com/sawalreverr/recything/internal/article" "github.com/sawalreverr/recything/internal/user" "gorm.io/gorm" ) type Video struct { - ID int `gorm:"primaryKey"` - Title string - Description string - Thumbnail string - Link string - Viewer int - VideoCategories []VideoCategory `gorm:"foreignKey:VideoID;references:ID"` - TrashCategories []TrashCategory `gorm:"foreignKey:VideoID;references:ID"` - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `gorm:"index"` -} - -type TrashCategory struct { - ID int `gorm:"primaryKey"` - VideoID int `gorm:"index"` - Name string - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `gorm:"index"` + ID int `gorm:"primaryKey"` + Title string + Description string + Thumbnail string + Link string + Viewer int + Categories []VideoCategory + ContentCategories []art.ContentCategory `gorm:"-"` + WasteCategories []art.WasteCategory `gorm:"-"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index"` } type VideoCategory struct { - ID int `gorm:"primaryKey"` - VideoID int `gorm:"index"` - Name string - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `gorm:"index"` + ID int `gorm:"primaryKey"` + VideoID int + Video Video `gorm:"foreignKey:VideoID"` + ContentCategoryID uint + ContentCategory art.ContentCategory `gorm:"foreignKey:ContentCategoryID"` + WasteCategoryID uint + WasteCategory art.WasteCategory `gorm:"foreignKey:WasteCategoryID"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index"` } type Comment struct { diff --git a/internal/video/manage_video/handler/manage_video_handler_impl.go b/internal/video/manage_video/handler/manage_video_handler_impl.go index 3ccaf63..313874c 100644 --- a/internal/video/manage_video/handler/manage_video_handler_impl.go +++ b/internal/video/manage_video/handler/manage_video_handler_impl.go @@ -171,6 +171,7 @@ func (handler *ManageVideoHandlerImpl) GetDetailsDataVideoByIdHandler(c echo.Con if errConvert != nil { return helper.ErrorHandler(c, http.StatusBadRequest, "invalid id parameter") } + video, err := handler.ManageVideoUsecase.GetDetailsDataVideoByIdUseCase(idInt) if err != nil { if errors.Is(err, pkg.ErrVideoNotFound) { @@ -178,31 +179,36 @@ func (handler *ManageVideoHandlerImpl) GetDetailsDataVideoByIdHandler(c echo.Con } return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) } - var dataVideo *dto.GetDetailsDataVideoByIdResponse - var dataVideoCategories []*dto.DataVideoCategory - var dataTrashCategories []*dto.DataTrashCategoryResponse - for _, category := range video.VideoCategories { - dataVideoCategories = append(dataVideoCategories, &dto.DataVideoCategory{ - Id: category.ID, - Name: category.Name, - }) - } - for _, category := range video.TrashCategories { - dataTrashCategories = append(dataTrashCategories, &dto.DataTrashCategoryResponse{ - Id: category.ID, - Name: category.Name, - }) + + var dataContentCategories []*dto.DataCategoryVideoResponse + var dataWasteCategories []*dto.DataTrashCategoryResponse + + for _, category := range video.Categories { + if category.ContentCategoryID != 0 { + dataContentCategories = append(dataContentCategories, &dto.DataCategoryVideoResponse{ + Id: int(category.ContentCategory.ID), + Name: category.ContentCategory.Name, + }) + } + if category.WasteCategoryID != 0 { + dataWasteCategories = append(dataWasteCategories, &dto.DataTrashCategoryResponse{ + Id: int(category.WasteCategory.ID), + Name: category.WasteCategory.Name, + }) + } } - dataVideo = &dto.GetDetailsDataVideoByIdResponse{ + + dataVideo := &dto.GetDetailsDataVideoByIdResponse{ Id: video.ID, Title: video.Title, Description: video.Description, UrlThumbnail: video.Thumbnail, LinkVideo: video.Link, Viewer: video.Viewer, - VideoCategory: dataVideoCategories, - TrashCategory: dataTrashCategories, + VideoCategory: dataContentCategories, + TrashCategory: dataWasteCategories, } + responseData := helper.ResponseData(http.StatusOK, "success", dataVideo) return c.JSON(http.StatusOK, responseData) } diff --git a/internal/video/manage_video/repository/manage_video_repository.go b/internal/video/manage_video/repository/manage_video_repository.go index 4c5661f..a17ec40 100644 --- a/internal/video/manage_video/repository/manage_video_repository.go +++ b/internal/video/manage_video/repository/manage_video_repository.go @@ -1,14 +1,16 @@ package repository import ( + art "github.com/sawalreverr/recything/internal/article" video "github.com/sawalreverr/recything/internal/video/manage_video/entity" ) type ManageVideoRepository interface { - CreateDataVideo(video *video.Video) error + CreateVideoAndCategories(video *video.Video) (*video.Video, error) + CreateVideoCategories(videoCategories []video.VideoCategory) error FindTitleVideo(title string) error - FindNameCategoryVideo(name string) error - FindNamaTrashCategory(name string) error + FindNameCategoryVideo(name string) (*art.ContentCategory, error) + FindNamaTrashCategory(name string) (*art.WasteCategory, error) GetAllCategoryVideo() ([]string, error) GetAllTrashCategoryVideo() ([]string, error) GetCategoryVideoById(id int) (*video.VideoCategory, error) diff --git a/internal/video/manage_video/repository/manage_video_repository_impl.go b/internal/video/manage_video/repository/manage_video_repository_impl.go index 9fcaa16..1902beb 100644 --- a/internal/video/manage_video/repository/manage_video_repository_impl.go +++ b/internal/video/manage_video/repository/manage_video_repository_impl.go @@ -6,6 +6,8 @@ import ( "github.com/sawalreverr/recything/internal/database" video "github.com/sawalreverr/recything/internal/video/manage_video/entity" "gorm.io/gorm" + + art "github.com/sawalreverr/recything/internal/article" ) type ManageVideoRepositoryImpl struct { @@ -16,8 +18,15 @@ func NewManageVideoRepository(db database.Database) *ManageVideoRepositoryImpl { return &ManageVideoRepositoryImpl{DB: db} } -func (repository *ManageVideoRepositoryImpl) CreateDataVideo(video *video.Video) error { +func (repository *ManageVideoRepositoryImpl) CreateVideoAndCategories(video *video.Video) (*video.Video, error) { if err := repository.DB.GetDB().Create(&video).Error; err != nil { + return nil, err + } + return video, nil +} + +func (repository *ManageVideoRepositoryImpl) CreateVideoCategories(videoCategories []video.VideoCategory) error { + if err := repository.DB.GetDB().Create(&videoCategories).Error; err != nil { return err } return nil @@ -31,20 +40,20 @@ func (repository *ManageVideoRepositoryImpl) FindTitleVideo(title string) error return nil } -func (repository *ManageVideoRepositoryImpl) FindNameCategoryVideo(name string) error { - var category video.VideoCategory +func (repository *ManageVideoRepositoryImpl) FindNameCategoryVideo(name string) (*art.ContentCategory, error) { + var category art.ContentCategory if err := repository.DB.GetDB().Where("name = ?", name).First(&category).Error; err != nil { - return err + return nil, err } - return nil + return &category, nil } -func (repository *ManageVideoRepositoryImpl) FindNamaTrashCategory(name string) error { - var category video.TrashCategory +func (repository *ManageVideoRepositoryImpl) FindNamaTrashCategory(name string) (*art.WasteCategory, error) { + var category art.WasteCategory if err := repository.DB.GetDB().Where("name = ?", name).First(&category).Error; err != nil { - return err + return nil, err } - return nil + return &category, nil } func (repository *ManageVideoRepositoryImpl) GetAllCategoryVideo() ([]string, error) { @@ -58,7 +67,7 @@ func (repository *ManageVideoRepositoryImpl) GetAllCategoryVideo() ([]string, er func (repository *ManageVideoRepositoryImpl) GetAllTrashCategoryVideo() ([]string, error) { var categories []string - if err := repository.DB.GetDB().Model(&video.TrashCategory{}).Distinct("name").Pluck("name", &categories). + if err := repository.DB.GetDB().Model(&video.Video{}).Distinct("name").Pluck("name", &categories). Error; err != nil { return nil, err } @@ -90,8 +99,8 @@ func (repository *ManageVideoRepositoryImpl) GetAllDataVideoPagination(limit int func (repository *ManageVideoRepositoryImpl) GetDetailsDataVideoById(id int) (*video.Video, error) { var video video.Video if err := repository.DB.GetDB(). - Preload("VideoCategories"). - Preload("TrashCategories"). + Preload("Categories.ContentCategory"). + Preload("Categories.WasteCategory"). Where("id = ?", id). First(&video).Error; err != nil { return nil, err @@ -100,7 +109,6 @@ func (repository *ManageVideoRepositoryImpl) GetDetailsDataVideoById(id int) (*v } func (repository *ManageVideoRepositoryImpl) UpdateDataVideo(videos *video.Video, id int) error { - tx := repository.DB.GetDB().Begin() defer func() { if r := recover(); r != nil { @@ -109,24 +117,19 @@ func (repository *ManageVideoRepositoryImpl) UpdateDataVideo(videos *video.Video } }() - if len(videos.VideoCategories) > 0 { - if err := tx.Model(&video.VideoCategory{}).Where("video_id = ?", id).Delete(&video.VideoCategory{}).Error; err != nil { + // Delete existing categories if any new ones are provided + if len(videos.Categories) > 0 { + if err := tx.Where("video_id = ?", id).Delete(&video.VideoCategory{}).Error; err != nil { tx.Rollback() return err } } - if len(videos.TrashCategories) > 0 { - if err := tx.Model(&video.TrashCategory{}).Where("video_id = ?", id).Delete(&video.TrashCategory{}).Error; err != nil { - tx.Rollback() - return err - } - - } if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Save(&videos).Error; err != nil { tx.Rollback() return err } + if err := tx.Commit().Error; err != nil { tx.Rollback() return err diff --git a/internal/video/manage_video/usecase/manage_video_usecase_impl.go b/internal/video/manage_video/usecase/manage_video_usecase_impl.go index c46a32a..6c2dfc1 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase_impl.go +++ b/internal/video/manage_video/usecase/manage_video_usecase_impl.go @@ -4,6 +4,7 @@ import ( "mime/multipart" "strings" + art "github.com/sawalreverr/recything/internal/article" "github.com/sawalreverr/recything/internal/helper" "github.com/sawalreverr/recything/internal/video/manage_video/dto" video "github.com/sawalreverr/recything/internal/video/manage_video/entity" @@ -26,10 +27,10 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat if len(thumbnail) == 0 { return pkg.ErrThumbnail } - if len(request.VideoCategories) == 0 { + if len(request.ContentCategory) == 0 { return pkg.ErrVideoCategory } - if len(request.TrashCategories) == 0 { + if len(request.WasteCategory) == 0 { return pkg.ErrVideoTrashCategory } if len(thumbnail) > 1 { @@ -44,31 +45,41 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat return pkg.ErrVideoTitleAlreadyExist } - var videoCategories []video.VideoCategory - var trashCategories []video.TrashCategory + // Memperoleh kategori konten dan limbah dari request + var contentCategories []*art.ContentCategory + var wasteCategories []*art.WasteCategory - for _, category := range request.VideoCategories { + // Menambahkan kategori konten + for _, category := range request.ContentCategory { name := strings.ToLower(category.Name) - if err := usecase.manageVideoRepository.FindNameCategoryVideo(name); err == gorm.ErrRecordNotFound { - return pkg.ErrNameCategoryVideoNotFound - } - videoCategory := video.VideoCategory{ - Name: name, - DeletedAt: gorm.DeletedAt{}, + content, err := usecase.manageVideoRepository.FindNameCategoryVideo(name) + if err != nil { + return pkg.ErrVideoCategory } - videoCategories = append(videoCategories, videoCategory) + contentCategories = append(contentCategories, content) } - for _, category := range request.TrashCategories { + + // Menambahkan kategori limbah + for _, category := range request.WasteCategory { name := strings.ToLower(category.Name) - if err := usecase.manageVideoRepository.FindNamaTrashCategory(name); err == gorm.ErrRecordNotFound { - return pkg.ErrNameTrashCategoryNotFound + waste, err := usecase.manageVideoRepository.FindNamaTrashCategory(name) + if err != nil { + return pkg.ErrVideoTrashCategory } - trashCategory := video.TrashCategory{ - Name: name, - DeletedAt: gorm.DeletedAt{}, + wasteCategories = append(wasteCategories, waste) + } + + // Membuat slice VideoCategory yang akan disimpan + var videoCategories []video.VideoCategory + for _, content := range contentCategories { + for _, waste := range wasteCategories { + videoCategories = append(videoCategories, video.VideoCategory{ + ContentCategoryID: content.ID, + WasteCategoryID: waste.ID, + }) } - trashCategories = append(trashCategories, trashCategory) } + view, errGetView := helper.GetVideoViewCount(request.LinkVideo) if errGetView != nil { return errGetView @@ -78,19 +89,20 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat return pkg.ErrUploadCloudinary } intView := int(view) - video := video.Video{ - Title: request.Title, - Description: request.Description, - Thumbnail: urlThumbnail, - Link: request.LinkVideo, - Viewer: intView, - VideoCategories: videoCategories, - TrashCategories: trashCategories, - DeletedAt: gorm.DeletedAt{}, - } - if err := usecase.manageVideoRepository.CreateDataVideo(&video); err != nil { - return err + videos := video.Video{ + Title: request.Title, + Description: request.Description, + Thumbnail: urlThumbnail, + Link: request.LinkVideo, + Viewer: intView, + Categories: videoCategories, } + + _, errVideo := usecase.manageVideoRepository.CreateVideoAndCategories(&videos) + if errVideo != nil { + return errVideo + } + return nil } @@ -99,11 +111,11 @@ func (usecase *ManageVideoUsecaseImpl) GetAllCategoryVideoUseCase() ([]string, [ if errvidCategory != nil { return nil, nil, errvidCategory } - trashCategories, errTrashCategory := usecase.manageVideoRepository.GetAllTrashCategoryVideo() + contentCatgory, errTrashCategory := usecase.manageVideoRepository.GetAllTrashCategoryVideo() if errTrashCategory != nil { return nil, nil, errTrashCategory } - return videoCategories, trashCategories, nil + return videoCategories, contentCatgory, nil } func (usecase *ManageVideoUsecaseImpl) GetAllDataVideoPaginationUseCase(limit int, page int) ([]video.Video, int, error) { @@ -129,48 +141,48 @@ func (usecase *ManageVideoUsecaseImpl) UpdateDataVideoUseCase(request *dto.Updat if len(thumbnail) > 1 { return pkg.ErrThumbnailMaximum } + dataVideo, err := usecase.manageVideoRepository.GetDetailsDataVideoById(id) if err != nil { return pkg.ErrVideoNotFound } + var videoCategories []video.VideoCategory - var trashCategories []video.TrashCategory - if request.VideoCategories != nil { - for _, category := range request.VideoCategories { + if request.ContentCategories != nil { + for _, category := range request.ContentCategories { name := strings.ToLower(category.Name) - if err := usecase.manageVideoRepository.FindNameCategoryVideo(name); err == gorm.ErrRecordNotFound { + contentCategory, err := usecase.manageVideoRepository.FindNameCategoryVideo(name) + if err == gorm.ErrRecordNotFound { return pkg.ErrNameCategoryVideoNotFound } videoCategory := video.VideoCategory{ - VideoID: id, - Name: name, - DeletedAt: gorm.DeletedAt{}, + VideoID: id, + ContentCategoryID: contentCategory.ID, } videoCategories = append(videoCategories, videoCategory) } - dataVideo.VideoCategories = videoCategories - } else { - videoCategories = dataVideo.VideoCategories + dataVideo.Categories = videoCategories } - if request.TrashCategories != nil { - for _, category := range request.TrashCategories { + var wasteCategories []video.VideoCategory + + if request.WasteCategories != nil { + for _, category := range request.WasteCategories { name := strings.ToLower(category.Name) - if err := usecase.manageVideoRepository.FindNamaTrashCategory(name); err == gorm.ErrRecordNotFound { + wasteCategory, err := usecase.manageVideoRepository.FindNamaTrashCategory(name) + if err == gorm.ErrRecordNotFound { return pkg.ErrNameTrashCategoryNotFound } - trashCategory := video.TrashCategory{ - VideoID: id, - Name: name, - DeletedAt: gorm.DeletedAt{}, + videoCategory := video.VideoCategory{ + VideoID: id, + WasteCategoryID: wasteCategory.ID, } - trashCategories = append(trashCategories, trashCategory) + wasteCategories = append(wasteCategories, videoCategory) } - dataVideo.TrashCategories = trashCategories - } else { - trashCategories = dataVideo.TrashCategories + dataVideo.Categories = append(dataVideo.Categories, wasteCategories...) } + var urlThumbnail string if len(thumbnail) == 1 { validImages, errImages := helper.ImagesValidation(thumbnail) diff --git a/internal/video/user_video/handler/user_video_handler_impl.go b/internal/video/user_video/handler/user_video_handler_impl.go index 70a9dbd..05a4b22 100644 --- a/internal/video/user_video/handler/user_video_handler_impl.go +++ b/internal/video/user_video/handler/user_video_handler_impl.go @@ -25,22 +25,35 @@ func (handler *UserVideoHandlerImpl) GetAllVideoHandler(c echo.Context) error { if err != nil { return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) } + var dataVideo []*dto.DataVideoSearchByCategory for _, video := range *videos { - videoCategories := make([]*dto.DataCategoryVideo, len(video.VideoCategories)) - for i, vc := range video.VideoCategories { - videoCategories[i] = &dto.DataCategoryVideo{ - Id: vc.ID, - Name: vc.Name, + uniqueContentCategories := make(map[uint]*dto.DataCategoryVideo) + uniqueTrashCategories := make(map[uint]*dto.DataTrashCategoryVideo) + + for _, vc := range video.Categories { + if _, exists := uniqueContentCategories[vc.ContentCategoryID]; !exists { + uniqueContentCategories[vc.ContentCategoryID] = &dto.DataCategoryVideo{ + Id: int(vc.ContentCategory.ID), + Name: vc.ContentCategory.Name, + } + } + if _, exists := uniqueTrashCategories[vc.WasteCategoryID]; !exists { + uniqueTrashCategories[vc.WasteCategoryID] = &dto.DataTrashCategoryVideo{ + Id: int(vc.WasteCategory.ID), + Name: vc.WasteCategory.Name, + } } } - trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.TrashCategories)) - for i, tc := range video.TrashCategories { - trashCategories[i] = &dto.DataTrashCategoryVideo{ - Id: tc.ID, - Name: tc.Name, - } + // Convert maps back to slices + var videoCategories []*dto.DataCategoryVideo + for _, vc := range uniqueContentCategories { + videoCategories = append(videoCategories, vc) + } + var trashCategories []*dto.DataTrashCategoryVideo + for _, tc := range uniqueTrashCategories { + trashCategories = append(trashCategories, tc) } dataVideo = append(dataVideo, &dto.DataVideoSearchByCategory{ @@ -67,21 +80,22 @@ func (handler *UserVideoHandlerImpl) SearchVideoByKeywordHandler(c echo.Context) if err != nil { return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) } + var dataVideo []*dto.DataVideoSearchByCategory for _, video := range *videos { - videoCategories := make([]*dto.DataCategoryVideo, len(video.VideoCategories)) - for i, vc := range video.VideoCategories { + videoCategories := make([]*dto.DataCategoryVideo, len(video.Categories)) + for i, vc := range video.Categories { videoCategories[i] = &dto.DataCategoryVideo{ - Id: vc.ID, - Name: vc.Name, + Id: int(vc.ContentCategory.ID), // Use ContentCategory instead of Categories + Name: vc.ContentCategory.Name, } } - trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.TrashCategories)) - for i, tc := range video.TrashCategories { + trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.Categories)) // Assuming trash category is also under ContentCategory + for i, vc := range video.Categories { trashCategories[i] = &dto.DataTrashCategoryVideo{ - Id: tc.ID, - Name: tc.Name, + Id: int(vc.WasteCategory.ID), + Name: vc.WasteCategory.Name, } } @@ -98,7 +112,6 @@ func (handler *UserVideoHandlerImpl) SearchVideoByKeywordHandler(c echo.Context) } responseData := helper.ResponseData(http.StatusOK, "success", dataVideo) - return c.JSON(http.StatusOK, responseData) } @@ -112,21 +125,22 @@ func (handler *UserVideoHandlerImpl) SearchVideoByCategoryHandler(c echo.Context } return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) } + var dataVideo []*dto.DataVideoSearchByCategory for _, video := range *videos { - videoCategories := make([]*dto.DataCategoryVideo, len(video.VideoCategories)) - for i, vc := range video.VideoCategories { + videoCategories := make([]*dto.DataCategoryVideo, len(video.Categories)) + for i, vc := range video.Categories { videoCategories[i] = &dto.DataCategoryVideo{ - Id: vc.ID, - Name: vc.Name, + Id: int(vc.ContentCategory.ID), // Use ContentCategory instead of Categories + Name: vc.ContentCategory.Name, } } - trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.TrashCategories)) - for i, tc := range video.TrashCategories { + trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.Categories)) // Assuming trash category is also under ContentCategory + for i, vc := range video.Categories { trashCategories[i] = &dto.DataTrashCategoryVideo{ - Id: tc.ID, - Name: tc.Name, + Id: int(vc.WasteCategory.ID), + Name: vc.WasteCategory.Name, } } diff --git a/internal/video/user_video/repository/user_video_repository_impl.go b/internal/video/user_video/repository/user_video_repository_impl.go index b3ba6b7..2adc8a7 100644 --- a/internal/video/user_video/repository/user_video_repository_impl.go +++ b/internal/video/user_video/repository/user_video_repository_impl.go @@ -1,6 +1,7 @@ package repository import ( + art "github.com/sawalreverr/recything/internal/article" "github.com/sawalreverr/recything/internal/database" video "github.com/sawalreverr/recything/internal/video/manage_video/entity" "github.com/sawalreverr/recything/pkg" @@ -18,12 +19,36 @@ func (repository *UserVideoRepositoryImpl) GetAllVideo() (*[]video.Video, error) var videos []video.Video if err := repository.DB.GetDB(). Order("created_at desc"). - Preload("VideoCategories"). - Preload("TrashCategories"). + Preload("Categories.ContentCategory"). + Preload("Categories.WasteCategory"). Find(&videos). Error; err != nil { return nil, err } + + for i := range videos { + contentCategorySet := make(map[uint]struct{}) + wasteCategorySet := make(map[uint]struct{}) + + var uniqueContentCategories []art.ContentCategory + var uniqueWasteCategories []art.WasteCategory + + for _, category := range videos[i].Categories { + if _, exists := contentCategorySet[category.ContentCategoryID]; !exists { + contentCategorySet[category.ContentCategoryID] = struct{}{} + uniqueContentCategories = append(uniqueContentCategories, category.ContentCategory) + } + if _, exists := wasteCategorySet[category.WasteCategoryID]; !exists { + wasteCategorySet[category.WasteCategoryID] = struct{}{} + uniqueWasteCategories = append(uniqueWasteCategories, category.WasteCategory) + } + } + + // Assign unique categories back to the video + videos[i].ContentCategories = uniqueContentCategories + videos[i].WasteCategories = uniqueWasteCategories + } + return &videos, nil } @@ -31,14 +56,16 @@ func (repository *UserVideoRepositoryImpl) SearchVideoByKeyword(keyword string) var videos []video.Video if err := repository.DB.GetDB(). Order("created_at desc"). - Preload("VideoCategories"). - Preload("TrashCategories"). + Preload("Categories.ContentCategory"). + Preload("Categories.WasteCategory"). Joins("JOIN video_categories ON video_categories.video_id = videos.id"). - Joins("JOIN trash_categories ON trash_categories.video_id = videos.id"). - Where("videos.title LIKE ? OR videos.description LIKE ? OR trash_categories.name LIKE ? OR video_categories.name LIKE ?", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%"). + Joins("JOIN content_categories ON content_categories.id = video_categories.content_category_id"). + Joins("JOIN waste_categories ON waste_categories.id = video_categories.waste_category_id"). + Where("videos.title LIKE ? OR videos.description LIKE ? OR content_categories.name LIKE ? OR waste_categories.name LIKE ?", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%"). Find(&videos).Error; err != nil { return nil, err } + videoMap := make(map[int]video.Video) for _, v := range videos { videoMap[v.ID] = v @@ -58,9 +85,10 @@ func (repository *UserVideoRepositoryImpl) SearchVideoByCategory(categoryType st if err := repository.DB.GetDB(). Order("created_at desc"). Joins("JOIN video_categories ON video_categories.video_id = videos.id"). - Where("video_categories.name LIKE ?", "%"+name+"%"). - Preload("VideoCategories"). - Preload("TrashCategories"). + Joins("JOIN content_categories ON content_categories.id = video_categories.content_category_id"). + Where("content_categories.name LIKE ?", "%"+name+"%"). + Preload("Categories.ContentCategory"). + Preload("Categories.WasteCategory"). Find(&videos).Error; err != nil { return nil, err } @@ -68,10 +96,11 @@ func (repository *UserVideoRepositoryImpl) SearchVideoByCategory(categoryType st } else if categoryType == "waste" { if err := repository.DB.GetDB(). Order("created_at desc"). - Joins("JOIN trash_categories ON trash_categories.video_id = videos.id"). - Where("trash_categories.name LIKE ?", "%"+name+"%"). - Preload("VideoCategories"). - Preload("TrashCategories"). + Joins("JOIN video_categories ON video_categories.video_id = videos.id"). + Joins("JOIN waste_categories ON waste_categories.id = video_categories.waste_category_id"). + Where("waste_categories.name LIKE ?", "%"+name+"%"). + Preload("Categories.ContentCategory"). + Preload("Categories.WasteCategory"). Find(&videos).Error; err != nil { return nil, err } From c5d42f0c1d35113d417f189fbb5d3ec7c964c97d Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 13:38:23 +0700 Subject: [PATCH 56/72] change some response body on get video detail --- .../handler/manage_video_handler_impl.go | 35 ++++++--- .../handler/user_video_handler_impl.go | 74 +++++++++++++------ .../repository/user_video_repository_impl.go | 24 ------ 3 files changed, 74 insertions(+), 59 deletions(-) diff --git a/internal/video/manage_video/handler/manage_video_handler_impl.go b/internal/video/manage_video/handler/manage_video_handler_impl.go index 313874c..41ac7fd 100644 --- a/internal/video/manage_video/handler/manage_video_handler_impl.go +++ b/internal/video/manage_video/handler/manage_video_handler_impl.go @@ -179,25 +179,38 @@ func (handler *ManageVideoHandlerImpl) GetDetailsDataVideoByIdHandler(c echo.Con } return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) } - - var dataContentCategories []*dto.DataCategoryVideoResponse - var dataWasteCategories []*dto.DataTrashCategoryResponse + uniqueContentCategories := make(map[uint]*dto.DataCategoryVideoResponse) + uniqueWasteCategories := make(map[uint]*dto.DataTrashCategoryResponse) for _, category := range video.Categories { if category.ContentCategoryID != 0 { - dataContentCategories = append(dataContentCategories, &dto.DataCategoryVideoResponse{ - Id: int(category.ContentCategory.ID), - Name: category.ContentCategory.Name, - }) + if _, exists := uniqueContentCategories[category.ContentCategoryID]; !exists { + uniqueContentCategories[category.ContentCategoryID] = &dto.DataCategoryVideoResponse{ + Id: int(category.ContentCategory.ID), + Name: category.ContentCategory.Name, + } + } } if category.WasteCategoryID != 0 { - dataWasteCategories = append(dataWasteCategories, &dto.DataTrashCategoryResponse{ - Id: int(category.WasteCategory.ID), - Name: category.WasteCategory.Name, - }) + if _, exists := uniqueWasteCategories[category.WasteCategoryID]; !exists { + uniqueWasteCategories[category.WasteCategoryID] = &dto.DataTrashCategoryResponse{ + Id: int(category.WasteCategory.ID), + Name: category.WasteCategory.Name, + } + } } } + // Convert maps back to slices + var dataContentCategories []*dto.DataCategoryVideoResponse + for _, vc := range uniqueContentCategories { + dataContentCategories = append(dataContentCategories, vc) + } + var dataWasteCategories []*dto.DataTrashCategoryResponse + for _, wc := range uniqueWasteCategories { + dataWasteCategories = append(dataWasteCategories, wc) + } + dataVideo := &dto.GetDetailsDataVideoByIdResponse{ Id: video.ID, Title: video.Title, diff --git a/internal/video/user_video/handler/user_video_handler_impl.go b/internal/video/user_video/handler/user_video_handler_impl.go index 05a4b22..e187b20 100644 --- a/internal/video/user_video/handler/user_video_handler_impl.go +++ b/internal/video/user_video/handler/user_video_handler_impl.go @@ -83,20 +83,32 @@ func (handler *UserVideoHandlerImpl) SearchVideoByKeywordHandler(c echo.Context) var dataVideo []*dto.DataVideoSearchByCategory for _, video := range *videos { - videoCategories := make([]*dto.DataCategoryVideo, len(video.Categories)) - for i, vc := range video.Categories { - videoCategories[i] = &dto.DataCategoryVideo{ - Id: int(vc.ContentCategory.ID), // Use ContentCategory instead of Categories - Name: vc.ContentCategory.Name, + uniqueContentCategories := make(map[uint]*dto.DataCategoryVideo) + uniqueTrashCategories := make(map[uint]*dto.DataTrashCategoryVideo) + + for _, vc := range video.Categories { + if _, exists := uniqueContentCategories[vc.ContentCategoryID]; !exists { + uniqueContentCategories[vc.ContentCategoryID] = &dto.DataCategoryVideo{ + Id: int(vc.ContentCategory.ID), + Name: vc.ContentCategory.Name, + } + } + if _, exists := uniqueTrashCategories[vc.WasteCategoryID]; !exists { + uniqueTrashCategories[vc.WasteCategoryID] = &dto.DataTrashCategoryVideo{ + Id: int(vc.WasteCategory.ID), + Name: vc.WasteCategory.Name, + } } } - trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.Categories)) // Assuming trash category is also under ContentCategory - for i, vc := range video.Categories { - trashCategories[i] = &dto.DataTrashCategoryVideo{ - Id: int(vc.WasteCategory.ID), - Name: vc.WasteCategory.Name, - } + // Convert maps back to slices + var videoCategories []*dto.DataCategoryVideo + for _, vc := range uniqueContentCategories { + videoCategories = append(videoCategories, vc) + } + var trashCategories []*dto.DataTrashCategoryVideo + for _, tc := range uniqueTrashCategories { + trashCategories = append(trashCategories, tc) } dataVideo = append(dataVideo, &dto.DataVideoSearchByCategory{ @@ -111,8 +123,10 @@ func (handler *UserVideoHandlerImpl) SearchVideoByKeywordHandler(c echo.Context) }) } - responseData := helper.ResponseData(http.StatusOK, "success", dataVideo) - return c.JSON(http.StatusOK, responseData) + responseData := dto.SearchVideoByCategoryVideoResponse{ + DataVideo: dataVideo, + } + return c.JSON(http.StatusOK, helper.ResponseData(http.StatusOK, "success", responseData.DataVideo)) } func (handler *UserVideoHandlerImpl) SearchVideoByCategoryHandler(c echo.Context) error { @@ -128,20 +142,32 @@ func (handler *UserVideoHandlerImpl) SearchVideoByCategoryHandler(c echo.Context var dataVideo []*dto.DataVideoSearchByCategory for _, video := range *videos { - videoCategories := make([]*dto.DataCategoryVideo, len(video.Categories)) - for i, vc := range video.Categories { - videoCategories[i] = &dto.DataCategoryVideo{ - Id: int(vc.ContentCategory.ID), // Use ContentCategory instead of Categories - Name: vc.ContentCategory.Name, + uniqueContentCategories := make(map[uint]*dto.DataCategoryVideo) + uniqueTrashCategories := make(map[uint]*dto.DataTrashCategoryVideo) + + for _, vc := range video.Categories { + if _, exists := uniqueContentCategories[vc.ContentCategoryID]; !exists { + uniqueContentCategories[vc.ContentCategoryID] = &dto.DataCategoryVideo{ + Id: int(vc.ContentCategory.ID), + Name: vc.ContentCategory.Name, + } + } + if _, exists := uniqueTrashCategories[vc.WasteCategoryID]; !exists { + uniqueTrashCategories[vc.WasteCategoryID] = &dto.DataTrashCategoryVideo{ + Id: int(vc.WasteCategory.ID), + Name: vc.WasteCategory.Name, + } } } - trashCategories := make([]*dto.DataTrashCategoryVideo, len(video.Categories)) // Assuming trash category is also under ContentCategory - for i, vc := range video.Categories { - trashCategories[i] = &dto.DataTrashCategoryVideo{ - Id: int(vc.WasteCategory.ID), - Name: vc.WasteCategory.Name, - } + // Convert maps back to slices + var videoCategories []*dto.DataCategoryVideo + for _, vc := range uniqueContentCategories { + videoCategories = append(videoCategories, vc) + } + var trashCategories []*dto.DataTrashCategoryVideo + for _, tc := range uniqueTrashCategories { + trashCategories = append(trashCategories, tc) } dataVideo = append(dataVideo, &dto.DataVideoSearchByCategory{ diff --git a/internal/video/user_video/repository/user_video_repository_impl.go b/internal/video/user_video/repository/user_video_repository_impl.go index 2adc8a7..a638f1b 100644 --- a/internal/video/user_video/repository/user_video_repository_impl.go +++ b/internal/video/user_video/repository/user_video_repository_impl.go @@ -1,7 +1,6 @@ package repository import ( - art "github.com/sawalreverr/recything/internal/article" "github.com/sawalreverr/recything/internal/database" video "github.com/sawalreverr/recything/internal/video/manage_video/entity" "github.com/sawalreverr/recything/pkg" @@ -26,29 +25,6 @@ func (repository *UserVideoRepositoryImpl) GetAllVideo() (*[]video.Video, error) return nil, err } - for i := range videos { - contentCategorySet := make(map[uint]struct{}) - wasteCategorySet := make(map[uint]struct{}) - - var uniqueContentCategories []art.ContentCategory - var uniqueWasteCategories []art.WasteCategory - - for _, category := range videos[i].Categories { - if _, exists := contentCategorySet[category.ContentCategoryID]; !exists { - contentCategorySet[category.ContentCategoryID] = struct{}{} - uniqueContentCategories = append(uniqueContentCategories, category.ContentCategory) - } - if _, exists := wasteCategorySet[category.WasteCategoryID]; !exists { - wasteCategorySet[category.WasteCategoryID] = struct{}{} - uniqueWasteCategories = append(uniqueWasteCategories, category.WasteCategory) - } - } - - // Assign unique categories back to the video - videos[i].ContentCategories = uniqueContentCategories - videos[i].WasteCategories = uniqueWasteCategories - } - return &videos, nil } From 08cf22990804c9f2da6182a3dc31f1a3197fa752 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 14:12:32 +0700 Subject: [PATCH 57/72] refactor(update data video): change some code for update data video --- .../manage_video_repository_impl.go | 1 + .../usecase/manage_video_usecase_impl.go | 48 +++++++++---------- pkg/errors.go | 8 ++-- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/internal/video/manage_video/repository/manage_video_repository_impl.go b/internal/video/manage_video/repository/manage_video_repository_impl.go index 1902beb..03dd673 100644 --- a/internal/video/manage_video/repository/manage_video_repository_impl.go +++ b/internal/video/manage_video/repository/manage_video_repository_impl.go @@ -125,6 +125,7 @@ func (repository *ManageVideoRepositoryImpl) UpdateDataVideo(videos *video.Video } } + // Update video details along with associations if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Save(&videos).Error; err != nil { tx.Rollback() return err diff --git a/internal/video/manage_video/usecase/manage_video_usecase_impl.go b/internal/video/manage_video/usecase/manage_video_usecase_impl.go index 6c2dfc1..cda56fa 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase_impl.go +++ b/internal/video/manage_video/usecase/manage_video_usecase_impl.go @@ -45,11 +45,9 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat return pkg.ErrVideoTitleAlreadyExist } - // Memperoleh kategori konten dan limbah dari request var contentCategories []*art.ContentCategory var wasteCategories []*art.WasteCategory - // Menambahkan kategori konten for _, category := range request.ContentCategory { name := strings.ToLower(category.Name) content, err := usecase.manageVideoRepository.FindNameCategoryVideo(name) @@ -59,7 +57,6 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat contentCategories = append(contentCategories, content) } - // Menambahkan kategori limbah for _, category := range request.WasteCategory { name := strings.ToLower(category.Name) waste, err := usecase.manageVideoRepository.FindNamaTrashCategory(name) @@ -69,7 +66,6 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat wasteCategories = append(wasteCategories, waste) } - // Membuat slice VideoCategory yang akan disimpan var videoCategories []video.VideoCategory for _, content := range contentCategories { for _, waste := range wasteCategories { @@ -147,42 +143,44 @@ func (usecase *ManageVideoUsecaseImpl) UpdateDataVideoUseCase(request *dto.Updat return pkg.ErrVideoNotFound } - var videoCategories []video.VideoCategory + var contentCategories []*art.ContentCategory + var wasteCategories []*art.WasteCategory if request.ContentCategories != nil { for _, category := range request.ContentCategories { name := strings.ToLower(category.Name) - contentCategory, err := usecase.manageVideoRepository.FindNameCategoryVideo(name) - if err == gorm.ErrRecordNotFound { - return pkg.ErrNameCategoryVideoNotFound + content, err := usecase.manageVideoRepository.FindNameCategoryVideo(name) + if err != nil { + return pkg.ErrVideoCategory } - videoCategory := video.VideoCategory{ - VideoID: id, - ContentCategoryID: contentCategory.ID, - } - videoCategories = append(videoCategories, videoCategory) + contentCategories = append(contentCategories, content) } - dataVideo.Categories = videoCategories } - var wasteCategories []video.VideoCategory - if request.WasteCategories != nil { for _, category := range request.WasteCategories { name := strings.ToLower(category.Name) - wasteCategory, err := usecase.manageVideoRepository.FindNamaTrashCategory(name) - if err == gorm.ErrRecordNotFound { - return pkg.ErrNameTrashCategoryNotFound + waste, err := usecase.manageVideoRepository.FindNamaTrashCategory(name) + if err != nil { + return pkg.ErrVideoTrashCategory } - videoCategory := video.VideoCategory{ - VideoID: id, - WasteCategoryID: wasteCategory.ID, - } - wasteCategories = append(wasteCategories, videoCategory) + wasteCategories = append(wasteCategories, waste) + } + + } + + var videoCategories []video.VideoCategory + for _, content := range contentCategories { + for _, waste := range wasteCategories { + videoCategories = append(videoCategories, video.VideoCategory{ + ContentCategoryID: content.ID, + WasteCategoryID: waste.ID, + }) } - dataVideo.Categories = append(dataVideo.Categories, wasteCategories...) } + dataVideo.Categories = videoCategories + var urlThumbnail string if len(thumbnail) == 1 { validImages, errImages := helper.ImagesValidation(thumbnail) diff --git a/pkg/errors.go b/pkg/errors.go index ae8269d..484e103 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -64,10 +64,10 @@ var ( ErrVideoService = errors.New("video service error") ErrApiYouTube = errors.New("api youtube error") ErrParsingUrl = errors.New("parsing url error") - ErrVideoCategory = errors.New("video category is required") - ErrVideoTrashCategory = errors.New("video category tash is required") - ErrNameCategoryVideoNotFound = errors.New("name category video not found") - ErrNameTrashCategoryNotFound = errors.New("name trash category not found") + ErrVideoCategory = errors.New("content category is required") + ErrVideoTrashCategory = errors.New("waste category is required") + ErrNameCategoryVideoNotFound = errors.New("name content category not found") + ErrNameTrashCategoryNotFound = errors.New("name waste category not found") // user achievement ErrUserNotHasHistoryPoint = errors.New("user not has history points") From 0f7b5a18e0e21e8663cb777fa4b744d8fb617ca4 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 14:12:32 +0700 Subject: [PATCH 58/72] refactor(update data video): change some code for update data video --- .../manage_video_repository_impl.go | 1 - .../usecase/manage_video_usecase_impl.go | 48 +++++++++---------- pkg/errors.go | 8 ++-- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/internal/video/manage_video/repository/manage_video_repository_impl.go b/internal/video/manage_video/repository/manage_video_repository_impl.go index 1902beb..6c57d9b 100644 --- a/internal/video/manage_video/repository/manage_video_repository_impl.go +++ b/internal/video/manage_video/repository/manage_video_repository_impl.go @@ -117,7 +117,6 @@ func (repository *ManageVideoRepositoryImpl) UpdateDataVideo(videos *video.Video } }() - // Delete existing categories if any new ones are provided if len(videos.Categories) > 0 { if err := tx.Where("video_id = ?", id).Delete(&video.VideoCategory{}).Error; err != nil { tx.Rollback() diff --git a/internal/video/manage_video/usecase/manage_video_usecase_impl.go b/internal/video/manage_video/usecase/manage_video_usecase_impl.go index 6c2dfc1..cda56fa 100644 --- a/internal/video/manage_video/usecase/manage_video_usecase_impl.go +++ b/internal/video/manage_video/usecase/manage_video_usecase_impl.go @@ -45,11 +45,9 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat return pkg.ErrVideoTitleAlreadyExist } - // Memperoleh kategori konten dan limbah dari request var contentCategories []*art.ContentCategory var wasteCategories []*art.WasteCategory - // Menambahkan kategori konten for _, category := range request.ContentCategory { name := strings.ToLower(category.Name) content, err := usecase.manageVideoRepository.FindNameCategoryVideo(name) @@ -59,7 +57,6 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat contentCategories = append(contentCategories, content) } - // Menambahkan kategori limbah for _, category := range request.WasteCategory { name := strings.ToLower(category.Name) waste, err := usecase.manageVideoRepository.FindNamaTrashCategory(name) @@ -69,7 +66,6 @@ func (usecase *ManageVideoUsecaseImpl) CreateDataVideoUseCase(request *dto.Creat wasteCategories = append(wasteCategories, waste) } - // Membuat slice VideoCategory yang akan disimpan var videoCategories []video.VideoCategory for _, content := range contentCategories { for _, waste := range wasteCategories { @@ -147,42 +143,44 @@ func (usecase *ManageVideoUsecaseImpl) UpdateDataVideoUseCase(request *dto.Updat return pkg.ErrVideoNotFound } - var videoCategories []video.VideoCategory + var contentCategories []*art.ContentCategory + var wasteCategories []*art.WasteCategory if request.ContentCategories != nil { for _, category := range request.ContentCategories { name := strings.ToLower(category.Name) - contentCategory, err := usecase.manageVideoRepository.FindNameCategoryVideo(name) - if err == gorm.ErrRecordNotFound { - return pkg.ErrNameCategoryVideoNotFound + content, err := usecase.manageVideoRepository.FindNameCategoryVideo(name) + if err != nil { + return pkg.ErrVideoCategory } - videoCategory := video.VideoCategory{ - VideoID: id, - ContentCategoryID: contentCategory.ID, - } - videoCategories = append(videoCategories, videoCategory) + contentCategories = append(contentCategories, content) } - dataVideo.Categories = videoCategories } - var wasteCategories []video.VideoCategory - if request.WasteCategories != nil { for _, category := range request.WasteCategories { name := strings.ToLower(category.Name) - wasteCategory, err := usecase.manageVideoRepository.FindNamaTrashCategory(name) - if err == gorm.ErrRecordNotFound { - return pkg.ErrNameTrashCategoryNotFound + waste, err := usecase.manageVideoRepository.FindNamaTrashCategory(name) + if err != nil { + return pkg.ErrVideoTrashCategory } - videoCategory := video.VideoCategory{ - VideoID: id, - WasteCategoryID: wasteCategory.ID, - } - wasteCategories = append(wasteCategories, videoCategory) + wasteCategories = append(wasteCategories, waste) + } + + } + + var videoCategories []video.VideoCategory + for _, content := range contentCategories { + for _, waste := range wasteCategories { + videoCategories = append(videoCategories, video.VideoCategory{ + ContentCategoryID: content.ID, + WasteCategoryID: waste.ID, + }) } - dataVideo.Categories = append(dataVideo.Categories, wasteCategories...) } + dataVideo.Categories = videoCategories + var urlThumbnail string if len(thumbnail) == 1 { validImages, errImages := helper.ImagesValidation(thumbnail) diff --git a/pkg/errors.go b/pkg/errors.go index ae8269d..484e103 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -64,10 +64,10 @@ var ( ErrVideoService = errors.New("video service error") ErrApiYouTube = errors.New("api youtube error") ErrParsingUrl = errors.New("parsing url error") - ErrVideoCategory = errors.New("video category is required") - ErrVideoTrashCategory = errors.New("video category tash is required") - ErrNameCategoryVideoNotFound = errors.New("name category video not found") - ErrNameTrashCategoryNotFound = errors.New("name trash category not found") + ErrVideoCategory = errors.New("content category is required") + ErrVideoTrashCategory = errors.New("waste category is required") + ErrNameCategoryVideoNotFound = errors.New("name content category not found") + ErrNameTrashCategoryNotFound = errors.New("name waste category not found") // user achievement ErrUserNotHasHistoryPoint = errors.New("user not has history points") From f9971d1410520af56598cef63b39da330a3c5659 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 17:17:17 +0700 Subject: [PATCH 59/72] refactor(update admin): change method update to patch --- docs/swagger.yml | 4 +- internal/admin/dto/admin_request.go | 10 ++-- internal/admin/handler/admin_handler_impl.go | 50 ++++++++++------- internal/admin/usecase/admin_usecase.go | 2 +- internal/admin/usecase/admin_usecase_impl.go | 59 +++++++++++++------- internal/server/route.go | 2 +- pkg/errors.go | 1 + 7 files changed, 81 insertions(+), 47 deletions(-) diff --git a/docs/swagger.yml b/docs/swagger.yml index f6f713e..919fd9b 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -1451,7 +1451,7 @@ paths: data: type: object $ref: '#/components/schemas/Admin' - put: + patch: tags: - manage admins summary: Update an admin @@ -1468,7 +1468,7 @@ paths: description: ID of the admin to be updated example: AD0001 requestBody: - required: true + required: false content: multipart/form-data: schema: diff --git a/internal/admin/dto/admin_request.go b/internal/admin/dto/admin_request.go index 8c63d4f..8c61bd5 100644 --- a/internal/admin/dto/admin_request.go +++ b/internal/admin/dto/admin_request.go @@ -9,9 +9,9 @@ type AdminRequestCreate struct { } type AdminUpdateRequest struct { - Name string `form:"name" validate:"required"` - Email string `form:"email" validate:"required,email"` - OldPassword string `form:"old_password" validate:"required"` - NewPassword string `form:"new_password" validate:"required"` - Role string `form:"role" validate:"required"` + Name string `form:"name"` + Email string `form:"email" validate:"email"` + OldPassword string `form:"old_password"` + NewPassword string `form:"new_password"` + Role string `form:"role"` } diff --git a/internal/admin/handler/admin_handler_impl.go b/internal/admin/handler/admin_handler_impl.go index fe74b25..7183e70 100644 --- a/internal/admin/handler/admin_handler_impl.go +++ b/internal/admin/handler/admin_handler_impl.go @@ -2,6 +2,7 @@ package handler import ( "errors" + "mime/multipart" "net/http" "strconv" "strings" @@ -162,34 +163,41 @@ func (handler *adminHandlerImpl) UpdateAdminHandler(c echo.Context) error { return helper.ErrorHandler(c, http.StatusBadRequest, "invalid request body") } - if err := c.Validate(&request); err != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + if request.Email != "" { + if err := c.Validate(&request); err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, err.Error()) + } } - if request.Role != "admin" && request.Role != "super admin" { - return helper.ErrorHandler(c, http.StatusBadRequest, "role must be admin or super admin") + if (request.NewPassword != "" && request.OldPassword == "") || (request.NewPassword == "" && request.OldPassword != "") { + return helper.ErrorHandler(c, http.StatusBadRequest, "both old_password and new_password must be provided together") } - file, errFile := c.FormFile("profile_photo") - if errFile != nil { - return helper.ErrorHandler(c, http.StatusBadRequest, "profile_photo is required") - } + var file multipart.File + reqFile, errFile := c.FormFile("profile_photo") - if file.Size > 2*1024*1024 { - return helper.ErrorHandler(c, http.StatusBadRequest, "file is too large") - } + if reqFile != nil { + if errFile != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, "profile_photo is required") + } - if !strings.HasPrefix(file.Header.Get("Content-Type"), "image") { - return helper.ErrorHandler(c, http.StatusBadRequest, "invalid file type") - } + if reqFile.Size > 2*1024*1024 { + return helper.ErrorHandler(c, http.StatusBadRequest, "file is too large") + } - src, errOpen := file.Open() - if errOpen != nil { - return helper.ErrorHandler(c, http.StatusInternalServerError, "failed to open file: "+errOpen.Error()) + if !strings.HasPrefix(reqFile.Header.Get("Content-Type"), "image") { + return helper.ErrorHandler(c, http.StatusBadRequest, "invalid file type") + } + src, errOpen := reqFile.Open() + if errOpen != nil { + return helper.ErrorHandler(c, http.StatusInternalServerError, "failed to open file: "+errOpen.Error()) + } + defer src.Close() + + file = src } - defer src.Close() - admin, errUc := handler.Usecase.UpdateAdminUsecase(request, id, src) + admin, errUc := handler.Usecase.UpdateAdminUsecase(&request, id, file) if errUc != nil { if errors.Is(errUc, pkg.ErrAdminNotFound) { return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrAdminNotFound.Error()) @@ -203,6 +211,10 @@ func (handler *adminHandlerImpl) UpdateAdminHandler(c echo.Context) error { return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrPasswordInvalid.Error()) } + if errors.Is(errUc, pkg.ErrRole) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrRole.Error()) + } + return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+errUc.Error()) } diff --git a/internal/admin/usecase/admin_usecase.go b/internal/admin/usecase/admin_usecase.go index 107fa6d..2d65a9b 100644 --- a/internal/admin/usecase/admin_usecase.go +++ b/internal/admin/usecase/admin_usecase.go @@ -11,7 +11,7 @@ type AdminUsecase interface { AddAdminUsecase(request dto.AdminRequestCreate, file io.Reader) (*entity.Admin, error) GetDataAllAdminUsecase(limit int, offset int) ([]entity.Admin, int, error) GetDataAdminByIdUsecase(id string) (*entity.Admin, error) - UpdateAdminUsecase(request dto.AdminUpdateRequest, id string, file io.Reader) (*entity.Admin, error) + UpdateAdminUsecase(request *dto.AdminUpdateRequest, id string, file io.Reader) (*entity.Admin, error) DeleteAdminUsecase(id string) error GetDataAdminByEmailUsecase(email string) (*entity.Admin, error) GetProfileAdmin(id string) (*entity.Admin, error) diff --git a/internal/admin/usecase/admin_usecase_impl.go b/internal/admin/usecase/admin_usecase_impl.go index d46c823..9758e13 100644 --- a/internal/admin/usecase/admin_usecase_impl.go +++ b/internal/admin/usecase/admin_usecase_impl.go @@ -88,34 +88,55 @@ func (usecase *AdminUsecaseImpl) GetDataAdminByEmailUsecase(email string) (*enti return admin, nil } -func (usecase *AdminUsecaseImpl) UpdateAdminUsecase(request dto.AdminUpdateRequest, id string, file io.Reader) (*entity.Admin, error) { - findAdmin, _ := usecase.Repository.FindAdminByID(id) - if findAdmin == nil { +func (usecase *AdminUsecaseImpl) UpdateAdminUsecase(request *dto.AdminUpdateRequest, id string, file io.Reader) (*entity.Admin, error) { + findAdmin, err := usecase.Repository.FindAdminByID(id) + if err != nil || findAdmin == nil { return nil, pkg.ErrAdminNotFound } - if matchPassword := helper.ComparePassword(findAdmin.Password, request.OldPassword); !matchPassword { - return nil, pkg.ErrPasswordInvalid + if request.Name != "" { + findAdmin.Name = request.Name } - hashPassword, _ := helper.GenerateHash(request.NewPassword) + if request.Email != "" { + findAdmin.Email = request.Email + } - imageUrl, errUpload := helper.UploadToCloudinary(file, "profile_admin_update") - if errUpload != nil { - return nil, pkg.ErrUploadCloudinary + if request.Role != "" { + if request.Role != "admin" && request.Role != "user" { + return nil, pkg.ErrRole + } + findAdmin.Role = request.Role } - admin, error := usecase.Repository.UpdateDataAdmin(&entity.Admin{ - Name: request.Name, - Email: request.Email, - Password: hashPassword, - Role: request.Role, - ImageUrl: imageUrl, - DeletedAt: gorm.DeletedAt{}, - }, id) - if error != nil { - return nil, error + if request.OldPassword != "" && request.NewPassword != "" { + if matchPassword := helper.ComparePassword(findAdmin.Password, request.OldPassword); !matchPassword { + return nil, pkg.ErrPasswordInvalid + } + hashPassword, err := helper.GenerateHash(request.NewPassword) + if err != nil { + return nil, err + } + findAdmin.Password = hashPassword } + + var imageUrl string + if file != nil { + imageUrlUpload, errUpload := helper.UploadToCloudinary(file, "profile_admin_update") + if errUpload != nil { + return nil, pkg.ErrUploadCloudinary + } + imageUrl = imageUrlUpload + } else { + imageUrl = findAdmin.ImageUrl + } + + findAdmin.ImageUrl = imageUrl + admin, err := usecase.Repository.UpdateDataAdmin(findAdmin, id) + if err != nil { + return nil, err + } + return admin, nil } diff --git a/internal/server/route.go b/internal/server/route.go index e96af86..668c902 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -146,7 +146,7 @@ func (s *echoServer) supAdminHttpHandler() { s.gr.GET("/admin/:adminId", handler.GetDataAdminByIdHandler, SuperAdminMiddleware) // update admin by super admin - s.gr.PUT("/admin/:adminId", handler.UpdateAdminHandler, SuperAdminMiddleware) + s.gr.PATCH("/admin/:adminId", handler.UpdateAdminHandler, SuperAdminMiddleware) // delete admin by super admin s.gr.DELETE("/admin/:adminId", handler.DeleteAdminHandler, SuperAdminMiddleware) diff --git a/pkg/errors.go b/pkg/errors.go index 484e103..0976f43 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -21,6 +21,7 @@ var ( // admin ErrAdminNotFound = errors.New("admin not found") + ErrRole = errors.New("role must be admin or super admin") // Report ErrReportNotFound = errors.New("report not found") From 43655143acb9eabb5446bb761b5e3a9c158a8d44 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 14:12:32 +0700 Subject: [PATCH 60/72] refactor(update data video): change some code for update data video --- .../manage_video/repository/manage_video_repository_impl.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/video/manage_video/repository/manage_video_repository_impl.go b/internal/video/manage_video/repository/manage_video_repository_impl.go index 03dd673..9eb56be 100644 --- a/internal/video/manage_video/repository/manage_video_repository_impl.go +++ b/internal/video/manage_video/repository/manage_video_repository_impl.go @@ -117,7 +117,6 @@ func (repository *ManageVideoRepositoryImpl) UpdateDataVideo(videos *video.Video } }() - // Delete existing categories if any new ones are provided if len(videos.Categories) > 0 { if err := tx.Where("video_id = ?", id).Delete(&video.VideoCategory{}).Error; err != nil { tx.Rollback() From af4a1b3bb993d0f68e78733c7df0bf3a36108600 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 17:48:29 +0700 Subject: [PATCH 61/72] refactor(get detail video): add profile user on res body, total comment, and sort comment --- .../user_video/dto/user_video_response.go | 16 ++++++----- .../handler/user_video_handler_impl.go | 28 ++++++++++++++----- .../repository/user_video_repository.go | 2 +- .../repository/user_video_repository_impl.go | 26 ++++++++--------- .../user_video/usecase/user_video_usecase.go | 2 +- .../usecase/user_video_usecase_impl.go | 12 ++++---- 6 files changed, 51 insertions(+), 35 deletions(-) diff --git a/internal/video/user_video/dto/user_video_response.go b/internal/video/user_video/dto/user_video_response.go index 090acc6..43d1294 100644 --- a/internal/video/user_video/dto/user_video_response.go +++ b/internal/video/user_video/dto/user_video_response.go @@ -20,16 +20,18 @@ type GetAllVideoResponse struct { } type DataComment struct { - Id int `json:"id"` - Comment string `json:"comment"` - UserID string `json:"user_id"` - UserName string `json:"user_name"` - CreatedAt time.Time `json:"created_at"` + Id int `json:"id"` + Comment string `json:"comment"` + UserID string `json:"user_id"` + UserName string `json:"user_name"` + UserProfile string `json:"user_profile"` + CreatedAt time.Time `json:"created_at"` } type GetDetailsDataVideoByIdResponse struct { - DataVideo *DataVideo `json:"data_video"` - Comments *[]DataComment `json:"comments"` + DataVideo *DataVideo `json:"data_video"` + TotalComment int `json:"total_comment"` + Comments *[]DataComment `json:"comments"` } type DataCategoryVideo struct { diff --git a/internal/video/user_video/handler/user_video_handler_impl.go b/internal/video/user_video/handler/user_video_handler_impl.go index e187b20..5f2dce4 100644 --- a/internal/video/user_video/handler/user_video_handler_impl.go +++ b/internal/video/user_video/handler/user_video_handler_impl.go @@ -3,6 +3,7 @@ package handler import ( "errors" "net/http" + "sort" "strconv" "github.com/labstack/echo/v4" @@ -195,7 +196,7 @@ func (handler *UserVideoHandlerImpl) GetVideoDetailHandler(c echo.Context) error return helper.ErrorHandler(c, http.StatusBadRequest, "invalid id parameter") } - video, comments, err := handler.Usecase.GetVideoDetailUsecase(intId) + video, comments, totalComment, err := handler.Usecase.GetVideoDetailUsecase(intId) if err != nil { if errors.Is(err, pkg.ErrVideoNotFound) { return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrVideoNotFound.Error()) @@ -203,6 +204,17 @@ func (handler *UserVideoHandlerImpl) GetVideoDetailHandler(c echo.Context) error return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) } + sortComments := c.QueryParam("sort-comments") + + if sortComments == "" { + sortComments = "false" + } + if sortComments == "true" { + sort.SliceStable(*comments, func(i, j int) bool { + return (*comments)[i].CreatedAt.After((*comments)[j].CreatedAt) + }) + } + var dataComments []dto.DataComment data := dto.GetDetailsDataVideoByIdResponse{ DataVideo: &dto.DataVideo{ @@ -213,16 +225,18 @@ func (handler *UserVideoHandlerImpl) GetVideoDetailHandler(c echo.Context) error LinkVideo: video.Link, Viewer: video.Viewer, }, - Comments: &dataComments, + TotalComment: totalComment, + Comments: &dataComments, } for _, comment := range *comments { dataComments = append(dataComments, dto.DataComment{ - Id: comment.ID, - Comment: comment.Comment, - UserID: comment.UserID, - UserName: comment.User.Name, - CreatedAt: comment.CreatedAt, + Id: comment.ID, + Comment: comment.Comment, + UserID: comment.UserID, + UserName: comment.User.Name, + UserProfile: comment.User.PictureURL, + CreatedAt: comment.CreatedAt, }) } data.Comments = &dataComments diff --git a/internal/video/user_video/repository/user_video_repository.go b/internal/video/user_video/repository/user_video_repository.go index 1d3e638..4fb0e52 100644 --- a/internal/video/user_video/repository/user_video_repository.go +++ b/internal/video/user_video/repository/user_video_repository.go @@ -8,7 +8,7 @@ type UserVideoRepository interface { GetAllVideo() (*[]video.Video, error) SearchVideoByKeyword(keyword string) (*[]video.Video, error) SearchVideoByCategory(categoryType string, name string) (*[]video.Video, error) - GetVideoDetail(id int) (*video.Video, *[]video.Comment, error) + GetVideoDetail(id int) (*video.Video, *[]video.Comment, int, error) AddComment(comment *video.Comment) error UpdateViewer(view int, id int) error } diff --git a/internal/video/user_video/repository/user_video_repository_impl.go b/internal/video/user_video/repository/user_video_repository_impl.go index a638f1b..8544f42 100644 --- a/internal/video/user_video/repository/user_video_repository_impl.go +++ b/internal/video/user_video/repository/user_video_repository_impl.go @@ -97,26 +97,26 @@ func (repository *UserVideoRepositoryImpl) SearchVideoByCategory(categoryType st return &uniqueVideos, nil } -func (repository *UserVideoRepositoryImpl) GetVideoDetail(id int) (*video.Video, *[]video.Comment, error) { +func (repository *UserVideoRepositoryImpl) GetVideoDetail(id int) (*video.Video, *[]video.Comment, int, error) { var videos video.Video var comments []video.Comment + var totalComment int64 - if err := repository.DB.GetDB(). - Where("id = ?", id). - Order("created_at desc"). - First(&videos).Error; err != nil { - return nil, nil, err + db := repository.DB.GetDB() + + if err := db.Model(&video.Video{}).Where("id = ?", id).First(&videos).Error; err != nil { + return nil, nil, 0, err } - if err := repository.DB.GetDB(). - Preload("User"). - Where("video_id = ?", id). - Order("created_at desc"). - Find(&comments).Error; err != nil { - return nil, nil, err + if err := db.Model(&video.Comment{}).Preload("User").Where("video_id = ?", id).Find(&comments).Error; err != nil { + return nil, nil, 0, err + } + + if err := db.Model(&video.Comment{}).Where("video_id = ?", id).Count(&totalComment).Error; err != nil { + return nil, nil, 0, err } - return &videos, &comments, nil + return &videos, &comments, int(totalComment), nil } func (repository *UserVideoRepositoryImpl) AddComment(comment *video.Comment) error { diff --git a/internal/video/user_video/usecase/user_video_usecase.go b/internal/video/user_video/usecase/user_video_usecase.go index 7d893f2..314c6c5 100644 --- a/internal/video/user_video/usecase/user_video_usecase.go +++ b/internal/video/user_video/usecase/user_video_usecase.go @@ -9,6 +9,6 @@ type UserVideoUsecase interface { GetAllVideoUsecase() (*[]video.Video, error) SearchVideoByKeywordUsecase(keyword string) (*[]video.Video, error) SearchVideoByCategoryUsecase(categoryType string, name string) (*[]video.Video, error) - GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, error) + GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, int, error) AddCommentUsecase(request *dto.AddCommentRequest, userId string) error } diff --git a/internal/video/user_video/usecase/user_video_usecase_impl.go b/internal/video/user_video/usecase/user_video_usecase_impl.go index 0868da1..0904773 100644 --- a/internal/video/user_video/usecase/user_video_usecase_impl.go +++ b/internal/video/user_video/usecase/user_video_usecase_impl.go @@ -71,19 +71,19 @@ func (usecase *UserVideoUsecaseImpl) SearchVideoByCategoryUsecase(categoryType s return videos, nil } -func (usecase *UserVideoUsecaseImpl) GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, error) { - video, comments, err := usecase.Repository.GetVideoDetail(id) +func (usecase *UserVideoUsecaseImpl) GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, int, error) { + video, comments, totalComment, err := usecase.Repository.GetVideoDetail(id) if err != nil { if err == gorm.ErrRecordNotFound { - return nil, nil, pkg.ErrVideoNotFound + return nil, nil, 0, pkg.ErrVideoNotFound } - return nil, nil, err + return nil, nil, 0, err } - return video, comments, nil + return video, comments, totalComment, nil } func (usecase *UserVideoUsecaseImpl) AddCommentUsecase(request *dto.AddCommentRequest, userId string) error { - if _, _, err := usecase.Repository.GetVideoDetail(request.VideoID); err != nil { + if _, _, _, err := usecase.Repository.GetVideoDetail(request.VideoID); err != nil { if err == gorm.ErrRecordNotFound { return pkg.ErrVideoNotFound } From 2aa2c4343089d385eb9bb737b050bb35b6940035 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 17:54:10 +0700 Subject: [PATCH 62/72] docs: add query params on get video detail --- docs/swagger.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/swagger.yml b/docs/swagger.yml index f8b0513..e3819b4 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -3658,6 +3658,14 @@ paths: type: integer example: 1 description: ID of video + - name : sort-comments + in: query + required: false + schema: + type: string + example: true + default: false + description: Sort comments responses: '200': description: Success @@ -3693,6 +3701,9 @@ paths: viewer: type: integer example: 1000 + total_comment: + type: integer + example: 10 comments: type: array items: @@ -3710,6 +3721,9 @@ paths: user_name: type: string example: John Doe + user_profile: + type: string + example: "https://example.com/profile.jpg" created_at: type: string format: date From 8219e82bf68b52b272e58171bb5f9996a3813c0a Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 14:12:32 +0700 Subject: [PATCH 63/72] refactor(update data video): change some code for update data video --- .../manage_video/repository/manage_video_repository_impl.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/video/manage_video/repository/manage_video_repository_impl.go b/internal/video/manage_video/repository/manage_video_repository_impl.go index 6c57d9b..9eb56be 100644 --- a/internal/video/manage_video/repository/manage_video_repository_impl.go +++ b/internal/video/manage_video/repository/manage_video_repository_impl.go @@ -124,6 +124,7 @@ func (repository *ManageVideoRepositoryImpl) UpdateDataVideo(videos *video.Video } } + // Update video details along with associations if err := tx.Session(&gorm.Session{FullSaveAssociations: true}).Save(&videos).Error; err != nil { tx.Rollback() return err From 0be74afa13859c7464dc4330df9d1cb65fb1f7ef Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 17:48:29 +0700 Subject: [PATCH 64/72] refactor(get detail video): add profile user on res body, total comment, and sort comment --- .../user_video/dto/user_video_response.go | 16 ++++++----- .../handler/user_video_handler_impl.go | 28 ++++++++++++++----- .../repository/user_video_repository.go | 2 +- .../repository/user_video_repository_impl.go | 26 ++++++++--------- .../user_video/usecase/user_video_usecase.go | 2 +- .../usecase/user_video_usecase_impl.go | 12 ++++---- 6 files changed, 51 insertions(+), 35 deletions(-) diff --git a/internal/video/user_video/dto/user_video_response.go b/internal/video/user_video/dto/user_video_response.go index 090acc6..43d1294 100644 --- a/internal/video/user_video/dto/user_video_response.go +++ b/internal/video/user_video/dto/user_video_response.go @@ -20,16 +20,18 @@ type GetAllVideoResponse struct { } type DataComment struct { - Id int `json:"id"` - Comment string `json:"comment"` - UserID string `json:"user_id"` - UserName string `json:"user_name"` - CreatedAt time.Time `json:"created_at"` + Id int `json:"id"` + Comment string `json:"comment"` + UserID string `json:"user_id"` + UserName string `json:"user_name"` + UserProfile string `json:"user_profile"` + CreatedAt time.Time `json:"created_at"` } type GetDetailsDataVideoByIdResponse struct { - DataVideo *DataVideo `json:"data_video"` - Comments *[]DataComment `json:"comments"` + DataVideo *DataVideo `json:"data_video"` + TotalComment int `json:"total_comment"` + Comments *[]DataComment `json:"comments"` } type DataCategoryVideo struct { diff --git a/internal/video/user_video/handler/user_video_handler_impl.go b/internal/video/user_video/handler/user_video_handler_impl.go index e187b20..5f2dce4 100644 --- a/internal/video/user_video/handler/user_video_handler_impl.go +++ b/internal/video/user_video/handler/user_video_handler_impl.go @@ -3,6 +3,7 @@ package handler import ( "errors" "net/http" + "sort" "strconv" "github.com/labstack/echo/v4" @@ -195,7 +196,7 @@ func (handler *UserVideoHandlerImpl) GetVideoDetailHandler(c echo.Context) error return helper.ErrorHandler(c, http.StatusBadRequest, "invalid id parameter") } - video, comments, err := handler.Usecase.GetVideoDetailUsecase(intId) + video, comments, totalComment, err := handler.Usecase.GetVideoDetailUsecase(intId) if err != nil { if errors.Is(err, pkg.ErrVideoNotFound) { return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrVideoNotFound.Error()) @@ -203,6 +204,17 @@ func (handler *UserVideoHandlerImpl) GetVideoDetailHandler(c echo.Context) error return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail : "+err.Error()) } + sortComments := c.QueryParam("sort-comments") + + if sortComments == "" { + sortComments = "false" + } + if sortComments == "true" { + sort.SliceStable(*comments, func(i, j int) bool { + return (*comments)[i].CreatedAt.After((*comments)[j].CreatedAt) + }) + } + var dataComments []dto.DataComment data := dto.GetDetailsDataVideoByIdResponse{ DataVideo: &dto.DataVideo{ @@ -213,16 +225,18 @@ func (handler *UserVideoHandlerImpl) GetVideoDetailHandler(c echo.Context) error LinkVideo: video.Link, Viewer: video.Viewer, }, - Comments: &dataComments, + TotalComment: totalComment, + Comments: &dataComments, } for _, comment := range *comments { dataComments = append(dataComments, dto.DataComment{ - Id: comment.ID, - Comment: comment.Comment, - UserID: comment.UserID, - UserName: comment.User.Name, - CreatedAt: comment.CreatedAt, + Id: comment.ID, + Comment: comment.Comment, + UserID: comment.UserID, + UserName: comment.User.Name, + UserProfile: comment.User.PictureURL, + CreatedAt: comment.CreatedAt, }) } data.Comments = &dataComments diff --git a/internal/video/user_video/repository/user_video_repository.go b/internal/video/user_video/repository/user_video_repository.go index 1d3e638..4fb0e52 100644 --- a/internal/video/user_video/repository/user_video_repository.go +++ b/internal/video/user_video/repository/user_video_repository.go @@ -8,7 +8,7 @@ type UserVideoRepository interface { GetAllVideo() (*[]video.Video, error) SearchVideoByKeyword(keyword string) (*[]video.Video, error) SearchVideoByCategory(categoryType string, name string) (*[]video.Video, error) - GetVideoDetail(id int) (*video.Video, *[]video.Comment, error) + GetVideoDetail(id int) (*video.Video, *[]video.Comment, int, error) AddComment(comment *video.Comment) error UpdateViewer(view int, id int) error } diff --git a/internal/video/user_video/repository/user_video_repository_impl.go b/internal/video/user_video/repository/user_video_repository_impl.go index a638f1b..8544f42 100644 --- a/internal/video/user_video/repository/user_video_repository_impl.go +++ b/internal/video/user_video/repository/user_video_repository_impl.go @@ -97,26 +97,26 @@ func (repository *UserVideoRepositoryImpl) SearchVideoByCategory(categoryType st return &uniqueVideos, nil } -func (repository *UserVideoRepositoryImpl) GetVideoDetail(id int) (*video.Video, *[]video.Comment, error) { +func (repository *UserVideoRepositoryImpl) GetVideoDetail(id int) (*video.Video, *[]video.Comment, int, error) { var videos video.Video var comments []video.Comment + var totalComment int64 - if err := repository.DB.GetDB(). - Where("id = ?", id). - Order("created_at desc"). - First(&videos).Error; err != nil { - return nil, nil, err + db := repository.DB.GetDB() + + if err := db.Model(&video.Video{}).Where("id = ?", id).First(&videos).Error; err != nil { + return nil, nil, 0, err } - if err := repository.DB.GetDB(). - Preload("User"). - Where("video_id = ?", id). - Order("created_at desc"). - Find(&comments).Error; err != nil { - return nil, nil, err + if err := db.Model(&video.Comment{}).Preload("User").Where("video_id = ?", id).Find(&comments).Error; err != nil { + return nil, nil, 0, err + } + + if err := db.Model(&video.Comment{}).Where("video_id = ?", id).Count(&totalComment).Error; err != nil { + return nil, nil, 0, err } - return &videos, &comments, nil + return &videos, &comments, int(totalComment), nil } func (repository *UserVideoRepositoryImpl) AddComment(comment *video.Comment) error { diff --git a/internal/video/user_video/usecase/user_video_usecase.go b/internal/video/user_video/usecase/user_video_usecase.go index 7d893f2..314c6c5 100644 --- a/internal/video/user_video/usecase/user_video_usecase.go +++ b/internal/video/user_video/usecase/user_video_usecase.go @@ -9,6 +9,6 @@ type UserVideoUsecase interface { GetAllVideoUsecase() (*[]video.Video, error) SearchVideoByKeywordUsecase(keyword string) (*[]video.Video, error) SearchVideoByCategoryUsecase(categoryType string, name string) (*[]video.Video, error) - GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, error) + GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, int, error) AddCommentUsecase(request *dto.AddCommentRequest, userId string) error } diff --git a/internal/video/user_video/usecase/user_video_usecase_impl.go b/internal/video/user_video/usecase/user_video_usecase_impl.go index 0868da1..0904773 100644 --- a/internal/video/user_video/usecase/user_video_usecase_impl.go +++ b/internal/video/user_video/usecase/user_video_usecase_impl.go @@ -71,19 +71,19 @@ func (usecase *UserVideoUsecaseImpl) SearchVideoByCategoryUsecase(categoryType s return videos, nil } -func (usecase *UserVideoUsecaseImpl) GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, error) { - video, comments, err := usecase.Repository.GetVideoDetail(id) +func (usecase *UserVideoUsecaseImpl) GetVideoDetailUsecase(id int) (*video.Video, *[]video.Comment, int, error) { + video, comments, totalComment, err := usecase.Repository.GetVideoDetail(id) if err != nil { if err == gorm.ErrRecordNotFound { - return nil, nil, pkg.ErrVideoNotFound + return nil, nil, 0, pkg.ErrVideoNotFound } - return nil, nil, err + return nil, nil, 0, err } - return video, comments, nil + return video, comments, totalComment, nil } func (usecase *UserVideoUsecaseImpl) AddCommentUsecase(request *dto.AddCommentRequest, userId string) error { - if _, _, err := usecase.Repository.GetVideoDetail(request.VideoID); err != nil { + if _, _, _, err := usecase.Repository.GetVideoDetail(request.VideoID); err != nil { if err == gorm.ErrRecordNotFound { return pkg.ErrVideoNotFound } From 0dbf8a745a48169879577a83b4fa88cb758001f4 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 17:54:10 +0700 Subject: [PATCH 65/72] docs: add query params on get video detail --- docs/swagger.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/swagger.yml b/docs/swagger.yml index 919fd9b..b4efa48 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -3661,6 +3661,14 @@ paths: type: integer example: 1 description: ID of video + - name : sort-comments + in: query + required: false + schema: + type: string + example: true + default: false + description: Sort comments responses: '200': description: Success @@ -3696,6 +3704,9 @@ paths: viewer: type: integer example: 1000 + total_comment: + type: integer + example: 10 comments: type: array items: @@ -3713,6 +3724,9 @@ paths: user_name: type: string example: John Doe + user_profile: + type: string + example: "https://example.com/profile.jpg" created_at: type: string format: date From f6853c81cd3610aa260ab7f1eeb84dec1644f620 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 19:51:25 +0700 Subject: [PATCH 66/72] feat(user task): add some repository and usecase for feature update status task step --- .../manage_task/entity/manage_task_entity.go | 1 + .../task/user_task/dto/user_task_request.go | 5 ++++ .../repository/user_task_repository.go | 2 ++ .../repository/user_task_repository_impl.go | 21 ++++++++++++++++ .../user_task/usecase/user_task_usecase.go | 1 + .../usecase/user_task_usecase_impl.go | 24 +++++++++++++++++++ pkg/errors.go | 1 + 7 files changed, 55 insertions(+) diff --git a/internal/task/manage_task/entity/manage_task_entity.go b/internal/task/manage_task/entity/manage_task_entity.go index 4814959..b6d10cf 100644 --- a/internal/task/manage_task/entity/manage_task_entity.go +++ b/internal/task/manage_task/entity/manage_task_entity.go @@ -29,6 +29,7 @@ type TaskStep struct { TaskChallengeId string `gorm:"index"` Title string Description string + Status bool `gorm:"default:false"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` diff --git a/internal/task/user_task/dto/user_task_request.go b/internal/task/user_task/dto/user_task_request.go index 7b68efd..eb45ea6 100644 --- a/internal/task/user_task/dto/user_task_request.go +++ b/internal/task/user_task/dto/user_task_request.go @@ -11,3 +11,8 @@ type UpdateUserTaskRequest struct { Description string `json:"description" validate:"required"` Images []*multipart.FileHeader `json:"-"` } + +type UpdateTaskStepRequest struct { + StepId int `json:"step_id" validate:"required"` + UserTask string `json:"user_task" validate:"required"` +} diff --git a/internal/task/user_task/repository/user_task_repository.go b/internal/task/user_task/repository/user_task_repository.go index 3d0f914..d3c71a2 100644 --- a/internal/task/user_task/repository/user_task_repository.go +++ b/internal/task/user_task/repository/user_task_repository.go @@ -19,4 +19,6 @@ type UserTaskRepository interface { UpdateUserTask(userTask *user_task.UserTaskChallenge, userTaskId string) (*user_task.UserTaskChallenge, error) GetUserTaskDetails(userTaskId string, userId string) (*user_task.UserTaskChallenge, []*user_task.UserTaskImage, error) GetHistoryPointByUserId(userId string) ([]user_task.UserTaskChallenge, error) + FindTaskStep(stepId int, taskId string) error + UpdateTaskStep(stepId int, taskId string) error } diff --git a/internal/task/user_task/repository/user_task_repository_impl.go b/internal/task/user_task/repository/user_task_repository_impl.go index fcabf1d..86c5b4f 100644 --- a/internal/task/user_task/repository/user_task_repository_impl.go +++ b/internal/task/user_task/repository/user_task_repository_impl.go @@ -243,3 +243,24 @@ func (repository *UserTaskRepositoryImpl) GetHistoryPointByUserId(userId string) } return userTask, nil } + +func (repository *UserTaskRepositoryImpl) FindTaskStep(stepId int, taskId string) error { + var taskStep task.TaskStep + if err := repository.DB.GetDB(). + Where("id = ?", stepId). + Where("task_challenge_id = ?", taskId). + First(&taskStep).Error; err != nil { + return err + } + return nil +} + +func (repository *UserTaskRepositoryImpl) UpdateTaskStep(stepId int, taskId string) error { + if err := repository.DB.GetDB().Model(&task.TaskStep{}). + Where("id = ?", stepId). + Where("task_challenge_id = ?", taskId). + Update("status", true).Error; err != nil { + return err + } + return nil +} diff --git a/internal/task/user_task/usecase/user_task_usecase.go b/internal/task/user_task/usecase/user_task_usecase.go index 7440317..97e9419 100644 --- a/internal/task/user_task/usecase/user_task_usecase.go +++ b/internal/task/user_task/usecase/user_task_usecase.go @@ -18,4 +18,5 @@ type UserTaskUsecase interface { UpdateUserTaskUsecase(request *dto.UpdateUserTaskRequest, fileImage []*multipart.FileHeader, userId string, userTaskId string) (*user_task.UserTaskChallenge, error) GetUserTaskDetailsUsecase(userTaskId string, userId string) (*user_task.UserTaskChallenge, []*user_task.UserTaskImage, error) GetHistoryPointByUserIdUsecase(userId string) ([]user_task.UserTaskChallenge, int, error) + UpdateTaskStepUsecase(request *dto.UpdateTaskStepRequest, userId string) (*user_task.UserTaskChallenge, error) } diff --git a/internal/task/user_task/usecase/user_task_usecase_impl.go b/internal/task/user_task/usecase/user_task_usecase_impl.go index 474ceb4..d56b366 100644 --- a/internal/task/user_task/usecase/user_task_usecase_impl.go +++ b/internal/task/user_task/usecase/user_task_usecase_impl.go @@ -234,3 +234,27 @@ func (usecase *UserTaskUsecaseImpl) GetHistoryPointByUserIdUsecase(userId string } return userTask, totalPoint, nil } + +func (usecase *UserTaskUsecaseImpl) UpdateTaskStepUsecase(request *dto.UpdateTaskStepRequest, userId string) (*user_task.UserTaskChallenge, error) { + userTask, errUserTask := usecase.ManageTaskRepository.FindUserTask(userId, request.UserTask) + + if errUserTask != nil { + return nil, pkg.ErrUserTaskNotFound + } + if userTask.StatusAccept != "in-progress" { + return nil, pkg.ErrUserTaskDone + } + errStatus := usecase.ManageTaskRepository.FindTaskStep(request.StepId, userTask.TaskChallengeId) + + if errStatus != nil { + return nil, pkg.ErrTaskStepNotFound + } + + errTaskStep := usecase.ManageTaskRepository.UpdateTaskStep(request.StepId, userTask.TaskChallengeId) + if errTaskStep != nil { + return nil, errTaskStep + } + + return userTask, nil + +} diff --git a/pkg/errors.go b/pkg/errors.go index 484e103..ae7b075 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -46,6 +46,7 @@ var ( ErrUserTaskNotReject = errors.New("user task not reject") ErrUserTaskAlreadyReject = errors.New("user task already reject") ErrUserTaskAlreadyApprove = errors.New("user task already approve") + ErrTaskStepNotFound = errors.New("task step not found") // manage achievement ErrAchievementLevelAlreadyExist = errors.New("archievement level already exist") From 2a0b2d6728b6139c9b25d4c781c35900d0d5a2be Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 21:54:14 +0700 Subject: [PATCH 67/72] feat(task): user tas, add enpoint for update status user steps --- internal/database/migrate.go | 1 + internal/server/route.go | 3 + .../manage_task/entity/manage_task_entity.go | 1 - .../task/user_task/dto/user_task_request.go | 4 +- .../task/user_task/dto/user_task_response.go | 36 ++++-- .../task/user_task/entity/user_task_entity.go | 11 ++ .../user_task/handler/user_task_handler.go | 1 + .../handler/user_task_handler_impl.go | 117 +++++++++++++++++- .../repository/user_task_repository.go | 5 +- .../repository/user_task_repository_impl.go | 48 +++++-- .../user_task/usecase/user_task_usecase.go | 1 + .../usecase/user_task_usecase_impl.go | 58 +++++++-- pkg/errors.go | 2 + 13 files changed, 246 insertions(+), 42 deletions(-) diff --git a/internal/database/migrate.go b/internal/database/migrate.go index 080f2a1..f1f86b7 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -42,6 +42,7 @@ func AutoMigrate(db Database) { &article.ArticleSection{}, &article.ArticleCategories{}, &article.ArticleComment{}, + &user_task.UserTaskStep{}, ); err != nil { log.Fatal("Database Migration Failed!") } diff --git a/internal/server/route.go b/internal/server/route.go index 668c902..263536f 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -242,6 +242,9 @@ func (s *echoServer) userTask() { // get history point by user current s.gr.GET("/user-current/tasks/history", handler.GetHistoryPointByUserIdHandler, UserMiddleware) + + // update user task step + s.gr.PUT("/user-current/steps", handler.UpdateTaskStepHandler, UserMiddleware) } func (s *echoServer) approvalTask() { diff --git a/internal/task/manage_task/entity/manage_task_entity.go b/internal/task/manage_task/entity/manage_task_entity.go index b6d10cf..4814959 100644 --- a/internal/task/manage_task/entity/manage_task_entity.go +++ b/internal/task/manage_task/entity/manage_task_entity.go @@ -29,7 +29,6 @@ type TaskStep struct { TaskChallengeId string `gorm:"index"` Title string Description string - Status bool `gorm:"default:false"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` diff --git a/internal/task/user_task/dto/user_task_request.go b/internal/task/user_task/dto/user_task_request.go index eb45ea6..894eee0 100644 --- a/internal/task/user_task/dto/user_task_request.go +++ b/internal/task/user_task/dto/user_task_request.go @@ -13,6 +13,6 @@ type UpdateUserTaskRequest struct { } type UpdateTaskStepRequest struct { - StepId int `json:"step_id" validate:"required"` - UserTask string `json:"user_task" validate:"required"` + UserTask string `json:"user_task"` + StepId int `json:"step_id"` } diff --git a/internal/task/user_task/dto/user_task_response.go b/internal/task/user_task/dto/user_task_response.go index 3716215..951d5a2 100644 --- a/internal/task/user_task/dto/user_task_response.go +++ b/internal/task/user_task/dto/user_task_response.go @@ -24,6 +24,7 @@ type TaskSteps struct { Id int `json:"id"` Title string `json:"title"` Description string `json:"description"` + Status bool `json:"status"` } type UserTaskResponseCreate struct { @@ -33,15 +34,24 @@ type UserTaskResponseCreate struct { } type TaskChallengeResponseCreate struct { - Id string `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - Thumbnail string `json:"thumbnail"` - StartDate time.Time `json:"start_date"` - EndDate time.Time `json:"end_date"` - Point int `json:"point"` - StatusTask bool `json:"status_task"` - TaskSteps []TaskSteps `json:"task_steps"` + Id string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Thumbnail string `json:"thumbnail"` + StartDate time.Time `json:"start_date"` + EndDate time.Time `json:"end_date"` + Point int `json:"point"` + StatusTask bool `json:"status_task"` + TaskSteps []TaskSteps `json:"task_steps"` + UserSteps []DataUserSteps `json:"user_steps"` +} + +type DataUserSteps struct { + Id int `json:"id"` + UserTaskChallengeID string `gorm:"index"` + TaskStepID int `gorm:"index"` + Completed bool `gorm:"default:false"` + CompletedAt time.Time `gorm:"type:datetime"` } type UserTaskUploadImageResponse struct { @@ -51,6 +61,7 @@ type UserTaskUploadImageResponse struct { Point int `json:"point"` TaskChallenge DataTaskChallenges `json:"task_challenge"` Images []Images `json:"images"` + UserSteps []DataUserSteps `json:"user_steps"` } type Images struct { @@ -70,6 +81,7 @@ type GetUserTaskDoneByIdUserResponse struct { Point int `json:"point"` ReasonReject string `json:"reason_reject"` TaskChallenge DataTaskChallenges `json:"task_challenge"` + UserSteps []DataUserSteps `json:"user_steps"` } type DataTaskChallenges struct { @@ -107,3 +119,9 @@ type HistoryPointResponse struct { TotalPoint int `json:"total_point"` Data []*DataHistoryPoint `json:"data_history_point"` } + +type UpdateTaskStep struct { + Id string `json:"id"` + StatusProgress string `json:"status_progress"` + TaskChalenge TaskChallengeResponseCreate `json:"task_challenge"` +} diff --git a/internal/task/user_task/entity/user_task_entity.go b/internal/task/user_task/entity/user_task_entity.go index b017696..0dcd5ab 100644 --- a/internal/task/user_task/entity/user_task_entity.go +++ b/internal/task/user_task/entity/user_task_entity.go @@ -20,6 +20,7 @@ type UserTaskChallenge struct { DescriptionImage string Point int Reason string + UserTaskSteps []UserTaskStep `gorm:"foreignKey:UserTaskChallengeID"` AcceptedAt time.Time `gorm:"column:accepted_at;type:datetime"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` @@ -34,3 +35,13 @@ type UserTaskImage struct { UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` } + +type UserTaskStep struct { + ID int `gorm:"primaryKey"` + UserTaskChallengeID string `gorm:"index"` + TaskStepID int `gorm:"index"` + Completed bool `gorm:"default:false"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index"` +} diff --git a/internal/task/user_task/handler/user_task_handler.go b/internal/task/user_task/handler/user_task_handler.go index 4b90e2e..0744bc4 100644 --- a/internal/task/user_task/handler/user_task_handler.go +++ b/internal/task/user_task/handler/user_task_handler.go @@ -12,4 +12,5 @@ type UserTaskHandler interface { UpdateUserTaskHandler(c echo.Context) error GetUserTaskDetailsHandler(c echo.Context) error GetHistoryPointByUserIdHandler(c echo.Context) error + UpdateTaskStepHandler(c echo.Context) error } diff --git a/internal/task/user_task/handler/user_task_handler_impl.go b/internal/task/user_task/handler/user_task_handler_impl.go index 423d13b..8ab5e8b 100644 --- a/internal/task/user_task/handler/user_task_handler_impl.go +++ b/internal/task/user_task/handler/user_task_handler_impl.go @@ -16,7 +16,7 @@ type UserTaskHandlerImpl struct { Usecase usecase.UserTaskUsecase } -func NewUserTaskHandler(usecase usecase.UserTaskUsecase) *UserTaskHandlerImpl { +func NewUserTaskHandler(usecase usecase.UserTaskUsecase) UserTaskHandler { return &UserTaskHandlerImpl{Usecase: usecase} } @@ -104,6 +104,7 @@ func (handler *UserTaskHandlerImpl) CreateUserTaskHandler(c echo.Context) error } var taskStep []dto.TaskSteps + var userSteps []dto.DataUserSteps for _, step := range userTask.TaskChallenge.TaskSteps { taskStep = append(taskStep, dto.TaskSteps{ @@ -112,6 +113,15 @@ func (handler *UserTaskHandlerImpl) CreateUserTaskHandler(c echo.Context) error Description: step.Description, }) } + + for _, step := range userTask.UserTaskSteps { + userSteps = append(userSteps, dto.DataUserSteps{ + Id: step.ID, + UserTaskChallengeID: step.UserTaskChallengeID, + TaskStepID: step.TaskStepID, + Completed: step.Completed, + }) + } data := dto.TaskChallengeResponseCreate{ Id: userTask.TaskChallenge.ID, Title: userTask.TaskChallenge.Title, @@ -122,15 +132,15 @@ func (handler *UserTaskHandlerImpl) CreateUserTaskHandler(c echo.Context) error Point: userTask.TaskChallenge.Point, StatusTask: userTask.TaskChallenge.Status, TaskSteps: taskStep, + UserSteps: userSteps, } dataUsertask := dto.UserTaskResponseCreate{ Id: userTask.ID, - TaskChalenge: data, StatusProgress: userTask.StatusProgress, + TaskChalenge: data, } responseData := helper.ResponseData(http.StatusCreated, "success", dataUsertask) return c.JSON(http.StatusCreated, responseData) - } func (handler *UserTaskHandlerImpl) UploadImageTaskHandler(c echo.Context) error { @@ -183,6 +193,7 @@ func (handler *UserTaskHandlerImpl) UploadImageTaskHandler(c echo.Context) error } var taskStep []dto.TaskSteps var urlImages []dto.Images + var userSteps []dto.DataUserSteps data := dto.UserTaskUploadImageResponse{ Id: userTask.ID, StatusProgress: userTask.StatusProgress, @@ -198,7 +209,17 @@ func (handler *UserTaskHandlerImpl) UploadImageTaskHandler(c echo.Context) error StatusTask: userTask.TaskChallenge.Status, TaskSteps: taskStep, }, - Images: urlImages, + Images: urlImages, + UserSteps: userSteps, + } + + for _, step := range userTask.UserTaskSteps { + userSteps = append(userSteps, dto.DataUserSteps{ + Id: step.ID, + UserTaskChallengeID: step.UserTaskChallengeID, + TaskStepID: step.TaskStepID, + Completed: step.Completed, + }) } for _, step := range userTask.TaskChallenge.TaskSteps { @@ -213,6 +234,7 @@ func (handler *UserTaskHandlerImpl) UploadImageTaskHandler(c echo.Context) error Images: image.ImageUrl, }) } + data.UserSteps = userSteps data.TaskChallenge.TaskSteps = taskStep data.Images = urlImages responseData := helper.ResponseData(http.StatusCreated, "success", data) @@ -245,6 +267,7 @@ func (handler *UserTaskHandlerImpl) GetUserTaskByUserIdHandler(c echo.Context) e Point: userTask.TaskChallenge.Point, StatusTask: userTask.TaskChallenge.Status, TaskSteps: []dto.TaskSteps{}, + UserSteps: []dto.DataUserSteps{}, }, }) @@ -255,6 +278,15 @@ func (handler *UserTaskHandlerImpl) GetUserTaskByUserIdHandler(c echo.Context) e Description: step.Description, }) } + + for _, step := range userTask.UserTaskSteps { + data[len(data)-1].TaskChallenge.UserSteps = append(data[len(data)-1].TaskChallenge.UserSteps, dto.DataUserSteps{ + Id: step.ID, + UserTaskChallengeID: step.UserTaskChallengeID, + TaskStepID: step.TaskStepID, + Completed: step.Completed, + }) + } } responseData := helper.ResponseData(http.StatusOK, "success", data) @@ -289,6 +321,7 @@ func (handler *UserTaskHandlerImpl) GetUserTaskDoneByUserIdHandler(c echo.Contex StatusTask: userTask.TaskChallenge.Status, TaskSteps: []dto.TaskSteps{}, }, + UserSteps: []dto.DataUserSteps{}, }) for _, step := range userTask.TaskChallenge.TaskSteps { @@ -298,6 +331,15 @@ func (handler *UserTaskHandlerImpl) GetUserTaskDoneByUserIdHandler(c echo.Contex Description: step.Description, }) } + + for _, step := range userTask.UserTaskSteps { + data[len(data)-1].UserSteps = append(data[len(data)-1].UserSteps, dto.DataUserSteps{ + Id: step.ID, + UserTaskChallengeID: step.UserTaskChallengeID, + TaskStepID: step.TaskStepID, + Completed: step.Completed, + }) + } } responseData := helper.ResponseData(http.StatusOK, "success", data) @@ -357,6 +399,7 @@ func (handler *UserTaskHandlerImpl) UpdateUserTaskHandler(c echo.Context) error } var taskStep []dto.TaskSteps var urlImages []dto.Images + var userSteps []dto.DataUserSteps data := dto.UserTaskUploadImageResponse{ Id: userTask.ID, StatusProgress: userTask.StatusProgress, @@ -372,7 +415,17 @@ func (handler *UserTaskHandlerImpl) UpdateUserTaskHandler(c echo.Context) error StatusTask: userTask.TaskChallenge.Status, TaskSteps: taskStep, }, - Images: urlImages, + Images: urlImages, + UserSteps: userSteps, + } + + for _, step := range userTask.UserTaskSteps { + userSteps = append(userSteps, dto.DataUserSteps{ + Id: step.ID, + UserTaskChallengeID: step.UserTaskChallengeID, + TaskStepID: step.TaskStepID, + Completed: step.Completed, + }) } for _, step := range userTask.TaskChallenge.TaskSteps { @@ -387,6 +440,8 @@ func (handler *UserTaskHandlerImpl) UpdateUserTaskHandler(c echo.Context) error Images: image.ImageUrl, }) } + + data.UserSteps = userSteps data.TaskChallenge.TaskSteps = taskStep data.Images = urlImages responseData := helper.ResponseData(http.StatusCreated, "success", data) @@ -454,3 +509,55 @@ func (handler *UserTaskHandlerImpl) GetHistoryPointByUserIdHandler(c echo.Contex responseData := helper.ResponseData(http.StatusOK, "success", data) return c.JSON(http.StatusOK, responseData) } + +func (handler *UserTaskHandlerImpl) UpdateTaskStepHandler(c echo.Context) error { + userId := c.Get("user").(*helper.JwtCustomClaims).UserID + request := new(dto.UpdateTaskStepRequest) + if err := c.Bind(request); err != nil { + return helper.ErrorHandler(c, http.StatusBadRequest, "invalid request body") + } + + userTask, err := handler.Usecase.UpdateTaskStepUsecase(request, userId) + if err != nil { + if errors.Is(err, pkg.ErrUserTaskNotFound) { + return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrUserTaskNotFound.Error()) + } + if errors.Is(err, pkg.ErrUserTaskDone) { + return helper.ErrorHandler(c, http.StatusConflict, pkg.ErrUserTaskDone.Error()) + } + if errors.Is(err, pkg.ErrTaskStepNotFound) { + return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrTaskStepNotFound.Error()) + } + return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail: "+err.Error()) + } + + var taskStep []dto.TaskSteps + for _, step := range userTask.TaskChallenge.TaskSteps { + taskStep = append(taskStep, dto.TaskSteps{ + Id: step.ID, + Title: step.Title, + Description: step.Description, + }) + } + + data := dto.TaskChallengeResponseCreate{ + Id: userTask.TaskChallenge.ID, + Title: userTask.TaskChallenge.Title, + Description: userTask.TaskChallenge.Description, + Thumbnail: userTask.TaskChallenge.Thumbnail, + StartDate: userTask.TaskChallenge.StartDate, + EndDate: userTask.TaskChallenge.EndDate, + Point: userTask.TaskChallenge.Point, + StatusTask: userTask.TaskChallenge.Status, + TaskSteps: taskStep, + } + + dataUsertask := dto.UserTaskResponseCreate{ + Id: userTask.ID, + TaskChalenge: data, + StatusProgress: userTask.StatusProgress, + } + + responseData := helper.ResponseData(http.StatusOK, "success", dataUsertask) + return c.JSON(http.StatusOK, responseData) +} diff --git a/internal/task/user_task/repository/user_task_repository.go b/internal/task/user_task/repository/user_task_repository.go index d3c71a2..311920c 100644 --- a/internal/task/user_task/repository/user_task_repository.go +++ b/internal/task/user_task/repository/user_task_repository.go @@ -19,6 +19,7 @@ type UserTaskRepository interface { UpdateUserTask(userTask *user_task.UserTaskChallenge, userTaskId string) (*user_task.UserTaskChallenge, error) GetUserTaskDetails(userTaskId string, userId string) (*user_task.UserTaskChallenge, []*user_task.UserTaskImage, error) GetHistoryPointByUserId(userId string) ([]user_task.UserTaskChallenge, error) - FindTaskStep(stepId int, taskId string) error - UpdateTaskStep(stepId int, taskId string) error + FindTaskStep(stepId int, taskId string) (*task.TaskStep, error) + FindUserTaskStep(userTaskChallengeID string, taskStepID int) (*user_task.UserTaskStep, error) + UpdateUserTaskStep(userTaskStep *user_task.UserTaskStep) error } diff --git a/internal/task/user_task/repository/user_task_repository_impl.go b/internal/task/user_task/repository/user_task_repository_impl.go index 86c5b4f..90e4747 100644 --- a/internal/task/user_task/repository/user_task_repository_impl.go +++ b/internal/task/user_task/repository/user_task_repository_impl.go @@ -50,10 +50,25 @@ func (repository *UserTaskRepositoryImpl) FindLastIdTaskChallenge() (string, err func (repository *UserTaskRepositoryImpl) CreateUserTask(userTask *user_task.UserTaskChallenge) (*user_task.UserTaskChallenge, error) { var result user_task.UserTaskChallenge err := repository.DB.GetDB().Transaction(func(tx *gorm.DB) error { + // Create the UserTaskChallenge if err := tx.Create(userTask).Error; err != nil { return err } + + // Create UserTaskSteps for each TaskStep in the TaskChallenge + for _, taskStep := range userTask.TaskChallenge.TaskSteps { + userTaskStep := user_task.UserTaskStep{ + UserTaskChallengeID: userTask.ID, + TaskStepID: taskStep.ID, + Completed: false, + } + if err := tx.Create(&userTaskStep).Error; err != nil { + return err + } + } + if err := tx.Preload("TaskChallenge.TaskSteps"). + Preload("UserTaskSteps"). Where("user_task_challenges.id = ?", userTask.ID). First(&result).Error; err != nil { return err @@ -71,7 +86,11 @@ func (repository *UserTaskRepositoryImpl) CreateUserTask(userTask *user_task.Use func (repository *UserTaskRepositoryImpl) FindUserTask(userId string, userTaskId string) (*user_task.UserTaskChallenge, error) { var userTask user_task.UserTaskChallenge - if err := repository.DB.GetDB().Where("user_id = ? and id = ?", userId, userTaskId).First(&userTask).Error; err != nil { + if err := repository.DB.GetDB(). + Preload("TaskChallenge.TaskSteps"). + Preload("UserTaskSteps"). + Where("user_id = ? and id = ?", userId, userTaskId). + First(&userTask).Error; err != nil { return nil, err } return &userTask, nil @@ -121,6 +140,7 @@ func (repository *UserTaskRepositoryImpl) UploadImageTask(userTask *user_task.Us tx.Preload("UserTaskImage"). Preload("TaskChallenge.TaskSteps"). + Preload("UserTaskSteps"). Where("id = ?", userTaskId). First(&userTask) @@ -136,6 +156,7 @@ func (repository *UserTaskRepositoryImpl) GetUserTaskByUserId(userId string) ([] var userTask []user_task.UserTaskChallenge if err := repository.DB.GetDB(). Preload("TaskChallenge.TaskSteps"). + Preload("UserTaskSteps"). Where("user_id = ? and status_progress = ?", userId, "in_progress"). Order("id desc"). Find(&userTask).Error; err != nil { @@ -148,6 +169,7 @@ func (repository *UserTaskRepositoryImpl) GetUserTaskDoneByUserId(userId string) var userTask []user_task.UserTaskChallenge if err := repository.DB.GetDB(). Preload("TaskChallenge.TaskSteps"). + Preload("UserTaskSteps"). Where("user_id = ? and status_progress = ?", userId, "done"). Order("id desc"). Find(&userTask).Error; err != nil { @@ -200,6 +222,7 @@ func (repository *UserTaskRepositoryImpl) UpdateUserTask(userTask *user_task.Use tx.Preload("UserTaskImage"). Preload("TaskChallenge.TaskSteps"). + Preload("UserTaskSteps"). Where("id = ?", userTaskId). First(&userTask) @@ -216,6 +239,7 @@ func (repository *UserTaskRepositoryImpl) GetUserTaskDetails(userTaskId string, if err := repository.DB.GetDB(). Preload("User"). Preload("TaskChallenge"). + Preload("UserTaskSteps"). Where("id = ? ", userTaskId). Where("user_id = ?", userId). First(&userTask).Error; err != nil { @@ -244,23 +268,23 @@ func (repository *UserTaskRepositoryImpl) GetHistoryPointByUserId(userId string) return userTask, nil } -func (repository *UserTaskRepositoryImpl) FindTaskStep(stepId int, taskId string) error { +func (repository *UserTaskRepositoryImpl) FindTaskStep(stepId int, taskId string) (*task.TaskStep, error) { var taskStep task.TaskStep if err := repository.DB.GetDB(). Where("id = ?", stepId). Where("task_challenge_id = ?", taskId). First(&taskStep).Error; err != nil { - return err + return nil, err } - return nil + return &taskStep, nil } -func (repository *UserTaskRepositoryImpl) UpdateTaskStep(stepId int, taskId string) error { - if err := repository.DB.GetDB().Model(&task.TaskStep{}). - Where("id = ?", stepId). - Where("task_challenge_id = ?", taskId). - Update("status", true).Error; err != nil { - return err - } - return nil +func (repository *UserTaskRepositoryImpl) UpdateUserTaskStep(userTaskStep *user_task.UserTaskStep) error { + return repository.DB.GetDB().Save(userTaskStep).Error +} + +func (repository *UserTaskRepositoryImpl) FindUserTaskStep(userTaskChallengeID string, taskStepID int) (*user_task.UserTaskStep, error) { + var userTaskStep user_task.UserTaskStep + err := repository.DB.GetDB().Where("user_task_challenge_id = ? AND task_step_id = ?", userTaskChallengeID, taskStepID).First(&userTaskStep).Error + return &userTaskStep, err } diff --git a/internal/task/user_task/usecase/user_task_usecase.go b/internal/task/user_task/usecase/user_task_usecase.go index 97e9419..534d929 100644 --- a/internal/task/user_task/usecase/user_task_usecase.go +++ b/internal/task/user_task/usecase/user_task_usecase.go @@ -19,4 +19,5 @@ type UserTaskUsecase interface { GetUserTaskDetailsUsecase(userTaskId string, userId string) (*user_task.UserTaskChallenge, []*user_task.UserTaskImage, error) GetHistoryPointByUserIdUsecase(userId string) ([]user_task.UserTaskChallenge, int, error) UpdateTaskStepUsecase(request *dto.UpdateTaskStepRequest, userId string) (*user_task.UserTaskChallenge, error) + GetUserTaskByUserTaskId(userId string, userTaskId string) (*user_task.UserTaskChallenge, error) } diff --git a/internal/task/user_task/usecase/user_task_usecase_impl.go b/internal/task/user_task/usecase/user_task_usecase_impl.go index d56b366..7e571f8 100644 --- a/internal/task/user_task/usecase/user_task_usecase_impl.go +++ b/internal/task/user_task/usecase/user_task_usecase_impl.go @@ -1,6 +1,7 @@ package usecase import ( + "errors" "mime/multipart" "time" @@ -10,6 +11,7 @@ import ( user_task "github.com/sawalreverr/recything/internal/task/user_task/entity" "github.com/sawalreverr/recything/internal/task/user_task/repository" "github.com/sawalreverr/recything/pkg" + "gorm.io/gorm" ) type UserTaskUsecaseImpl struct { @@ -44,7 +46,7 @@ func (usecase *UserTaskUsecaseImpl) CreateUserTaskUsecase(taskChallengeId string return nil, pkg.ErrTaskNotFound } - if findtask.Status == false { + if !findtask.Status { return nil, pkg.ErrTaskCannotBeFollowed } @@ -64,14 +66,18 @@ func (usecase *UserTaskUsecaseImpl) CreateUserTaskUsecase(taskChallengeId string TaskChallengeId: taskChallengeId, AcceptedAt: time.Now(), ImageTask: []user_task.UserTaskImage{}, + StatusProgress: "in_progress", + UserTaskSteps: []user_task.UserTaskStep{}, // Initialize the UserTaskSteps } + // Attach TaskSteps to the UserTaskChallenge + userTask.TaskChallenge = *findtask + userTaskData, err := usecase.ManageTaskRepository.CreateUserTask(userTask) if err != nil { return nil, err } return userTaskData, nil - } func (usecase *UserTaskUsecaseImpl) UploadImageTaskUsecase(request *dto.UploadImageTask, fileImage []*multipart.FileHeader, userId string, userTaskId string) (*user_task.UserTaskChallenge, error) { @@ -237,24 +243,54 @@ func (usecase *UserTaskUsecaseImpl) GetHistoryPointByUserIdUsecase(userId string func (usecase *UserTaskUsecaseImpl) UpdateTaskStepUsecase(request *dto.UpdateTaskStepRequest, userId string) (*user_task.UserTaskChallenge, error) { userTask, errUserTask := usecase.ManageTaskRepository.FindUserTask(userId, request.UserTask) - if errUserTask != nil { return nil, pkg.ErrUserTaskNotFound } - if userTask.StatusAccept != "in-progress" { + + if userTask.StatusAccept != "in_progress" { return nil, pkg.ErrUserTaskDone } - errStatus := usecase.ManageTaskRepository.FindTaskStep(request.StepId, userTask.TaskChallengeId) - if errStatus != nil { - return nil, pkg.ErrTaskStepNotFound + taskStep, errStep := usecase.ManageTaskRepository.FindTaskStep(request.StepId, userTask.TaskChallengeId) + if errStep != nil { + if errStep == gorm.ErrRecordNotFound { + return nil, pkg.ErrTaskStepNotFound + } + return nil, errStep } - errTaskStep := usecase.ManageTaskRepository.UpdateTaskStep(request.StepId, userTask.TaskChallengeId) - if errTaskStep != nil { - return nil, errTaskStep + userTaskStep, errUserTaskStep := usecase.ManageTaskRepository.FindUserTaskStep(userTask.ID, taskStep.ID) + if errUserTaskStep != nil { + return nil, pkg.ErrUserTaskStepNotFound } - return userTask, nil + userTaskStep.Completed = true + + if err := usecase.ManageTaskRepository.UpdateUserTaskStep(userTaskStep); err != nil { + return nil, err + } + + // Re-fetch updated user task with its relations + updatedUserTask, err := usecase.ManageTaskRepository.FindUserTask(userId, request.UserTask) + if err != nil { + return nil, err + } + + return updatedUserTask, nil +} +func (usecase *UserTaskUsecaseImpl) GetUserTaskByUserTaskId(userId string, userTaskId string) (*user_task.UserTaskChallenge, error) { + + userTask, err := usecase.ManageTaskRepository.FindUserTask(userId, userTaskId) + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, pkg.ErrUserTaskNotFound + } + return nil, err + } + if (userTask.StatusProgress != "in_progress" && userTask.StatusAccept != "need_rivew") || userTask.StatusAccept == "reject" { + return nil, pkg.ErrUserTaskDone + } + return userTask, nil } diff --git a/pkg/errors.go b/pkg/errors.go index dd4a6f7..973383b 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -48,6 +48,8 @@ var ( ErrUserTaskAlreadyReject = errors.New("user task already reject") ErrUserTaskAlreadyApprove = errors.New("user task already approve") ErrTaskStepNotFound = errors.New("task step not found") + ErrTaskStepDone = errors.New("task step already done") + ErrUserTaskStepNotFound = errors.New("user task step not found") // manage achievement ErrAchievementLevelAlreadyExist = errors.New("archievement level already exist") From dc4b030963de91eb241e47935ce2074c6a977b6b Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 22:11:26 +0700 Subject: [PATCH 68/72] feat(task): user task, add enpoint for get user task by user task id --- internal/server/route.go | 3 ++ .../task/user_task/dto/user_task_response.go | 10 ++-- .../user_task/handler/user_task_handler.go | 1 + .../handler/user_task_handler_impl.go | 50 +++++++++++++++++++ 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/internal/server/route.go b/internal/server/route.go index 263536f..8ef2db8 100644 --- a/internal/server/route.go +++ b/internal/server/route.go @@ -245,6 +245,9 @@ func (s *echoServer) userTask() { // update user task step s.gr.PUT("/user-current/steps", handler.UpdateTaskStepHandler, UserMiddleware) + + // get user task by user task id + s.gr.GET("/user/task/:userTaskId", handler.GetUserTaskByUserTaskIdHandler, UserMiddleware) } func (s *echoServer) approvalTask() { diff --git a/internal/task/user_task/dto/user_task_response.go b/internal/task/user_task/dto/user_task_response.go index 951d5a2..a9dca78 100644 --- a/internal/task/user_task/dto/user_task_response.go +++ b/internal/task/user_task/dto/user_task_response.go @@ -24,7 +24,6 @@ type TaskSteps struct { Id int `json:"id"` Title string `json:"title"` Description string `json:"description"` - Status bool `json:"status"` } type UserTaskResponseCreate struct { @@ -47,11 +46,10 @@ type TaskChallengeResponseCreate struct { } type DataUserSteps struct { - Id int `json:"id"` - UserTaskChallengeID string `gorm:"index"` - TaskStepID int `gorm:"index"` - Completed bool `gorm:"default:false"` - CompletedAt time.Time `gorm:"type:datetime"` + Id int `json:"id"` + UserTaskChallengeID string `gorm:"index"` + TaskStepID int `gorm:"index"` + Completed bool `gorm:"default:false"` } type UserTaskUploadImageResponse struct { diff --git a/internal/task/user_task/handler/user_task_handler.go b/internal/task/user_task/handler/user_task_handler.go index 0744bc4..ca155e8 100644 --- a/internal/task/user_task/handler/user_task_handler.go +++ b/internal/task/user_task/handler/user_task_handler.go @@ -13,4 +13,5 @@ type UserTaskHandler interface { GetUserTaskDetailsHandler(c echo.Context) error GetHistoryPointByUserIdHandler(c echo.Context) error UpdateTaskStepHandler(c echo.Context) error + GetUserTaskByUserTaskIdHandler(c echo.Context) error } diff --git a/internal/task/user_task/handler/user_task_handler_impl.go b/internal/task/user_task/handler/user_task_handler_impl.go index 8ab5e8b..fdf8018 100644 --- a/internal/task/user_task/handler/user_task_handler_impl.go +++ b/internal/task/user_task/handler/user_task_handler_impl.go @@ -561,3 +561,53 @@ func (handler *UserTaskHandlerImpl) UpdateTaskStepHandler(c echo.Context) error responseData := helper.ResponseData(http.StatusOK, "success", dataUsertask) return c.JSON(http.StatusOK, responseData) } + +func (handler *UserTaskHandlerImpl) GetUserTaskByUserTaskIdHandler(c echo.Context) error { + userId := c.Get("user").(*helper.JwtCustomClaims).UserID + userTaskId := c.Param("userTaskId") + userTask, err := handler.Usecase.GetUserTaskByUserTaskId(userId, userTaskId) + if err != nil { + if errors.Is(err, pkg.ErrUserTaskNotFound) { + return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrUserTaskNotFound.Error()) + } + return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail: "+err.Error()) + } + var taskStep []dto.TaskSteps + var userSteps []dto.DataUserSteps + + for _, step := range userTask.TaskChallenge.TaskSteps { + taskStep = append(taskStep, dto.TaskSteps{ + Id: step.ID, + Title: step.Title, + Description: step.Description, + }) + } + + for _, step := range userTask.UserTaskSteps { + userSteps = append(userSteps, dto.DataUserSteps{ + Id: step.ID, + UserTaskChallengeID: step.UserTaskChallengeID, + TaskStepID: step.TaskStepID, + Completed: step.Completed, + }) + } + data := dto.TaskChallengeResponseCreate{ + Id: userTask.TaskChallenge.ID, + Title: userTask.TaskChallenge.Title, + Description: userTask.TaskChallenge.Description, + Thumbnail: userTask.TaskChallenge.Thumbnail, + StartDate: userTask.TaskChallenge.StartDate, + EndDate: userTask.TaskChallenge.EndDate, + Point: userTask.TaskChallenge.Point, + StatusTask: userTask.TaskChallenge.Status, + TaskSteps: taskStep, + UserSteps: userSteps, + } + dataUsertask := dto.UserTaskResponseCreate{ + Id: userTask.ID, + StatusProgress: userTask.StatusProgress, + TaskChalenge: data, + } + responseData := helper.ResponseData(http.StatusCreated, "success", dataUsertask) + return c.JSON(http.StatusCreated, responseData) +} From ee1bab4c34e066edaee3bdff4f41f590ab608cdc Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Thu, 13 Jun 2024 22:39:42 +0700 Subject: [PATCH 69/72] fix: duplicate InitArticle on main.go --- cmd/api/main.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 328a7b8..b2b4647 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -51,9 +51,6 @@ func main() { // Init Videos db.InitDataVideos() - // Init Article - db.InitArticle() - app := server.NewEchoServer(conf, db) c := cron.New() From 5ecbf44d117b4662b7bf659729129ebed08cd906 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 13 Jun 2024 23:55:10 +0700 Subject: [PATCH 70/72] refactor(update user step): add some code for update user step must be in order --- .../task/user_task/dto/user_task_request.go | 4 +- .../handler/user_task_handler_impl.go | 31 +++++++++-- .../repository/user_task_repository.go | 2 + .../repository/user_task_repository_impl.go | 12 ++++ .../usecase/user_task_usecase_impl.go | 55 +++++++++++++++++-- pkg/errors.go | 29 +++++----- 6 files changed, 107 insertions(+), 26 deletions(-) diff --git a/internal/task/user_task/dto/user_task_request.go b/internal/task/user_task/dto/user_task_request.go index 894eee0..dc1d121 100644 --- a/internal/task/user_task/dto/user_task_request.go +++ b/internal/task/user_task/dto/user_task_request.go @@ -13,6 +13,6 @@ type UpdateUserTaskRequest struct { } type UpdateTaskStepRequest struct { - UserTask string `json:"user_task"` - StepId int `json:"step_id"` + UserTaskId string `json:"user_task_id"` + TaskStepId int `json:"task_step_id"` } diff --git a/internal/task/user_task/handler/user_task_handler_impl.go b/internal/task/user_task/handler/user_task_handler_impl.go index fdf8018..ecdbb91 100644 --- a/internal/task/user_task/handler/user_task_handler_impl.go +++ b/internal/task/user_task/handler/user_task_handler_impl.go @@ -189,6 +189,9 @@ func (handler *UserTaskHandlerImpl) UploadImageTaskHandler(c echo.Context) error if errors.Is(err, pkg.ErrImagesExceed) { return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrImagesExceed.Error()) } + if errors.Is(err, pkg.ErrUserTaskNotCompleted) { + return helper.ErrorHandler(c, http.StatusConflict, pkg.ErrUserTaskNotCompleted.Error()) + } return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail: "+err.Error()) } var taskStep []dto.TaskSteps @@ -528,10 +531,21 @@ func (handler *UserTaskHandlerImpl) UpdateTaskStepHandler(c echo.Context) error if errors.Is(err, pkg.ErrTaskStepNotFound) { return helper.ErrorHandler(c, http.StatusNotFound, pkg.ErrTaskStepNotFound.Error()) } + if errors.Is(err, pkg.ErrStepNotInOrder) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrStepNotInOrder.Error()) + } + if errors.Is(err, pkg.ErrUserTaskAlreadyApprove) { + return helper.ErrorHandler(c, http.StatusBadRequest, pkg.ErrUserTaskAlreadyApprove.Error()) + } + if errors.Is(err, pkg.ErrUserTaskStepAlreadyCompleted) { + return helper.ErrorHandler(c, http.StatusConflict, pkg.ErrUserTaskStepAlreadyCompleted.Error()) + } return helper.ErrorHandler(c, http.StatusInternalServerError, "internal server error, detail: "+err.Error()) } var taskStep []dto.TaskSteps + var userSteps []dto.DataUserSteps + for _, step := range userTask.TaskChallenge.TaskSteps { taskStep = append(taskStep, dto.TaskSteps{ Id: step.ID, @@ -540,6 +554,14 @@ func (handler *UserTaskHandlerImpl) UpdateTaskStepHandler(c echo.Context) error }) } + for _, step := range userTask.UserTaskSteps { + userSteps = append(userSteps, dto.DataUserSteps{ + Id: step.ID, + UserTaskChallengeID: step.UserTaskChallengeID, + TaskStepID: step.TaskStepID, + Completed: step.Completed, + }) + } data := dto.TaskChallengeResponseCreate{ Id: userTask.TaskChallenge.ID, Title: userTask.TaskChallenge.Title, @@ -550,16 +572,15 @@ func (handler *UserTaskHandlerImpl) UpdateTaskStepHandler(c echo.Context) error Point: userTask.TaskChallenge.Point, StatusTask: userTask.TaskChallenge.Status, TaskSteps: taskStep, + UserSteps: userSteps, } - dataUsertask := dto.UserTaskResponseCreate{ Id: userTask.ID, - TaskChalenge: data, StatusProgress: userTask.StatusProgress, + TaskChalenge: data, } - - responseData := helper.ResponseData(http.StatusOK, "success", dataUsertask) - return c.JSON(http.StatusOK, responseData) + responseData := helper.ResponseData(http.StatusCreated, "success", dataUsertask) + return c.JSON(http.StatusCreated, responseData) } func (handler *UserTaskHandlerImpl) GetUserTaskByUserTaskIdHandler(c echo.Context) error { diff --git a/internal/task/user_task/repository/user_task_repository.go b/internal/task/user_task/repository/user_task_repository.go index 311920c..bdc93d6 100644 --- a/internal/task/user_task/repository/user_task_repository.go +++ b/internal/task/user_task/repository/user_task_repository.go @@ -22,4 +22,6 @@ type UserTaskRepository interface { FindTaskStep(stepId int, taskId string) (*task.TaskStep, error) FindUserTaskStep(userTaskChallengeID string, taskStepID int) (*user_task.UserTaskStep, error) UpdateUserTaskStep(userTaskStep *user_task.UserTaskStep) error + FindUserSteps(userTaskChallengeID string) ([]user_task.UserTaskStep, error) + FindCompletedUserSteps(userTaskChallengeID string) ([]user_task.UserTaskStep, error) } diff --git a/internal/task/user_task/repository/user_task_repository_impl.go b/internal/task/user_task/repository/user_task_repository_impl.go index 90e4747..2cdb278 100644 --- a/internal/task/user_task/repository/user_task_repository_impl.go +++ b/internal/task/user_task/repository/user_task_repository_impl.go @@ -288,3 +288,15 @@ func (repository *UserTaskRepositoryImpl) FindUserTaskStep(userTaskChallengeID s err := repository.DB.GetDB().Where("user_task_challenge_id = ? AND task_step_id = ?", userTaskChallengeID, taskStepID).First(&userTaskStep).Error return &userTaskStep, err } + +func (repository *UserTaskRepositoryImpl) FindUserSteps(userTaskChallengeID string) ([]user_task.UserTaskStep, error) { + var userTaskStep []user_task.UserTaskStep + err := repository.DB.GetDB().Where("user_task_challenge_id = ?", userTaskChallengeID).Find(&userTaskStep).Error + return userTaskStep, err +} + +func (repository *UserTaskRepositoryImpl) FindCompletedUserSteps(userTaskChallengeID string) ([]user_task.UserTaskStep, error) { + var userTaskSteps []user_task.UserTaskStep + err := repository.DB.GetDB().Where("user_task_challenge_id = ? AND completed = ?", userTaskChallengeID, true).Order("task_step_id asc").Find(&userTaskSteps).Error + return userTaskSteps, err +} diff --git a/internal/task/user_task/usecase/user_task_usecase_impl.go b/internal/task/user_task/usecase/user_task_usecase_impl.go index 7e571f8..f88b0c1 100644 --- a/internal/task/user_task/usecase/user_task_usecase_impl.go +++ b/internal/task/user_task/usecase/user_task_usecase_impl.go @@ -92,6 +92,10 @@ func (usecase *UserTaskUsecaseImpl) UploadImageTaskUsecase(request *dto.UploadIm if errFindTask != nil { return nil, pkg.ErrTaskNotFound } + + if !findTask.Status { + return nil, pkg.ErrTaskCannotBeFollowed + } countImage := len(fileImage) countTaskSteps := len(findTask.TaskSteps) * 3 if countImage > countTaskSteps { @@ -102,6 +106,18 @@ func (usecase *UserTaskUsecaseImpl) UploadImageTaskUsecase(request *dto.UploadIm return nil, pkg.ErrUserTaskDone } + findUserSteps, errFindUserSteps := usecase.ManageTaskRepository.FindUserSteps(userTaskId) + + if errFindUserSteps != nil { + return nil, errFindUserSteps + } + + for _, userStep := range findUserSteps { + if !userStep.Completed { + return nil, pkg.ErrUserTaskNotCompleted + } + } + validImages, errImages := helper.ImagesValidation(fileImage) if errImages != nil { return nil, errImages @@ -242,16 +258,20 @@ func (usecase *UserTaskUsecaseImpl) GetHistoryPointByUserIdUsecase(userId string } func (usecase *UserTaskUsecaseImpl) UpdateTaskStepUsecase(request *dto.UpdateTaskStepRequest, userId string) (*user_task.UserTaskChallenge, error) { - userTask, errUserTask := usecase.ManageTaskRepository.FindUserTask(userId, request.UserTask) + userTask, errUserTask := usecase.ManageTaskRepository.FindUserTask(userId, request.UserTaskId) if errUserTask != nil { return nil, pkg.ErrUserTaskNotFound } - if userTask.StatusAccept != "in_progress" { + if userTask.StatusProgress != "in_progress" { return nil, pkg.ErrUserTaskDone } - taskStep, errStep := usecase.ManageTaskRepository.FindTaskStep(request.StepId, userTask.TaskChallengeId) + if userTask.StatusAccept != "need_rivew" { + return nil, pkg.ErrUserTaskAlreadyApprove + } + + taskStep, errStep := usecase.ManageTaskRepository.FindTaskStep(request.TaskStepId, userTask.TaskChallengeId) if errStep != nil { if errStep == gorm.ErrRecordNotFound { return nil, pkg.ErrTaskStepNotFound @@ -264,14 +284,37 @@ func (usecase *UserTaskUsecaseImpl) UpdateTaskStepUsecase(request *dto.UpdateTas return nil, pkg.ErrUserTaskStepNotFound } - userTaskStep.Completed = true + // Check if the step is already completed + if userTaskStep.Completed { + return nil, pkg.ErrUserTaskStepAlreadyCompleted + } + + // Find completed user task steps + completedSteps, err := usecase.ManageTaskRepository.FindCompletedUserSteps(userTask.ID) + if err != nil { + return nil, err + } + // Check if the step to be updated is the next in sequence + if len(completedSteps) > 0 { + nextStepID := completedSteps[len(completedSteps)-1].TaskStepID + 1 + if request.TaskStepId != nextStepID { + return nil, pkg.ErrStepNotInOrder + } + } else { + // If no steps are completed, only allow the first step to be completed + firstStep := userTask.TaskChallenge.TaskSteps[0] + if request.TaskStepId != firstStep.ID { + return nil, pkg.ErrStepNotInOrder + } + } + + userTaskStep.Completed = true if err := usecase.ManageTaskRepository.UpdateUserTaskStep(userTaskStep); err != nil { return nil, err } - // Re-fetch updated user task with its relations - updatedUserTask, err := usecase.ManageTaskRepository.FindUserTask(userId, request.UserTask) + updatedUserTask, err := usecase.ManageTaskRepository.FindUserTask(userId, request.UserTaskId) if err != nil { return nil, err } diff --git a/pkg/errors.go b/pkg/errors.go index 973383b..b86c487 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -37,19 +37,22 @@ var ( ErrThumbnailMaximum = errors.New("thumbnail must be one image") // User Task - ErrImageTaskNull = errors.New("image task cannot be null") - ErrUserTaskExist = errors.New("user task already exist") - ErrUserTaskNotFound = errors.New("user task not found") - ErrUserTaskDone = errors.New("user task already done") - ErrTaskCannotBeFollowed = errors.New("task cannot be followed") - ErrUserNoHasTask = errors.New("user has no task") - ErrImagesExceed = errors.New("image exceed limit") - ErrUserTaskNotReject = errors.New("user task not reject") - ErrUserTaskAlreadyReject = errors.New("user task already reject") - ErrUserTaskAlreadyApprove = errors.New("user task already approve") - ErrTaskStepNotFound = errors.New("task step not found") - ErrTaskStepDone = errors.New("task step already done") - ErrUserTaskStepNotFound = errors.New("user task step not found") + ErrImageTaskNull = errors.New("image task cannot be null") + ErrUserTaskExist = errors.New("user task already exist") + ErrUserTaskNotFound = errors.New("user task not found") + ErrUserTaskDone = errors.New("user task already done") + ErrTaskCannotBeFollowed = errors.New("task cannot be followed") + ErrUserNoHasTask = errors.New("user has no task") + ErrImagesExceed = errors.New("image exceed limit") + ErrUserTaskNotReject = errors.New("user task not reject") + ErrUserTaskAlreadyReject = errors.New("user task already reject") + ErrUserTaskAlreadyApprove = errors.New("user task already approve") + ErrTaskStepNotFound = errors.New("task step not found") + ErrTaskStepDone = errors.New("task step already done") + ErrUserTaskStepNotFound = errors.New("user task step not found") + ErrUserTaskNotCompleted = errors.New("task step not completed") + ErrStepNotInOrder = errors.New("task step must be completed in order") + ErrUserTaskStepAlreadyCompleted = errors.New("user task step already completed") // manage achievement ErrAchievementLevelAlreadyExist = errors.New("archievement level already exist") From da97fa25f5db43db849ffe695d777e93f31376aa Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Fri, 14 Jun 2024 03:11:19 +0700 Subject: [PATCH 71/72] chore(jwt): add token expired handling and update jwt expired to 30day --- internal/helper/jwt.go | 7 ++++--- internal/middleware/middleware.go | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/helper/jwt.go b/internal/helper/jwt.go index b020c4d..8310e57 100644 --- a/internal/helper/jwt.go +++ b/internal/helper/jwt.go @@ -15,9 +15,10 @@ type JwtCustomClaims struct { func GenerateTokenJWT(userID string, role string) (string, error) { claims := &JwtCustomClaims{ - userID, role, - jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 72)), + UserID: userID, + Role: role, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 30)), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index 9e25018..8704a35 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -1,6 +1,7 @@ package middleware import ( + "errors" "net/http" "strings" @@ -30,6 +31,9 @@ func RoleBasedMiddleware(allowedRoles ...string) echo.MiddlewareFunc { }) if err != nil { + if errors.Is(err, jwt.ErrTokenExpired) { + return helper.ErrorHandler(c, http.StatusUnauthorized, "token has expired") + } return helper.ErrorHandler(c, http.StatusUnauthorized, "invalid token signature") } From e54e86f5d0231d3026a7c4513511e017f0f1841f Mon Sep 17 00:00:00 2001 From: sawalreverr Date: Fri, 14 Jun 2024 03:21:26 +0700 Subject: [PATCH 72/72] chore(user): update default badge user --- internal/user/entity.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/user/entity.go b/internal/user/entity.go index 7a0c040..120506c 100644 --- a/internal/user/entity.go +++ b/internal/user/entity.go @@ -21,7 +21,7 @@ type User struct { PictureURL string `json:"picture_url"` OTP uint `json:"otp"` IsVerified bool `json:"is_verified" gorm:"default:false"` - Badge string `json:"badge" gorm:"default:'https://res.cloudinary.com/dymhvau8n/image/upload/v1717758679/achievement_badge/cq2n246e6twuksnia62t.png'"` + Badge string `json:"badge" gorm:"default:'https://res.cloudinary.com/dymhvau8n/image/upload/v1718189121/user_badge/htaemsjtlhfof7ww01ss.png'"` CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"`