366 lines
9.1 KiB
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)
|
|
}
|