From b6f5984ebadda1dc5523bcb3d5e2973f7ddb9f5e Mon Sep 17 00:00:00 2001 From: pahmiudahgede Date: Fri, 24 Jan 2025 20:39:12 +0700 Subject: [PATCH] refact: optimize bannner domain --- dto/banner.go | 39 +------- internal/api/routes.go | 14 ++- internal/controllers/banner.go | 166 +++++++++---------------------- internal/repositories/banner.go | 86 ++++------------ internal/services/banner.go | 170 ++++++++++++++++++++++++++------ 5 files changed, 220 insertions(+), 255 deletions(-) diff --git a/dto/banner.go b/dto/banner.go index 06ab9a8..34cd76a 100644 --- a/dto/banner.go +++ b/dto/banner.go @@ -1,7 +1,5 @@ package dto -import "github.com/go-playground/validator/v10" - type BannerResponse struct { ID string `json:"id"` BannerName string `json:"bannername"` @@ -10,39 +8,12 @@ type BannerResponse struct { UpdatedAt string `json:"updatedAt"` } -func NewBannerResponse(id, bannerName, bannerImage, createdAt, updatedAt string) BannerResponse { - return BannerResponse{ - ID: id, - BannerName: bannerName, - BannerImage: bannerImage, - CreatedAt: createdAt, - UpdatedAt: updatedAt, - } -} - -type BannerRequest struct { +type BannerCreateRequest struct { BannerName string `json:"bannername" validate:"required"` - BannerImage string `json:"bannerimage" validate:"required,url"` + BannerImage string `json:"bannerimage" validate:"required"` } -func NewBannerRequest(bannerName, bannerImage string) BannerRequest { - return BannerRequest{ - BannerName: bannerName, - BannerImage: bannerImage, - } +type BannerUpdateRequest struct { + BannerName string `json:"bannername,omitempty"` + BannerImage string `json:"bannerimage,omitempty"` } - -func (b *BannerRequest) Validate() error { - validate := validator.New() - return validate.Struct(b) -} - -type BannerUpdateDTO struct { - BannerName string `json:"bannername" validate:"required"` - BannerImage string `json:"bannerimage" validate:"required,url"` -} - -func (b *BannerUpdateDTO) Validate() error { - validate := validator.New() - return validate.Struct(b) -} \ No newline at end of file diff --git a/internal/api/routes.go b/internal/api/routes.go index 2445cce..e3f8d96 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -15,6 +15,10 @@ func AppRouter(app *fiber.App) { pointService := services.NewPointService(pointRepo) pointController := controllers.NewPointController(pointService) + bannerRepo := repositories.NewBannerRepository() + bannerService := services.NewBannerService(bannerRepo) + bannerController := controllers.NewBannerController(bannerService) + // # api group domain endpoint # api := app.Group("/apirijikid") @@ -94,11 +98,11 @@ func AppRouter(app *fiber.App) { api.Delete("/trash-category/delete-trash-detail/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.DeleteTrashDetail) // # banner # - api.Get("/banners", controllers.GetBanners) - api.Get("/banner/:id", controllers.GetBannerByID) - api.Post("/banner/create-banner", middleware.RoleRequired(utils.RoleAdministrator), controllers.CreateBanner) - api.Put("/banner/update-banner/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.UpdateBanner) - api.Delete("/banner/delete-banner/:id", middleware.RoleRequired(utils.RoleAdministrator), controllers.DeleteBanner) + api.Get("/banners", bannerController.GetAllBanners) + api.Get("/banner/:id", bannerController.GetBannerByID) + api.Post("/banner/create-banner", middleware.RoleRequired(utils.RoleAdministrator), bannerController.CreateBanner) + api.Put("/banner/update-banner/:id", middleware.RoleRequired(utils.RoleAdministrator), bannerController.UpdateBanner) + api.Delete("/banner/delete-banner/:id", middleware.RoleRequired(utils.RoleAdministrator), bannerController.DeleteBanner) // # wilayah di indonesia # api.Get("/wilayah-indonesia/provinces", controllers.GetProvinces) diff --git a/internal/controllers/banner.go b/internal/controllers/banner.go index 06a54e1..c98d2dd 100644 --- a/internal/controllers/banner.go +++ b/internal/controllers/banner.go @@ -7,190 +7,116 @@ import ( "github.com/pahmiudahgede/senggoldong/utils" ) -func GetBanners(c *fiber.Ctx) error { +type BannerController struct { + service *services.BannerService +} - banners, err := services.GetBanners() +func NewBannerController(service *services.BannerService) *BannerController { + return &BannerController{service: service} +} + +func (bc *BannerController) GetAllBanners(c *fiber.Ctx) error { + banners, err := bc.service.GetAllBanners() if err != nil { - - return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse( fiber.StatusInternalServerError, "Failed to fetch banners", - nil, )) } - - var bannerResponses []dto.BannerResponse - for _, banner := range banners { - - bannerResponses = append(bannerResponses, dto.BannerResponse{ - ID: banner.ID, - BannerName: banner.BannerName, - BannerImage: banner.BannerImage, - CreatedAt: utils.FormatDateToIndonesianFormat(banner.CreatedAt), - UpdatedAt: utils.FormatDateToIndonesianFormat(banner.UpdatedAt), - }) - } - return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( fiber.StatusOK, "Banners fetched successfully", - bannerResponses, + banners, )) } -func GetBannerByID(c *fiber.Ctx) error { +func (bc *BannerController) GetBannerByID(c *fiber.Ctx) error { id := c.Params("id") - - banner, err := services.GetBannerByID(id) + banner, err := bc.service.GetBannerByID(id) if err != nil { - - if err.Error() == "banner not found" { - return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse( - fiber.StatusNotFound, - "Banner not found", - nil, - )) - } - - return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( - fiber.StatusInternalServerError, - "Failed to fetch banner", - nil, + return c.Status(fiber.StatusNotFound).JSON(utils.ErrorResponse( + fiber.StatusNotFound, + "Banner not found", )) } - - bannerResponse := dto.BannerResponse{ - ID: banner.ID, - BannerName: banner.BannerName, - BannerImage: banner.BannerImage, - CreatedAt: utils.FormatDateToIndonesianFormat(banner.CreatedAt), - UpdatedAt: utils.FormatDateToIndonesianFormat(banner.UpdatedAt), - } - return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( fiber.StatusOK, "Banner fetched successfully", - struct { - Banner dto.BannerResponse `json:"banner"` - }{ - Banner: bannerResponse, - }, + banner, )) } -func CreateBanner(c *fiber.Ctx) error { - var bannerInput dto.BannerRequest +func (bc *BannerController) CreateBanner(c *fiber.Ctx) error { + var request dto.BannerCreateRequest - if err := c.BodyParser(&bannerInput); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( + if err := c.BodyParser(&request); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse( fiber.StatusBadRequest, - "Invalid input data", - nil, + "Invalid request body", )) } - if err := bannerInput.Validate(); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( - fiber.StatusBadRequest, - "Validation failed: "+err.Error(), - nil, - )) - } - - newBanner, err := services.CreateBanner(bannerInput.BannerName, bannerInput.BannerImage) + banner, err := bc.service.CreateBanner(&request) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse( fiber.StatusInternalServerError, - "Failed to create banner", - nil, + err.Error(), )) } - bannerResponse := dto.NewBannerResponse( - newBanner.ID, - newBanner.BannerName, - newBanner.BannerImage, - utils.FormatDateToIndonesianFormat(newBanner.CreatedAt), - utils.FormatDateToIndonesianFormat(newBanner.UpdatedAt), - ) - return c.Status(fiber.StatusCreated).JSON(utils.FormatResponse( fiber.StatusCreated, "Banner created successfully", - struct { - Banner dto.BannerResponse `json:"banner"` - }{ - Banner: bannerResponse, - }, + banner, )) } -func UpdateBanner(c *fiber.Ctx) error { +func (bc *BannerController) UpdateBanner(c *fiber.Ctx) error { id := c.Params("id") + var request dto.BannerUpdateRequest - var bannerInput dto.BannerUpdateDTO - if err := c.BodyParser(&bannerInput); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( + if err := c.BodyParser(&request); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(utils.ErrorResponse( fiber.StatusBadRequest, - "Invalid input data", - nil, + "Invalid request body", )) } - if err := bannerInput.Validate(); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(utils.FormatResponse( - fiber.StatusBadRequest, - "Validation failed: "+err.Error(), - nil, - )) - } - - updatedBanner, err := services.UpdateBanner(id, bannerInput.BannerName, bannerInput.BannerImage) + banner, err := bc.service.UpdateBanner(id, &request) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + if err.Error() == "banner not found" { + return c.Status(fiber.StatusNotFound).JSON(utils.ErrorResponse( + fiber.StatusNotFound, + "Banner not found", + )) + } + return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse( fiber.StatusInternalServerError, - "Failed to update banner", - nil, + err.Error(), )) } - bannerResponse := dto.NewBannerResponse( - updatedBanner.ID, - updatedBanner.BannerName, - updatedBanner.BannerImage, - utils.FormatDateToIndonesianFormat(updatedBanner.CreatedAt), - utils.FormatDateToIndonesianFormat(updatedBanner.UpdatedAt), - ) - return c.Status(fiber.StatusOK).JSON(utils.FormatResponse( fiber.StatusOK, "Banner updated successfully", - struct { - Banner dto.BannerResponse `json:"banner"` - }{ - Banner: bannerResponse, - }, + banner, )) } -func DeleteBanner(c *fiber.Ctx) error { +func (bc *BannerController) DeleteBanner(c *fiber.Ctx) error { id := c.Params("id") - err := services.DeleteBanner(id) + err := bc.service.DeleteBanner(id) if err != nil { - if err.Error() == "banner not found" { - return c.Status(fiber.StatusNotFound).JSON(utils.FormatResponse( + return c.Status(fiber.StatusNotFound).JSON(utils.ErrorResponse( fiber.StatusNotFound, "Banner not found", - nil, )) } - - return c.Status(fiber.StatusInternalServerError).JSON(utils.FormatResponse( + return c.Status(fiber.StatusInternalServerError).JSON(utils.ErrorResponse( fiber.StatusInternalServerError, - "Failed to delete banner", - nil, + err.Error(), )) } diff --git a/internal/repositories/banner.go b/internal/repositories/banner.go index 3a02432..857f122 100644 --- a/internal/repositories/banner.go +++ b/internal/repositories/banner.go @@ -1,90 +1,44 @@ package repositories import ( - "encoding/json" - "time" + "errors" "github.com/pahmiudahgede/senggoldong/config" "github.com/pahmiudahgede/senggoldong/domain" ) -func GetBanners() ([]domain.Banner, error) { +type BannerRepository struct{} + +func NewBannerRepository() *BannerRepository { + return &BannerRepository{} +} + +func (r *BannerRepository) GetAll() ([]domain.Banner, error) { var banners []domain.Banner - - ctx := config.Context() - cachedBanners, err := config.RedisClient.Get(ctx, "banners").Result() - if err == nil && cachedBanners != "" { - - if err := json.Unmarshal([]byte(cachedBanners), &banners); err != nil { - return nil, err - } - return banners, nil - } - - if err := config.DB.Find(&banners).Error; err != nil { - return nil, err - } - - bannersJSON, err := json.Marshal(banners) + err := config.DB.Find(&banners).Error if err != nil { - return nil, err + return nil, errors.New("failed to fetch banners from database") } - config.RedisClient.Set(ctx, "banners", bannersJSON, time.Hour).Err() - return banners, nil } -func GetBannerByID(id string) (domain.Banner, error) { +func (r *BannerRepository) GetByID(id string) (*domain.Banner, error) { var banner domain.Banner - - ctx := config.Context() - cachedBanner, err := config.RedisClient.Get(ctx, "banner:"+id).Result() - if err == nil && cachedBanner != "" { - - if err := json.Unmarshal([]byte(cachedBanner), &banner); err != nil { - return banner, err - } - return banner, nil - } - - if err := config.DB.Where("id = ?", id).First(&banner).Error; err != nil { - return banner, err - } - - bannerJSON, err := json.Marshal(banner) + err := config.DB.First(&banner, "id = ?", id).Error if err != nil { - return banner, err + return nil, errors.New("banner not found") } - config.RedisClient.Set(ctx, "banner:"+id, bannerJSON, time.Hour*24).Err() - - return banner, nil + return &banner, nil } -func CreateBanner(banner *domain.Banner) error { - if err := config.DB.Create(banner).Error; err != nil { - return err - } - - config.RedisClient.Del(config.Context(), "banners") - return nil +func (r *BannerRepository) Create(banner *domain.Banner) error { + return config.DB.Create(banner).Error } -func UpdateBanner(banner *domain.Banner) error { - if err := config.DB.Save(banner).Error; err != nil { - return err - } - - config.RedisClient.Del(config.Context(), "banners") - config.RedisClient.Del(config.Context(), "banner:"+banner.ID) - return nil +func (r *BannerRepository) Update(banner *domain.Banner) error { + return config.DB.Save(banner).Error } -func DeleteBanner(id string) error { - if err := config.DB.Where("id = ?", id).Delete(&domain.Banner{}).Error; err != nil { - return err - } - - config.RedisClient.Del(config.Context(), "banners") - config.RedisClient.Del(config.Context(), "banner:"+id) - return nil +func (r *BannerRepository) Delete(banner *domain.Banner) error { + return config.DB.Delete(banner).Error } diff --git a/internal/services/banner.go b/internal/services/banner.go index 26d1bba..c10fbb1 100644 --- a/internal/services/banner.go +++ b/internal/services/banner.go @@ -1,60 +1,170 @@ package services import ( + "encoding/json" "errors" + "time" + + "github.com/pahmiudahgede/senggoldong/config" "github.com/pahmiudahgede/senggoldong/domain" + "github.com/pahmiudahgede/senggoldong/dto" "github.com/pahmiudahgede/senggoldong/internal/repositories" + "github.com/pahmiudahgede/senggoldong/utils" ) -func GetBanners() ([]domain.Banner, error) { - return repositories.GetBanners() +type BannerService struct { + repo *repositories.BannerRepository } -func GetBannerByID(id string) (domain.Banner, error) { - banner, err := repositories.GetBannerByID(id) +func NewBannerService(repo *repositories.BannerRepository) *BannerService { + return &BannerService{repo: repo} +} + +func (s *BannerService) GetAllBanners() ([]dto.BannerResponse, error) { + ctx := config.Context() + cacheKey := "banners:all" + + cachedData, err := config.RedisClient.Get(ctx, cacheKey).Result() + if err == nil && cachedData != "" { + var cachedBanners []dto.BannerResponse + if err := json.Unmarshal([]byte(cachedData), &cachedBanners); err == nil { + return cachedBanners, nil + } + } + + banners, err := s.repo.GetAll() if err != nil { - return domain.Banner{}, errors.New("banner not found") + return nil, err } - return banner, nil + + var result []dto.BannerResponse + for _, banner := range banners { + result = append(result, dto.BannerResponse{ + ID: banner.ID, + BannerName: banner.BannerName, + BannerImage: banner.BannerImage, + CreatedAt: utils.FormatDateToIndonesianFormat(banner.CreatedAt), + UpdatedAt: utils.FormatDateToIndonesianFormat(banner.UpdatedAt), + }) + } + + cacheData, _ := json.Marshal(result) + config.RedisClient.Set(ctx, cacheKey, cacheData, time.Minute*5) + + return result, nil } -func CreateBanner(bannerName, bannerImage string) (domain.Banner, error) { - newBanner := domain.Banner{ - BannerName: bannerName, - BannerImage: bannerImage, +func (s *BannerService) GetBannerByID(id string) (*dto.BannerResponse, error) { + ctx := config.Context() + cacheKey := "banners:" + id + + cachedData, err := config.RedisClient.Get(ctx, cacheKey).Result() + if err == nil && cachedData != "" { + var cachedBanner dto.BannerResponse + if err := json.Unmarshal([]byte(cachedData), &cachedBanner); err == nil { + return &cachedBanner, nil + } } - if err := repositories.CreateBanner(&newBanner); err != nil { - return domain.Banner{}, err - } - - return newBanner, nil -} - -func UpdateBanner(id, bannerName, bannerImage string) (domain.Banner, error) { - banner, err := repositories.GetBannerByID(id) + banner, err := s.repo.GetByID(id) if err != nil { - return domain.Banner{}, err + return nil, err } - banner.BannerName = bannerName - banner.BannerImage = bannerImage - - if err := repositories.UpdateBanner(&banner); err != nil { - return domain.Banner{}, err + result := &dto.BannerResponse{ + ID: banner.ID, + BannerName: banner.BannerName, + BannerImage: banner.BannerImage, + CreatedAt: utils.FormatDateToIndonesianFormat(banner.CreatedAt), + UpdatedAt: utils.FormatDateToIndonesianFormat(banner.UpdatedAt), } - return banner, nil + cacheData, _ := json.Marshal(result) + config.RedisClient.Set(ctx, cacheKey, cacheData, time.Minute*5) + + return result, nil } -func DeleteBanner(id string) error { - _, err := repositories.GetBannerByID(id) +func (s *BannerService) CreateBanner(request *dto.BannerCreateRequest) (*dto.BannerResponse, error) { + + if request.BannerName == "" || request.BannerImage == "" { + return nil, errors.New("invalid input data") + } + + newBanner := &domain.Banner{ + BannerName: request.BannerName, + BannerImage: request.BannerImage, + } + + err := s.repo.Create(newBanner) + if err != nil { + return nil, errors.New("failed to create banner") + } + + ctx := config.Context() + config.RedisClient.Del(ctx, "banners:all") + + response := &dto.BannerResponse{ + ID: newBanner.ID, + BannerName: newBanner.BannerName, + BannerImage: newBanner.BannerImage, + CreatedAt: utils.FormatDateToIndonesianFormat(newBanner.CreatedAt), + UpdatedAt: utils.FormatDateToIndonesianFormat(newBanner.UpdatedAt), + } + + return response, nil +} + +func (s *BannerService) UpdateBanner(id string, request *dto.BannerUpdateRequest) (*dto.BannerResponse, error) { + + banner, err := s.repo.GetByID(id) + if err != nil { + return nil, errors.New("banner not found") + } + + if request.BannerName != "" { + banner.BannerName = request.BannerName + } + if request.BannerImage != "" { + banner.BannerImage = request.BannerImage + } + banner.UpdatedAt = time.Now() + + err = s.repo.Update(banner) + if err != nil { + return nil, errors.New("failed to update banner") + } + + ctx := config.Context() + config.RedisClient.Del(ctx, "banners:all") + config.RedisClient.Del(ctx, "banners:"+id) + + response := &dto.BannerResponse{ + ID: banner.ID, + BannerName: banner.BannerName, + BannerImage: banner.BannerImage, + CreatedAt: utils.FormatDateToIndonesianFormat(banner.CreatedAt), + UpdatedAt: utils.FormatDateToIndonesianFormat(banner.UpdatedAt), + } + + return response, nil +} + +func (s *BannerService) DeleteBanner(id string) error { + + banner, err := s.repo.GetByID(id) if err != nil { return errors.New("banner not found") } - if err := repositories.DeleteBanner(id); err != nil { - return err + err = s.repo.Delete(banner) + if err != nil { + return errors.New("failed to delete banner") } + + ctx := config.Context() + config.RedisClient.Del(ctx, "banners:all") + config.RedisClient.Del(ctx, "banners:"+id) + return nil }