narasiahli-be/app/module/ebooks/service/ebooks.service.go

366 lines
9.1 KiB
Go

package service
import (
"context"
"errors"
"fmt"
"io"
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/ebooks/mapper"
"narasi-ahli-be/app/module/ebooks/repository"
"narasi-ahli-be/app/module/ebooks/request"
"narasi-ahli-be/app/module/ebooks/response"
usersRepository "narasi-ahli-be/app/module/users/repository"
config "narasi-ahli-be/config/config"
minioStorage "narasi-ahli-be/config/config"
"narasi-ahli-be/utils/paginator"
utilSvc "narasi-ahli-be/utils/service"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/minio/minio-go/v7"
"github.com/rs/zerolog"
)
// EbooksService
type ebooksService struct {
Repo repository.EbooksRepository
WishlistRepo repository.EbookWishlistsRepository
PurchaseRepo repository.EbookPurchasesRepository
Log zerolog.Logger
Cfg *config.Config
UsersRepo usersRepository.UsersRepository
MinioStorage *minioStorage.MinioStorage
}
// EbooksService define interface of IEbooksService
type EbooksService interface {
All(req request.EbooksQueryRequest) (ebooks []*response.EbooksResponse, paging paginator.Pagination, err error)
Show(id uint) (ebook *response.EbooksResponse, err error)
ShowBySlug(slug string) (ebook *response.EbooksResponse, err error)
Save(req request.EbooksCreateRequest, authToken string) (ebook *entity.Ebooks, err error)
SavePdfFile(c *fiber.Ctx, ebookId uint) (err error)
SaveThumbnail(c *fiber.Ctx, ebookId uint) (err error)
Update(id uint, req request.EbooksUpdateRequest) (err error)
Delete(id uint) error
SummaryStats(authToken string) (summaryStats *response.EbookSummaryStats, err error)
DownloadPdf(c *fiber.Ctx, ebookId uint) error
}
// NewEbooksService init EbooksService
func NewEbooksService(
repo repository.EbooksRepository,
wishlistRepo repository.EbookWishlistsRepository,
purchaseRepo repository.EbookPurchasesRepository,
log zerolog.Logger,
cfg *config.Config,
usersRepo usersRepository.UsersRepository,
minioStorage *minioStorage.MinioStorage) EbooksService {
return &ebooksService{
Repo: repo,
WishlistRepo: wishlistRepo,
PurchaseRepo: purchaseRepo,
Log: log,
UsersRepo: usersRepo,
MinioStorage: minioStorage,
Cfg: cfg,
}
}
// All implement interface of EbooksService
func (_i *ebooksService) All(req request.EbooksQueryRequest) (ebooks []*response.EbooksResponse, paging paginator.Pagination, err error) {
ebooksData, paging, err := _i.Repo.GetAll(req)
if err != nil {
return nil, paging, err
}
ebooks = mapper.ToEbooksResponseList(ebooksData)
return ebooks, paging, nil
}
// Show implement interface of EbooksService
func (_i *ebooksService) Show(id uint) (ebook *response.EbooksResponse, err error) {
ebookData, err := _i.Repo.FindOne(id)
if err != nil {
return nil, err
}
ebook = mapper.ToEbooksResponse(ebookData)
return ebook, nil
}
// ShowBySlug implement interface of EbooksService
func (_i *ebooksService) ShowBySlug(slug string) (ebook *response.EbooksResponse, err error) {
ebookData, err := _i.Repo.FindBySlug(slug)
if err != nil {
return nil, err
}
ebook = mapper.ToEbooksResponse(ebookData)
return ebook, nil
}
// Save implement interface of EbooksService
func (_i *ebooksService) Save(req request.EbooksCreateRequest, authToken string) (ebook *entity.Ebooks, err error) {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user == nil {
return nil, errors.New("user not found")
}
ebookEntity := req.ToEntity()
ebookEntity.AuthorId = user.ID
ebookEntity.CreatedById = &user.ID
ebookData, err := _i.Repo.Create(ebookEntity)
if err != nil {
return nil, err
}
return ebookData, nil
}
// SavePdfFile implement interface of EbooksService
func (_i *ebooksService) SavePdfFile(c *fiber.Ctx, ebookId uint) (err error) {
// Get the uploaded file
file, err := c.FormFile("file")
if err != nil {
return errors.New("file is required")
}
// Validate file type
contentType := file.Header.Get("Content-Type")
if contentType != "application/pdf" {
return errors.New("only PDF files are allowed")
}
// Validate file size (max 50MB)
if file.Size > 50*1024*1024 {
return errors.New("file size must be less than 50MB")
}
// Generate unique filename
ext := filepath.Ext(file.Filename)
filename := fmt.Sprintf("ebook_%d_%d%s", ebookId, time.Now().Unix(), ext)
// Upload to MinIO
fileReader, err := file.Open()
if err != nil {
return err
}
defer fileReader.Close()
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return err
}
objectName := fmt.Sprintf("ebooks/pdfs/%s", filename)
_, err = minioClient.PutObject(
context.Background(),
bucketName,
objectName,
fileReader,
file.Size,
minio.PutObjectOptions{
ContentType: contentType,
},
)
if err != nil {
return err
}
// Update ebook record with file info
ebookUpdate := &entity.Ebooks{
PdfFilePath: &objectName,
PdfFileName: &filename,
PdfFileSize: &file.Size,
}
err = _i.Repo.UpdateSkipNull(ebookId, ebookUpdate)
if err != nil {
return err
}
return nil
}
// SaveThumbnail implement interface of EbooksService
func (_i *ebooksService) SaveThumbnail(c *fiber.Ctx, ebookId uint) (err error) {
// Get the uploaded file
file, err := c.FormFile("file")
if err != nil {
return errors.New("file is required")
}
// Validate file type
contentType := file.Header.Get("Content-Type")
if !strings.HasPrefix(contentType, "image/") {
return errors.New("only image files are allowed")
}
// Validate file size (max 5MB)
if file.Size > 5*1024*1024 {
return errors.New("file size must be less than 5MB")
}
// Generate unique filename
ext := filepath.Ext(file.Filename)
filename := fmt.Sprintf("ebook_thumbnail_%d_%d%s", ebookId, time.Now().Unix(), ext)
// Upload to MinIO
fileReader, err := file.Open()
if err != nil {
return err
}
defer fileReader.Close()
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return err
}
objectName := fmt.Sprintf("ebooks/thumbnails/%s", filename)
_, err = minioClient.PutObject(
context.Background(),
bucketName,
objectName,
fileReader,
file.Size,
minio.PutObjectOptions{
ContentType: contentType,
},
)
if err != nil {
return err
}
// Update ebook record with thumbnail info
ebookUpdate := &entity.Ebooks{
ThumbnailPath: &objectName,
ThumbnailName: &filename,
}
err = _i.Repo.UpdateSkipNull(ebookId, ebookUpdate)
if err != nil {
return err
}
return nil
}
// Update implement interface of EbooksService
func (_i *ebooksService) Update(id uint, req request.EbooksUpdateRequest) (err error) {
ebookEntity := req.ToEntity()
err = _i.Repo.Update(id, ebookEntity)
if err != nil {
return err
}
return nil
}
// Delete implement interface of EbooksService
func (_i *ebooksService) Delete(id uint) error {
err := _i.Repo.Delete(id)
if err != nil {
return err
}
return nil
}
// SummaryStats implement interface of EbooksService
func (_i *ebooksService) SummaryStats(authToken string) (summaryStats *response.EbookSummaryStats, err error) {
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user == nil {
return nil, errors.New("user not found")
}
summaryStats, err = _i.Repo.SummaryStats(user.ID)
if err != nil {
return nil, err
}
return summaryStats, nil
}
// DownloadPdf implement interface of EbooksService
func (_i *ebooksService) DownloadPdf(c *fiber.Ctx, ebookId uint) error {
// Get ebook data
ebook, err := _i.Repo.FindOne(ebookId)
if err != nil {
return err
}
if ebook.PdfFilePath == nil {
return errors.New("PDF file not found")
}
// Check if user has purchased this ebook
authToken := c.Get("Authorization")
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if user == nil {
return errors.New("user not found")
}
purchase, err := _i.PurchaseRepo.FindByBuyerAndEbook(user.ID, ebookId)
if err != nil {
return errors.New("you must purchase this ebook before downloading")
}
if purchase.PaymentStatus == nil || *purchase.PaymentStatus != "paid" {
return errors.New("payment not completed")
}
// Get file from MinIO
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return err
}
object, err := minioClient.GetObject(
context.Background(),
bucketName,
*ebook.PdfFilePath,
minio.GetObjectOptions{},
)
if err != nil {
return err
}
defer object.Close()
// Read file content
fileContent, err := io.ReadAll(object)
if err != nil {
return err
}
// Update download count
err = _i.Repo.UpdateDownloadCount(ebookId)
if err != nil {
_i.Log.Error().Err(err).Msg("Failed to update download count")
}
err = _i.PurchaseRepo.UpdateDownloadCount(purchase.ID)
if err != nil {
_i.Log.Error().Err(err).Msg("Failed to update purchase download count")
}
// Set response headers
c.Set("Content-Type", "application/pdf")
c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *ebook.PdfFileName))
c.Set("Content-Length", strconv.Itoa(len(fileContent)))
return c.Send(fileContent)
}