feat: add feature banner domain

This commit is contained in:
pahmiudahgede 2025-02-16 16:55:36 +07:00
parent e748c6f492
commit 8616d725a4
10 changed files with 594 additions and 2 deletions

3
.gitignore vendored
View File

@ -28,4 +28,5 @@ go.work.sum
# Ignore avatar images
/public/uploads/avatars/
/public/uploads/articles/
/public/uploads/articles/
/public/uploads/banners/

View File

@ -44,6 +44,7 @@ func ConnectDatabase() {
&model.UserPin{},
&model.Address{},
&model.Article{},
&model.Banner{},
// ==main feature==
)
if err != nil {

29
dto/banner_dto.go Normal file
View File

@ -0,0 +1,29 @@
package dto
import "strings"
type ResponseBannerDTO struct {
ID string `json:"id"`
BannerName string `json:"bannername"`
BannerImage string `json:"bannerimage"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestBannerDTO struct {
BannerName string `json:"bannername"`
BannerImage string `json:"bannerimage"`
}
func (r *RequestBannerDTO) ValidateBannerInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.BannerName) == "" {
errors["bannername"] = append(errors["bannername"], "nama banner harus diisi")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -41,7 +41,7 @@ func (h *ArticleHandler) CreateArticle(c *fiber.Ctx) error {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.SuccessResponse(c, articleResponse, "Article created successfully")
return utils.CreateResponse(c, articleResponse, "Article created successfully")
}
func (h *ArticleHandler) GetAllArticles(c *fiber.Ctx) error {

View File

@ -0,0 +1,108 @@
package handler
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/utils"
)
type BannerHandler struct {
BannerService services.BannerService
}
func NewBannerHandler(bannerService services.BannerService) *BannerHandler {
return &BannerHandler{BannerService: bannerService}
}
func (h *BannerHandler) CreateBanner(c *fiber.Ctx) error {
var request dto.RequestBannerDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := request.ValidateBannerInput()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
bannerImage, err := c.FormFile("bannerimage")
if err != nil {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Banner image is required")
}
bannerResponse, err := h.BannerService.CreateBanner(request, bannerImage)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.CreateResponse(c, bannerResponse, "Banner created successfully")
}
func (h *BannerHandler) GetAllBanners(c *fiber.Ctx) error {
banners, err := h.BannerService.GetAllBanners()
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch banners")
}
return utils.NonPaginatedResponse(c, banners, len(banners), "Banners fetched successfully")
}
func (h *BannerHandler) GetBannerByID(c *fiber.Ctx) error {
id := c.Params("banner_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Banner ID is required")
}
banner, err := h.BannerService.GetBannerByID(id)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, "Failed to fetch banner")
}
return utils.SuccessResponse(c, banner, "Banner fetched successfully")
}
func (h *BannerHandler) UpdateBanner(c *fiber.Ctx) error {
id := c.Params("banner_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Banner ID is required")
}
var request dto.RequestBannerDTO
if err := c.BodyParser(&request); err != nil {
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
}
errors, valid := request.ValidateBannerInput()
if !valid {
return utils.ValidationErrorResponse(c, errors)
}
bannerImage, err := c.FormFile("bannerimage")
if err != nil && err.Error() != "no such file" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Banner image is required")
}
bannerResponse, err := h.BannerService.UpdateBanner(id, request, bannerImage)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.SuccessResponse(c, bannerResponse, "Banner updated successfully")
}
func (h *BannerHandler) DeleteBanner(c *fiber.Ctx) error {
id := c.Params("banner_id")
if id == "" {
return utils.GenericResponse(c, fiber.StatusBadRequest, "Banner ID is required")
}
err := h.BannerService.DeleteBanner(id)
if err != nil {
return utils.GenericResponse(c, fiber.StatusInternalServerError, err.Error())
}
return utils.GenericResponse(c, fiber.StatusOK, "Banner deleted successfully")
}

View File

@ -0,0 +1,69 @@
package repositories
import (
"fmt"
"github.com/pahmiudahgede/senggoldong/model"
"gorm.io/gorm"
)
type BannerRepository interface {
CreateBanner(banner *model.Banner) error
FindBannerByID(id string) (*model.Banner, error)
FindAllBanners() ([]model.Banner, error)
UpdateBanner(id string, banner *model.Banner) error
DeleteBanner(id string) error
}
type bannerRepository struct {
DB *gorm.DB
}
func NewBannerRepository(db *gorm.DB) BannerRepository {
return &bannerRepository{DB: db}
}
func (r *bannerRepository) CreateBanner(banner *model.Banner) error {
if err := r.DB.Create(banner).Error; err != nil {
return fmt.Errorf("failed to create banner: %v", err)
}
return nil
}
func (r *bannerRepository) FindBannerByID(id string) (*model.Banner, error) {
var banner model.Banner
err := r.DB.Where("id = ?", id).First(&banner).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("banner with ID %s not found", id)
}
return nil, fmt.Errorf("failed to fetch banner by ID: %v", err)
}
return &banner, nil
}
func (r *bannerRepository) FindAllBanners() ([]model.Banner, error) {
var banners []model.Banner
err := r.DB.Find(&banners).Error
if err != nil {
return nil, fmt.Errorf("failed to fetch banners: %v", err)
}
return banners, nil
}
func (r *bannerRepository) UpdateBanner(id string, banner *model.Banner) error {
err := r.DB.Model(&model.Banner{}).Where("id = ?", id).Updates(banner).Error
if err != nil {
return fmt.Errorf("failed to update banner: %v", err)
}
return nil
}
func (r *bannerRepository) DeleteBanner(id string) error {
result := r.DB.Delete(&model.Banner{}, "id = ?", id)
if result.Error != nil {
return fmt.Errorf("failed to delete banner: %v", result.Error)
}
return nil
}

View File

@ -0,0 +1,347 @@
package services
import (
"fmt"
"mime/multipart"
"os"
"path/filepath"
"time"
"github.com/google/uuid"
"github.com/pahmiudahgede/senggoldong/dto"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/model"
"github.com/pahmiudahgede/senggoldong/utils"
)
type BannerService interface {
CreateBanner(request dto.RequestBannerDTO, bannerImage *multipart.FileHeader) (*dto.ResponseBannerDTO, error)
GetAllBanners() ([]dto.ResponseBannerDTO, error)
GetBannerByID(id string) (*dto.ResponseBannerDTO, error)
UpdateBanner(id string, request dto.RequestBannerDTO, bannerImage *multipart.FileHeader) (*dto.ResponseBannerDTO, error)
DeleteBanner(id string) error
}
type bannerService struct {
BannerRepo repositories.BannerRepository
}
func NewBannerService(bannerRepo repositories.BannerRepository) BannerService {
return &bannerService{BannerRepo: bannerRepo}
}
func (s *bannerService) saveBannerImage(bannerImage *multipart.FileHeader) (string, error) {
bannerImageDir := "./public/uploads/banners"
if _, err := os.Stat(bannerImageDir); os.IsNotExist(err) {
if err := os.MkdirAll(bannerImageDir, os.ModePerm); err != nil {
return "", fmt.Errorf("failed to create directory for banner image: %v", err)
}
}
allowedExtensions := map[string]bool{".jpg": true, ".jpeg": true, ".png": true}
extension := filepath.Ext(bannerImage.Filename)
if !allowedExtensions[extension] {
return "", fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
}
bannerImageFileName := fmt.Sprintf("%s_banner%s", uuid.New().String(), extension)
bannerImagePath := filepath.Join(bannerImageDir, bannerImageFileName)
src, err := bannerImage.Open()
if err != nil {
return "", fmt.Errorf("failed to open uploaded file: %v", err)
}
defer src.Close()
dst, err := os.Create(bannerImagePath)
if err != nil {
return "", fmt.Errorf("failed to create banner image file: %v", err)
}
defer dst.Close()
if _, err := dst.ReadFrom(src); err != nil {
return "", fmt.Errorf("failed to save banner image: %v", err)
}
return bannerImagePath, nil
}
func (s *bannerService) CreateBanner(request dto.RequestBannerDTO, bannerImage *multipart.FileHeader) (*dto.ResponseBannerDTO, error) {
errors, valid := request.ValidateBannerInput()
if !valid {
return nil, fmt.Errorf("validation error: %v", errors)
}
bannerImagePath, err := s.saveBannerImage(bannerImage)
if err != nil {
return nil, fmt.Errorf("failed to save banner image: %v", err)
}
banner := model.Banner{
BannerName: request.BannerName,
BannerImage: bannerImagePath,
}
if err := s.BannerRepo.CreateBanner(&banner); err != nil {
return nil, fmt.Errorf("failed to create banner: %v", err)
}
createdAt, _ := utils.FormatDateToIndonesianFormat(banner.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(banner.UpdatedAt)
bannerResponseDTO := &dto.ResponseBannerDTO{
ID: banner.ID,
BannerName: banner.BannerName,
BannerImage: banner.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
articlesCacheKey := "banners:all"
err = utils.DeleteData(articlesCacheKey)
if err != nil {
fmt.Printf("Error deleting cache for all banners: %v\n", err)
}
cacheKey := fmt.Sprintf("banner:%s", banner.ID)
cacheData := map[string]interface{}{
"data": bannerResponseDTO,
}
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching banner: %v\n", err)
}
banners, err := s.BannerRepo.FindAllBanners()
if err == nil {
var bannersDTO []dto.ResponseBannerDTO
for _, b := range banners {
createdAt, _ := utils.FormatDateToIndonesianFormat(b.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(b.UpdatedAt)
bannersDTO = append(bannersDTO, dto.ResponseBannerDTO{
ID: b.ID,
BannerName: b.BannerName,
BannerImage: b.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
})
}
cacheData = map[string]interface{}{
"data": bannersDTO,
}
if err := utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching updated banners to Redis: %v\n", err)
}
} else {
fmt.Printf("Error fetching all banners: %v\n", err)
}
return bannerResponseDTO, nil
}
func (s *bannerService) GetAllBanners() ([]dto.ResponseBannerDTO, error) {
var banners []dto.ResponseBannerDTO
cacheKey := "banners:all"
cachedData, err := utils.GetJSONData(cacheKey)
if err == nil && cachedData != nil {
if data, ok := cachedData["data"].([]interface{}); ok {
for _, item := range data {
if bannerData, ok := item.(map[string]interface{}); ok {
banners = append(banners, dto.ResponseBannerDTO{
ID: bannerData["id"].(string),
BannerName: bannerData["bannername"].(string),
BannerImage: bannerData["bannerimage"].(string),
CreatedAt: bannerData["createdAt"].(string),
UpdatedAt: bannerData["updatedAt"].(string),
})
}
}
return banners, nil
}
}
records, err := s.BannerRepo.FindAllBanners()
if err != nil {
return nil, fmt.Errorf("failed to fetch banners: %v", err)
}
for _, record := range records {
createdAt, _ := utils.FormatDateToIndonesianFormat(record.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(record.UpdatedAt)
banners = append(banners, dto.ResponseBannerDTO{
ID: record.ID,
BannerName: record.BannerName,
BannerImage: record.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
})
}
cacheData := map[string]interface{}{
"data": banners,
}
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching banners: %v\n", err)
}
return banners, nil
}
func (s *bannerService) GetBannerByID(id string) (*dto.ResponseBannerDTO, error) {
cacheKey := fmt.Sprintf("banner:%s", id)
cachedData, err := utils.GetJSONData(cacheKey)
if err == nil && cachedData != nil {
if data, ok := cachedData["data"].(map[string]interface{}); ok {
return &dto.ResponseBannerDTO{
ID: data["id"].(string),
BannerName: data["bannername"].(string),
BannerImage: data["bannerimage"].(string),
CreatedAt: data["createdAt"].(string),
UpdatedAt: data["updatedAt"].(string),
}, nil
}
}
banner, err := s.BannerRepo.FindBannerByID(id)
if err != nil {
return nil, fmt.Errorf("failed to fetch banner by ID: %v", err)
}
createdAt, _ := utils.FormatDateToIndonesianFormat(banner.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(banner.UpdatedAt)
bannerResponseDTO := &dto.ResponseBannerDTO{
ID: banner.ID,
BannerName: banner.BannerName,
BannerImage: banner.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
cacheData := map[string]interface{}{
"data": bannerResponseDTO,
}
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching banner: %v\n", err)
}
return bannerResponseDTO, nil
}
func (s *bannerService) UpdateBanner(id string, request dto.RequestBannerDTO, bannerImage *multipart.FileHeader) (*dto.ResponseBannerDTO, error) {
// Cari banner yang ingin diupdate
banner, err := s.BannerRepo.FindBannerByID(id)
if err != nil {
return nil, fmt.Errorf("banner not found: %v", err)
}
// Update data banner
banner.BannerName = request.BannerName
if bannerImage != nil {
// Hapus file lama jika ada gambar baru yang diupload
bannerImagePath, err := s.saveBannerImage(bannerImage)
if err != nil {
return nil, fmt.Errorf("failed to save banner image: %v", err)
}
banner.BannerImage = bannerImagePath
}
// Simpan perubahan ke database
if err := s.BannerRepo.UpdateBanner(id, banner); err != nil {
return nil, fmt.Errorf("failed to update banner: %v", err)
}
// Format tanggal
createdAt, _ := utils.FormatDateToIndonesianFormat(banner.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(banner.UpdatedAt)
// Membuat Response DTO
bannerResponseDTO := &dto.ResponseBannerDTO{
ID: banner.ID,
BannerName: banner.BannerName,
BannerImage: banner.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
// Menghapus cache untuk banner yang lama
cacheKey := fmt.Sprintf("banner:%s", id)
err = utils.DeleteData(cacheKey)
if err != nil {
fmt.Printf("Error deleting cache for banner: %v\n", err)
}
// Cache banner yang terbaru
cacheData := map[string]interface{}{
"data": bannerResponseDTO,
}
if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching updated banner: %v\n", err)
}
// Menghapus dan memperbarui cache untuk seluruh banner
articlesCacheKey := "banners:all"
err = utils.DeleteData(articlesCacheKey)
if err != nil {
fmt.Printf("Error deleting cache for all banners: %v\n", err)
}
// Cache seluruh daftar banner yang terbaru
banners, err := s.BannerRepo.FindAllBanners()
if err == nil {
var bannersDTO []dto.ResponseBannerDTO
for _, b := range banners {
createdAt, _ := utils.FormatDateToIndonesianFormat(b.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(b.UpdatedAt)
bannersDTO = append(bannersDTO, dto.ResponseBannerDTO{
ID: b.ID,
BannerName: b.BannerName,
BannerImage: b.BannerImage,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
})
}
cacheData = map[string]interface{}{
"data": bannersDTO,
}
if err := utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24); err != nil {
fmt.Printf("Error caching updated banners to Redis: %v\n", err)
}
} else {
fmt.Printf("Error fetching all banners: %v\n", err)
}
return bannerResponseDTO, nil
}
// DeleteBanner - Menghapus banner dan memperbarui cache
func (s *bannerService) DeleteBanner(id string) error {
// Hapus banner dari database
if err := s.BannerRepo.DeleteBanner(id); err != nil {
return fmt.Errorf("failed to delete banner: %v", err)
}
// Menghapus cache untuk banner yang dihapus
cacheKey := fmt.Sprintf("banner:%s", id)
err := utils.DeleteData(cacheKey)
if err != nil {
fmt.Printf("Error deleting cache for banner: %v\n", err)
}
// Menghapus cache untuk seluruh banner
articlesCacheKey := "banners:all"
err = utils.DeleteData(articlesCacheKey)
if err != nil {
fmt.Printf("Error deleting cache for all banners: %v\n", err)
}
return nil
}

11
model/banner_model.go Normal file
View File

@ -0,0 +1,11 @@
package model
import "time"
type Banner struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"`
BannerName string `gorm:"not null" json:"bannername"`
BannerImage string `gorm:"not null" json:"bannerimage"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
}

View File

@ -0,0 +1,25 @@
package presentation
import (
"github.com/gofiber/fiber/v2"
"github.com/pahmiudahgede/senggoldong/config"
"github.com/pahmiudahgede/senggoldong/internal/handler"
"github.com/pahmiudahgede/senggoldong/internal/repositories"
"github.com/pahmiudahgede/senggoldong/internal/services"
"github.com/pahmiudahgede/senggoldong/middleware"
"github.com/pahmiudahgede/senggoldong/utils"
)
func BannerRouter(api fiber.Router) {
bannerRepo := repositories.NewBannerRepository(config.DB)
bannerService := services.NewBannerService(bannerRepo)
BannerHandler := handler.NewBannerHandler(bannerService)
bannerAPI := api.Group("/banner-rijik")
bannerAPI.Post("/create-banner", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), BannerHandler.CreateBanner)
bannerAPI.Get("/getall-banner", BannerHandler.GetAllBanners)
bannerAPI.Get("/get-banner/:banner_id", BannerHandler.GetBannerByID)
bannerAPI.Put("/update-banner/:banner_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), BannerHandler.UpdateBanner)
bannerAPI.Delete("/delete-banner/:banner_id", middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RoleAdministrator), BannerHandler.DeleteBanner)
}

View File

@ -17,4 +17,5 @@ func SetupRoutes(app *fiber.App) {
presentation.WilayahRouter(api)
presentation.AddressRouter(api)
presentation.ArticleRouter(api)
presentation.BannerRouter(api)
}