feat: add ebook features
This commit is contained in:
parent
9788d5c2f9
commit
21c68096c3
|
|
@ -0,0 +1,26 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
users "narasi-ahli-be/app/database/entity/users"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EbookPurchases struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
BuyerId uint `json:"buyer_id" gorm:"type:int4"`
|
||||||
|
Buyer *users.Users `json:"buyer" gorm:"foreignKey:BuyerId;references:ID"`
|
||||||
|
EbookId uint `json:"ebook_id" gorm:"type:int4"`
|
||||||
|
Ebook *Ebooks `json:"ebook" gorm:"foreignKey:EbookId;references:ID"`
|
||||||
|
PurchasePrice float64 `json:"purchase_price" gorm:"type:decimal(10,2)"`
|
||||||
|
PaymentMethod *string `json:"payment_method" gorm:"type:varchar"`
|
||||||
|
PaymentStatus *string `json:"payment_status" gorm:"type:varchar;default:'pending'"`
|
||||||
|
TransactionId *string `json:"transaction_id" gorm:"type:varchar"`
|
||||||
|
PaymentProof *string `json:"payment_proof" gorm:"type:varchar"`
|
||||||
|
PaymentDate *time.Time `json:"payment_date" gorm:"type:timestamp"`
|
||||||
|
DownloadCount *int `json:"download_count" gorm:"type:int4;default:0"`
|
||||||
|
LastDownloadAt *time.Time `json:"last_download_at" gorm:"type:timestamp"`
|
||||||
|
StatusId *int `json:"status_id" gorm:"type:int4;default:1"`
|
||||||
|
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
users "narasi-ahli-be/app/database/entity/users"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EbookRatings struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
UserId uint `json:"user_id" gorm:"type:int4"`
|
||||||
|
User *users.Users `json:"user" gorm:"foreignKey:UserId;references:ID"`
|
||||||
|
EbookId uint `json:"ebook_id" gorm:"type:int4"`
|
||||||
|
Ebook *Ebooks `json:"ebook" gorm:"foreignKey:EbookId;references:ID"`
|
||||||
|
PurchaseId uint `json:"purchase_id" gorm:"type:int4"`
|
||||||
|
Purchase *EbookPurchases `json:"purchase" gorm:"foreignKey:PurchaseId;references:ID"`
|
||||||
|
Rating int `json:"rating" gorm:"type:int4;check:rating >= 1 AND rating <= 5"`
|
||||||
|
Review *string `json:"review" gorm:"type:text"`
|
||||||
|
IsAnonymous *bool `json:"is_anonymous" gorm:"type:bool;default:false"`
|
||||||
|
IsVerified *bool `json:"is_verified" gorm:"type:bool;default:true"`
|
||||||
|
StatusId *int `json:"status_id" gorm:"type:int4;default:1"`
|
||||||
|
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
users "narasi-ahli-be/app/database/entity/users"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EbookWishlists struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
UserId uint `json:"user_id" gorm:"type:int4"`
|
||||||
|
User *users.Users `json:"user" gorm:"foreignKey:UserId;references:ID"`
|
||||||
|
EbookId uint `json:"ebook_id" gorm:"type:int4"`
|
||||||
|
Ebook *Ebooks `json:"ebook" gorm:"foreignKey:EbookId;references:ID"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
users "narasi-ahli-be/app/database/entity/users"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ebooks struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||||
|
Title string `json:"title" gorm:"type:varchar"`
|
||||||
|
Slug string `json:"slug" gorm:"type:varchar"`
|
||||||
|
Description string `json:"description" gorm:"type:varchar"`
|
||||||
|
Price float64 `json:"price" gorm:"type:decimal(10,2)"`
|
||||||
|
PdfFilePath *string `json:"pdf_file_path" gorm:"type:varchar"`
|
||||||
|
PdfFileName *string `json:"pdf_file_name" gorm:"type:varchar"`
|
||||||
|
PdfFileSize *int64 `json:"pdf_file_size" gorm:"type:int8"`
|
||||||
|
ThumbnailPath *string `json:"thumbnail_path" gorm:"type:varchar"`
|
||||||
|
ThumbnailName *string `json:"thumbnail_name" gorm:"type:varchar"`
|
||||||
|
AuthorId uint `json:"author_id" gorm:"type:int4"`
|
||||||
|
Author *users.Users `json:"author" gorm:"foreignKey:AuthorId;references:ID"`
|
||||||
|
Category *string `json:"category" gorm:"type:varchar"`
|
||||||
|
Tags *string `json:"tags" gorm:"type:varchar"`
|
||||||
|
PageCount *int `json:"page_count" gorm:"type:int4"`
|
||||||
|
Language *string `json:"language" gorm:"type:varchar;default:'id'"`
|
||||||
|
Isbn *string `json:"isbn" gorm:"type:varchar"`
|
||||||
|
Publisher *string `json:"publisher" gorm:"type:varchar"`
|
||||||
|
PublishedYear *int `json:"published_year" gorm:"type:int4"`
|
||||||
|
DownloadCount *int `json:"download_count" gorm:"type:int4;default:0"`
|
||||||
|
PurchaseCount *int `json:"purchase_count" gorm:"type:int4;default:0"`
|
||||||
|
WishlistCount *int `json:"wishlist_count" gorm:"type:int4;default:0"`
|
||||||
|
Rating *float64 `json:"rating" gorm:"type:decimal(3,2);default:0"`
|
||||||
|
ReviewCount *int `json:"review_count" gorm:"type:int4;default:0"`
|
||||||
|
StatusId *int `json:"status_id" gorm:"type:int4;default:1"`
|
||||||
|
IsActive *bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||||
|
IsPublished *bool `json:"is_published" gorm:"type:bool;default:false"`
|
||||||
|
PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"`
|
||||||
|
CreatedById *uint `json:"created_by_id" gorm:"type:int4"`
|
||||||
|
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||||
|
}
|
||||||
|
|
@ -125,6 +125,12 @@ func Models() []interface{} {
|
||||||
entity.AIChatSessions{},
|
entity.AIChatSessions{},
|
||||||
entity.AIChatMessages{},
|
entity.AIChatMessages{},
|
||||||
entity.AIChatLogs{},
|
entity.AIChatLogs{},
|
||||||
|
|
||||||
|
// Ebook entities
|
||||||
|
entity.Ebooks{},
|
||||||
|
entity.EbookWishlists{},
|
||||||
|
entity.EbookPurchases{},
|
||||||
|
entity.EbookRatings{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/ebooks/service"
|
||||||
|
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
utilRes "narasi-ahli-be/utils/response"
|
||||||
|
utilVal "narasi-ahli-be/utils/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ebookPurchasesController struct {
|
||||||
|
purchaseService service.EbookPurchasesService
|
||||||
|
usersRepo usersRepository.UsersRepository
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookPurchasesController interface {
|
||||||
|
GetByBuyerId(c *fiber.Ctx) error
|
||||||
|
PurchaseEbook(c *fiber.Ctx) error
|
||||||
|
UpdatePaymentStatus(c *fiber.Ctx) error
|
||||||
|
GetPurchaseById(c *fiber.Ctx) error
|
||||||
|
ConfirmPayment(c *fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEbookPurchasesController(purchaseService service.EbookPurchasesService, usersRepo usersRepository.UsersRepository, log zerolog.Logger) EbookPurchasesController {
|
||||||
|
return &ebookPurchasesController{
|
||||||
|
purchaseService: purchaseService,
|
||||||
|
usersRepo: usersRepo,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByBuyerId Purchase
|
||||||
|
// @Summary Get user purchases
|
||||||
|
// @Description API for getting user purchases
|
||||||
|
// @Tags Ebook Purchase
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/purchases [get]
|
||||||
|
func (_i *ebookPurchasesController) GetByBuyerId(c *fiber.Ctx) error {
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
purchasesData, paging, err := _i.purchaseService.GetByBuyerId(authToken, paginate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Purchases successfully retrieved"},
|
||||||
|
Data: purchasesData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PurchaseEbook Purchase
|
||||||
|
// @Summary Purchase ebook
|
||||||
|
// @Description API for purchasing ebook
|
||||||
|
// @Tags Ebook Purchase
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param ebookId path int true "Ebook ID"
|
||||||
|
// @Param paymentMethod query string true "Payment Method"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/purchase/{ebookId} [post]
|
||||||
|
func (_i *ebookPurchasesController) PurchaseEbook(c *fiber.Ctx) error {
|
||||||
|
ebookId, err := strconv.ParseUint(c.Params("ebookId"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentMethod := c.Query("paymentMethod")
|
||||||
|
if paymentMethod == "" {
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: false,
|
||||||
|
Messages: utilRes.Messages{"Payment method is required"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
purchase, err := _i.purchaseService.PurchaseEbook(authToken, uint(ebookId), paymentMethod)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook purchase successfully created"},
|
||||||
|
Data: purchase,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePaymentStatus Purchase
|
||||||
|
// @Summary Update payment status
|
||||||
|
// @Description API for updating payment status
|
||||||
|
// @Tags Ebook Purchase
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "Purchase ID"
|
||||||
|
// @Param payload body PaymentStatusUpdateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/purchases/{id}/payment [put]
|
||||||
|
func (_i *ebookPurchasesController) UpdatePaymentStatus(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(PaymentStatusUpdateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.purchaseService.UpdatePaymentStatus(uint(id), req.PaymentStatus, req.TransactionId, req.PaymentProof)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Payment status successfully updated"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPurchaseById Purchase
|
||||||
|
// @Summary Get purchase by ID
|
||||||
|
// @Description API for getting purchase by ID
|
||||||
|
// @Tags Ebook Purchase
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path int true "Purchase ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/purchases/{id} [get]
|
||||||
|
func (_i *ebookPurchasesController) GetPurchaseById(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
purchaseData, err := _i.purchaseService.GetPurchaseById(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Purchase successfully retrieved"},
|
||||||
|
Data: purchaseData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfirmPayment Purchase
|
||||||
|
// @Summary Confirm payment
|
||||||
|
// @Description API for confirming payment
|
||||||
|
// @Tags Ebook Purchase
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "Purchase ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/purchases/{id}/confirm [put]
|
||||||
|
func (_i *ebookPurchasesController) ConfirmPayment(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.purchaseService.ConfirmPayment(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Payment successfully confirmed"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaymentStatusUpdateRequest struct for payment status update
|
||||||
|
type PaymentStatusUpdateRequest struct {
|
||||||
|
PaymentStatus string `json:"paymentStatus" validate:"required"`
|
||||||
|
TransactionId string `json:"transactionId"`
|
||||||
|
PaymentProof string `json:"paymentProof"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,313 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/ebooks/request"
|
||||||
|
"narasi-ahli-be/app/module/ebooks/service"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
utilRes "narasi-ahli-be/utils/response"
|
||||||
|
utilVal "narasi-ahli-be/utils/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ebookRatingsController struct {
|
||||||
|
ratingsService service.EbookRatingsService
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookRatingsController interface {
|
||||||
|
GetAll(c *fiber.Ctx) error
|
||||||
|
GetByEbookId(c *fiber.Ctx) error
|
||||||
|
GetByUserId(c *fiber.Ctx) error
|
||||||
|
GetEbookRatingSummary(c *fiber.Ctx) error
|
||||||
|
Create(c *fiber.Ctx) error
|
||||||
|
Update(c *fiber.Ctx) error
|
||||||
|
Delete(c *fiber.Ctx) error
|
||||||
|
GetRatingStats(c *fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEbookRatingsController(ratingsService service.EbookRatingsService, log zerolog.Logger) EbookRatingsController {
|
||||||
|
return &ebookRatingsController{
|
||||||
|
ratingsService: ratingsService,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll Ratings
|
||||||
|
// @Summary Get all Ebook Ratings
|
||||||
|
// @Description API for getting all Ebook Ratings
|
||||||
|
// @Tags Ebook Ratings
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param req query request.EbookRatingsQueryRequest false "query parameters"
|
||||||
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebook-ratings [get]
|
||||||
|
func (_i *ebookRatingsController) GetAll(c *fiber.Ctx) error {
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqContext := request.EbookRatingsQueryRequestContext{
|
||||||
|
EbookId: c.Query("ebookId"),
|
||||||
|
UserId: c.Query("userId"),
|
||||||
|
Rating: c.Query("rating"),
|
||||||
|
IsVerified: c.Query("isVerified"),
|
||||||
|
StatusId: c.Query("statusId"),
|
||||||
|
}
|
||||||
|
req := reqContext.ToParamRequest()
|
||||||
|
req.Pagination = paginate
|
||||||
|
|
||||||
|
ratingsData, paging, err := _i.ratingsService.GetAll(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook ratings list successfully retrieved"},
|
||||||
|
Data: ratingsData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByEbookId Ratings
|
||||||
|
// @Summary Get ratings by ebook ID
|
||||||
|
// @Description API for getting ratings by ebook ID
|
||||||
|
// @Tags Ebook Ratings
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param id path int true "Ebook ID"
|
||||||
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebook-ratings/ebook/{id} [get]
|
||||||
|
func (_i *ebookRatingsController) GetByEbookId(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ratingsData, paging, err := _i.ratingsService.GetByEbookId(uint(id), paginate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook ratings successfully retrieved"},
|
||||||
|
Data: ratingsData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByUserId Ratings
|
||||||
|
// @Summary Get user ratings
|
||||||
|
// @Description API for getting user ratings
|
||||||
|
// @Tags Ebook Ratings
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebook-ratings/user [get]
|
||||||
|
func (_i *ebookRatingsController) GetByUserId(c *fiber.Ctx) error {
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
ratingsData, paging, err := _i.ratingsService.GetByUserId(authToken, paginate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"User ratings successfully retrieved"},
|
||||||
|
Data: ratingsData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEbookRatingSummary Ratings
|
||||||
|
// @Summary Get ebook rating summary
|
||||||
|
// @Description API for getting ebook rating summary with stats and recent reviews
|
||||||
|
// @Tags Ebook Ratings
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param id path int true "Ebook ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebook-ratings/summary/{id} [get]
|
||||||
|
func (_i *ebookRatingsController) GetEbookRatingSummary(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
summaryData, err := _i.ratingsService.GetEbookRatingSummary(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook rating summary successfully retrieved"},
|
||||||
|
Data: summaryData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Rating
|
||||||
|
// @Summary Create ebook rating
|
||||||
|
// @Description API for creating ebook rating
|
||||||
|
// @Tags Ebook Ratings
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
|
// @Param payload body request.EbookRatingsCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebook-ratings [post]
|
||||||
|
func (_i *ebookRatingsController) Create(c *fiber.Ctx) error {
|
||||||
|
req := new(request.EbookRatingsCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
dataResult, err := _i.ratingsService.Create(*req, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook rating successfully created"},
|
||||||
|
Data: dataResult,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Rating
|
||||||
|
// @Summary Update ebook rating
|
||||||
|
// @Description API for updating ebook rating
|
||||||
|
// @Tags Ebook Ratings
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
|
// @Param payload body request.EbookRatingsUpdateRequest true "Required payload"
|
||||||
|
// @Param id path int true "Rating ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebook-ratings/{id} [put]
|
||||||
|
func (_i *ebookRatingsController) Update(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(request.EbookRatingsUpdateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
err = _i.ratingsService.Update(uint(id), *req, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook rating successfully updated"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete Rating
|
||||||
|
// @Summary Delete ebook rating
|
||||||
|
// @Description API for deleting ebook rating
|
||||||
|
// @Tags Ebook Ratings
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
|
// @Param id path int true "Rating ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebook-ratings/{id} [delete]
|
||||||
|
func (_i *ebookRatingsController) Delete(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
err = _i.ratingsService.Delete(uint(id), authToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook rating successfully deleted"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRatingStats Ratings
|
||||||
|
// @Summary Get ebook rating statistics
|
||||||
|
// @Description API for getting ebook rating statistics
|
||||||
|
// @Tags Ebook Ratings
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param id path int true "Ebook ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebook-ratings/stats/{id} [get]
|
||||||
|
func (_i *ebookRatingsController) GetRatingStats(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
statsData, err := _i.ratingsService.GetRatingStats(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook rating statistics successfully retrieved"},
|
||||||
|
Data: statsData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/ebooks/service"
|
||||||
|
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
utilRes "narasi-ahli-be/utils/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ebookWishlistsController struct {
|
||||||
|
wishlistService service.EbookWishlistsService
|
||||||
|
usersRepo usersRepository.UsersRepository
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookWishlistsController interface {
|
||||||
|
GetByUserId(c *fiber.Ctx) error
|
||||||
|
AddToWishlist(c *fiber.Ctx) error
|
||||||
|
RemoveFromWishlist(c *fiber.Ctx) error
|
||||||
|
IsInWishlist(c *fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEbookWishlistsController(wishlistService service.EbookWishlistsService, usersRepo usersRepository.UsersRepository, log zerolog.Logger) EbookWishlistsController {
|
||||||
|
return &ebookWishlistsController{
|
||||||
|
wishlistService: wishlistService,
|
||||||
|
usersRepo: usersRepo,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByUserId Wishlist
|
||||||
|
// @Summary Get user wishlist
|
||||||
|
// @Description API for getting user wishlist
|
||||||
|
// @Tags Ebook Wishlist
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/wishlist [get]
|
||||||
|
func (_i *ebookWishlistsController) GetByUserId(c *fiber.Ctx) error {
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
wishlistsData, paging, err := _i.wishlistService.GetByUserId(authToken, paginate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Wishlist successfully retrieved"},
|
||||||
|
Data: wishlistsData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToWishlist Wishlist
|
||||||
|
// @Summary Add ebook to wishlist
|
||||||
|
// @Description API for adding ebook to wishlist
|
||||||
|
// @Tags Ebook Wishlist
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param ebookId path int true "Ebook ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/wishlist/{ebookId} [post]
|
||||||
|
func (_i *ebookWishlistsController) AddToWishlist(c *fiber.Ctx) error {
|
||||||
|
ebookId, err := strconv.ParseUint(c.Params("ebookId"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
err = _i.wishlistService.AddToWishlist(authToken, uint(ebookId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook successfully added to wishlist"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFromWishlist Wishlist
|
||||||
|
// @Summary Remove ebook from wishlist
|
||||||
|
// @Description API for removing ebook from wishlist
|
||||||
|
// @Tags Ebook Wishlist
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param ebookId path int true "Ebook ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/wishlist/{ebookId} [delete]
|
||||||
|
func (_i *ebookWishlistsController) RemoveFromWishlist(c *fiber.Ctx) error {
|
||||||
|
ebookId, err := strconv.ParseUint(c.Params("ebookId"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
err = _i.wishlistService.RemoveFromWishlist(authToken, uint(ebookId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook successfully removed from wishlist"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInWishlist Wishlist
|
||||||
|
// @Summary Check if ebook is in wishlist
|
||||||
|
// @Description API for checking if ebook is in wishlist
|
||||||
|
// @Tags Ebook Wishlist
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param ebookId path int true "Ebook ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/wishlist/check/{ebookId} [get]
|
||||||
|
func (_i *ebookWishlistsController) IsInWishlist(c *fiber.Ctx) error {
|
||||||
|
ebookId, err := strconv.ParseUint(c.Params("ebookId"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
isInWishlist, err := _i.wishlistService.IsInWishlist(authToken, uint(ebookId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Wishlist status successfully retrieved"},
|
||||||
|
Data: map[string]bool{"isInWishlist": isInWishlist},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,350 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/ebooks/request"
|
||||||
|
"narasi-ahli-be/app/module/ebooks/service"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
utilRes "narasi-ahli-be/utils/response"
|
||||||
|
utilVal "narasi-ahli-be/utils/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ebooksController struct {
|
||||||
|
ebooksService service.EbooksService
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbooksController interface {
|
||||||
|
All(c *fiber.Ctx) error
|
||||||
|
Show(c *fiber.Ctx) error
|
||||||
|
ShowBySlug(c *fiber.Ctx) error
|
||||||
|
Save(c *fiber.Ctx) error
|
||||||
|
SavePdfFile(c *fiber.Ctx) error
|
||||||
|
SaveThumbnail(c *fiber.Ctx) error
|
||||||
|
Update(c *fiber.Ctx) error
|
||||||
|
Delete(c *fiber.Ctx) error
|
||||||
|
SummaryStats(c *fiber.Ctx) error
|
||||||
|
DownloadPdf(c *fiber.Ctx) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEbooksController(ebooksService service.EbooksService, log zerolog.Logger) EbooksController {
|
||||||
|
return &ebooksController{
|
||||||
|
ebooksService: ebooksService,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All Ebooks
|
||||||
|
// @Summary Get all Ebooks
|
||||||
|
// @Description API for getting all Ebooks
|
||||||
|
// @Tags Ebooks
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Client-Key header string true "Insert the X-Client-Key"
|
||||||
|
// @Param req query request.EbooksQueryRequest false "query parameters"
|
||||||
|
// @Param req query paginator.Pagination false "pagination parameters"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks [get]
|
||||||
|
func (_i *ebooksController) All(c *fiber.Ctx) error {
|
||||||
|
paginate, err := paginator.Paginate(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqContext := request.EbooksQueryRequestContext{
|
||||||
|
Title: c.Query("title"),
|
||||||
|
Description: c.Query("description"),
|
||||||
|
AuthorId: c.Query("authorId"),
|
||||||
|
Category: c.Query("category"),
|
||||||
|
Tags: c.Query("tags"),
|
||||||
|
MinPrice: c.Query("minPrice"),
|
||||||
|
MaxPrice: c.Query("maxPrice"),
|
||||||
|
StatusId: c.Query("statusId"),
|
||||||
|
IsPublished: c.Query("isPublished"),
|
||||||
|
}
|
||||||
|
req := reqContext.ToParamRequest()
|
||||||
|
req.Pagination = paginate
|
||||||
|
|
||||||
|
ebooksData, paging, err := _i.ebooksService.All(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebooks list successfully retrieved"},
|
||||||
|
Data: ebooksData,
|
||||||
|
Meta: paging,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show Ebook
|
||||||
|
// @Summary Get one Ebook
|
||||||
|
// @Description API for getting one Ebook
|
||||||
|
// @Tags Ebooks
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path int true "Ebook ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/{id} [get]
|
||||||
|
func (_i *ebooksController) Show(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ebookData, err := _i.ebooksService.Show(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook successfully retrieved"},
|
||||||
|
Data: ebookData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowBySlug Ebook
|
||||||
|
// @Summary Get one Ebook by slug
|
||||||
|
// @Description API for getting one Ebook by slug
|
||||||
|
// @Tags Ebooks
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param slug path string true "Ebook Slug"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/slug/{slug} [get]
|
||||||
|
func (_i *ebooksController) ShowBySlug(c *fiber.Ctx) error {
|
||||||
|
slug := c.Params("slug")
|
||||||
|
|
||||||
|
ebookData, err := _i.ebooksService.ShowBySlug(slug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook successfully retrieved"},
|
||||||
|
Data: ebookData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save Ebook
|
||||||
|
// @Summary Create Ebook
|
||||||
|
// @Description API for create Ebook
|
||||||
|
// @Tags Ebooks
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token"
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
|
// @Param payload body request.EbooksCreateRequest true "Required payload"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks [post]
|
||||||
|
func (_i *ebooksController) Save(c *fiber.Ctx) error {
|
||||||
|
req := new(request.EbooksCreateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
dataResult, err := _i.ebooksService.Save(*req, authToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook successfully created"},
|
||||||
|
Data: dataResult,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SavePdfFile Ebook
|
||||||
|
// @Summary Save PDF File Ebook
|
||||||
|
// @Description API for Save PDF File of Ebook
|
||||||
|
// @Tags Ebooks
|
||||||
|
// @Security Bearer
|
||||||
|
// @Produce json
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param file formData file true "Upload PDF file"
|
||||||
|
// @Param id path int true "Ebook ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/pdf/{id} [post]
|
||||||
|
func (_i *ebooksController) SavePdfFile(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.ebooksService.SavePdfFile(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"PDF file of Ebook successfully uploaded"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveThumbnail Ebook
|
||||||
|
// @Summary Save Thumbnail Ebook
|
||||||
|
// @Description API for Save Thumbnail of Ebook
|
||||||
|
// @Tags Ebooks
|
||||||
|
// @Security Bearer
|
||||||
|
// @Produce json
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param files formData file true "Upload thumbnail"
|
||||||
|
// @Param id path int true "Ebook ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/thumbnail/{id} [post]
|
||||||
|
func (_i *ebooksController) SaveThumbnail(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.ebooksService.SaveThumbnail(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Thumbnail of Ebook successfully uploaded"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Ebook
|
||||||
|
// @Summary Update Ebook
|
||||||
|
// @Description API for update Ebook
|
||||||
|
// @Tags Ebooks
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param payload body request.EbooksUpdateRequest true "Required payload"
|
||||||
|
// @Param id path int true "Ebook ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/{id} [put]
|
||||||
|
func (_i *ebooksController) Update(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(request.EbooksUpdateRequest)
|
||||||
|
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.ebooksService.Update(uint(id), *req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook successfully updated"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete Ebook
|
||||||
|
// @Summary Delete Ebook
|
||||||
|
// @Description API for delete Ebook
|
||||||
|
// @Tags Ebooks
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||||
|
// @Param id path int true "Ebook ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/{id} [delete]
|
||||||
|
func (_i *ebooksController) Delete(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.ebooksService.Delete(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Ebook successfully deleted"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SummaryStats Ebook
|
||||||
|
// @Summary SummaryStats Ebook
|
||||||
|
// @Description API for Summary Stats of Ebook
|
||||||
|
// @Tags Ebooks
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/statistic/summary [get]
|
||||||
|
func (_i *ebooksController) SummaryStats(c *fiber.Ctx) error {
|
||||||
|
authToken := c.Get("Authorization")
|
||||||
|
|
||||||
|
response, err := _i.ebooksService.SummaryStats(authToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Summary Stats of Ebooks successfully retrieved"},
|
||||||
|
Data: response,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadPdf Ebook
|
||||||
|
// @Summary Download PDF Ebook
|
||||||
|
// @Description API for Download PDF of Ebook
|
||||||
|
// @Tags Ebooks
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path int true "Ebook ID"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /ebooks/download/{id} [get]
|
||||||
|
func (_i *ebooksController) DownloadPdf(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.ebooksService.DownloadPdf(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
package ebooks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/module/ebooks/controller"
|
||||||
|
"narasi-ahli-be/app/module/ebooks/repository"
|
||||||
|
"narasi-ahli-be/app/module/ebooks/service"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EbooksRouter struct of EbooksRouter
|
||||||
|
type EbooksRouter struct {
|
||||||
|
App fiber.Router
|
||||||
|
Controller *Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controller struct for all controllers
|
||||||
|
type Controller struct {
|
||||||
|
Ebooks controller.EbooksController
|
||||||
|
Wishlists controller.EbookWishlistsController
|
||||||
|
Purchases controller.EbookPurchasesController
|
||||||
|
Ratings controller.EbookRatingsController
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEbooksModule register bulky of Ebooks module
|
||||||
|
var NewEbooksModule = fx.Options(
|
||||||
|
// register repository of Ebooks module
|
||||||
|
fx.Provide(repository.NewEbooksRepository),
|
||||||
|
fx.Provide(repository.NewEbookWishlistsRepository),
|
||||||
|
fx.Provide(repository.NewEbookPurchasesRepository),
|
||||||
|
fx.Provide(repository.NewEbookRatingsRepository),
|
||||||
|
|
||||||
|
// register service of Ebooks module
|
||||||
|
fx.Provide(service.NewEbooksService),
|
||||||
|
fx.Provide(service.NewEbookWishlistsService),
|
||||||
|
fx.Provide(service.NewEbookPurchasesService),
|
||||||
|
fx.Provide(service.NewEbookRatingsService),
|
||||||
|
|
||||||
|
// register controller of Ebooks module
|
||||||
|
fx.Provide(controller.NewEbooksController),
|
||||||
|
fx.Provide(controller.NewEbookWishlistsController),
|
||||||
|
fx.Provide(controller.NewEbookPurchasesController),
|
||||||
|
fx.Provide(controller.NewEbookRatingsController),
|
||||||
|
fx.Provide(NewController),
|
||||||
|
|
||||||
|
// register router of Ebooks module
|
||||||
|
fx.Provide(NewEbooksRouter),
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewController init Controller
|
||||||
|
func NewController(
|
||||||
|
ebooksController controller.EbooksController,
|
||||||
|
wishlistsController controller.EbookWishlistsController,
|
||||||
|
purchasesController controller.EbookPurchasesController,
|
||||||
|
ratingsController controller.EbookRatingsController,
|
||||||
|
) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
Ebooks: ebooksController,
|
||||||
|
Wishlists: wishlistsController,
|
||||||
|
Purchases: purchasesController,
|
||||||
|
Ratings: ratingsController,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEbooksRouter init EbooksRouter
|
||||||
|
func NewEbooksRouter(fiber *fiber.App, controller *Controller) *EbooksRouter {
|
||||||
|
return &EbooksRouter{
|
||||||
|
App: fiber,
|
||||||
|
Controller: controller,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterEbooksRoutes register routes of Ebooks module
|
||||||
|
func (_i *EbooksRouter) RegisterEbooksRoutes() {
|
||||||
|
// define controllers
|
||||||
|
ebooksController := _i.Controller.Ebooks
|
||||||
|
wishlistsController := _i.Controller.Wishlists
|
||||||
|
purchasesController := _i.Controller.Purchases
|
||||||
|
ratingsController := _i.Controller.Ratings
|
||||||
|
|
||||||
|
// define routes
|
||||||
|
_i.App.Route("/ebooks", func(router fiber.Router) {
|
||||||
|
// Ebook CRUD routes
|
||||||
|
router.Get("/", ebooksController.All)
|
||||||
|
router.Get("/slug/:slug", ebooksController.ShowBySlug)
|
||||||
|
router.Get("/:id", ebooksController.Show)
|
||||||
|
router.Post("/", ebooksController.Save)
|
||||||
|
router.Put("/:id", ebooksController.Update)
|
||||||
|
router.Delete("/:id", ebooksController.Delete)
|
||||||
|
|
||||||
|
// File upload routes
|
||||||
|
router.Post("/pdf/:id", ebooksController.SavePdfFile)
|
||||||
|
router.Post("/thumbnail/:id", ebooksController.SaveThumbnail)
|
||||||
|
|
||||||
|
// Download route
|
||||||
|
router.Get("/download/:id", ebooksController.DownloadPdf)
|
||||||
|
|
||||||
|
// Statistics route
|
||||||
|
router.Get("/statistic/summary", ebooksController.SummaryStats)
|
||||||
|
|
||||||
|
// Wishlist routes
|
||||||
|
router.Get("/wishlist", wishlistsController.GetByUserId)
|
||||||
|
router.Post("/wishlist/:ebookId", wishlistsController.AddToWishlist)
|
||||||
|
router.Delete("/wishlist/:ebookId", wishlistsController.RemoveFromWishlist)
|
||||||
|
router.Get("/wishlist/check/:ebookId", wishlistsController.IsInWishlist)
|
||||||
|
|
||||||
|
// Purchase routes
|
||||||
|
router.Get("/purchases", purchasesController.GetByBuyerId)
|
||||||
|
router.Post("/purchase/:ebookId", purchasesController.PurchaseEbook)
|
||||||
|
router.Get("/purchases/:id", purchasesController.GetPurchaseById)
|
||||||
|
router.Put("/purchases/:id/payment", purchasesController.UpdatePaymentStatus)
|
||||||
|
router.Put("/purchases/:id/confirm", purchasesController.ConfirmPayment)
|
||||||
|
|
||||||
|
// Rating routes
|
||||||
|
router.Get("/ratings", ratingsController.GetAll)
|
||||||
|
router.Post("/ratings", ratingsController.Create)
|
||||||
|
router.Put("/ratings/:id", ratingsController.Update)
|
||||||
|
router.Delete("/ratings/:id", ratingsController.Delete)
|
||||||
|
router.Get("/ratings/ebook/:id", ratingsController.GetByEbookId)
|
||||||
|
router.Get("/ratings/user", ratingsController.GetByUserId)
|
||||||
|
router.Get("/ratings/summary/:id", ratingsController.GetEbookRatingSummary)
|
||||||
|
router.Get("/ratings/stats/:id", ratingsController.GetRatingStats)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
ebookRatingsResponse "narasi-ahli-be/app/module/ebooks/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToEbookRatingsResponse(rating *entity.EbookRatings) *ebookRatingsResponse.EbookRatingsResponse {
|
||||||
|
if rating == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var userName, userEmail *string
|
||||||
|
if rating.User != nil {
|
||||||
|
if rating.IsAnonymous != nil && *rating.IsAnonymous {
|
||||||
|
userName = stringPtr("Anonymous")
|
||||||
|
userEmail = nil
|
||||||
|
} else {
|
||||||
|
userName = &rating.User.Fullname
|
||||||
|
userEmail = &rating.User.Email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ebookTitle *string
|
||||||
|
if rating.Ebook != nil {
|
||||||
|
ebookTitle = &rating.Ebook.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ebookRatingsResponse.EbookRatingsResponse{
|
||||||
|
ID: rating.ID,
|
||||||
|
UserId: rating.UserId,
|
||||||
|
UserName: userName,
|
||||||
|
UserEmail: userEmail,
|
||||||
|
EbookId: rating.EbookId,
|
||||||
|
EbookTitle: ebookTitle,
|
||||||
|
PurchaseId: rating.PurchaseId,
|
||||||
|
Rating: rating.Rating,
|
||||||
|
Review: rating.Review,
|
||||||
|
IsAnonymous: rating.IsAnonymous,
|
||||||
|
IsVerified: rating.IsVerified,
|
||||||
|
StatusId: rating.StatusId,
|
||||||
|
IsActive: rating.IsActive,
|
||||||
|
CreatedAt: rating.CreatedAt,
|
||||||
|
UpdatedAt: rating.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToEbookRatingsResponseList(ratings []*entity.EbookRatings) []*ebookRatingsResponse.EbookRatingsResponse {
|
||||||
|
if ratings == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var responses []*ebookRatingsResponse.EbookRatingsResponse
|
||||||
|
for _, rating := range ratings {
|
||||||
|
responses = append(responses, ToEbookRatingsResponse(rating))
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToEbookRatingSummaryResponse(ebookId uint, ebookTitle string, averageRating float64, totalRatings int, ratingCounts map[int]int, recentReviews []*entity.EbookRatings) *ebookRatingsResponse.EbookRatingSummaryResponse {
|
||||||
|
reviews := ToEbookRatingsResponseList(recentReviews)
|
||||||
|
var reviewsList []ebookRatingsResponse.EbookRatingsResponse
|
||||||
|
for _, review := range reviews {
|
||||||
|
reviewsList = append(reviewsList, *review)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ebookRatingsResponse.EbookRatingSummaryResponse{
|
||||||
|
EbookId: ebookId,
|
||||||
|
EbookTitle: ebookTitle,
|
||||||
|
AverageRating: averageRating,
|
||||||
|
TotalRatings: totalRatings,
|
||||||
|
RatingCounts: ratingCounts,
|
||||||
|
RecentReviews: reviewsList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToEbookRatingStatsResponse(totalRatings int, averageRating float64, ratingCounts map[int]int) *ebookRatingsResponse.EbookRatingStatsResponse {
|
||||||
|
return &ebookRatingsResponse.EbookRatingStatsResponse{
|
||||||
|
TotalRatings: totalRatings,
|
||||||
|
AverageRating: averageRating,
|
||||||
|
RatingCounts: ratingCounts,
|
||||||
|
FiveStarCount: ratingCounts[5],
|
||||||
|
FourStarCount: ratingCounts[4],
|
||||||
|
ThreeStarCount: ratingCounts[3],
|
||||||
|
TwoStarCount: ratingCounts[2],
|
||||||
|
OneStarCount: ratingCounts[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function
|
||||||
|
func stringPtr(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
package mapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
ebooksResponse "narasi-ahli-be/app/module/ebooks/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToEbooksResponse(ebook *entity.Ebooks) *ebooksResponse.EbooksResponse {
|
||||||
|
if ebook == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var authorName, authorEmail *string
|
||||||
|
if ebook.Author != nil {
|
||||||
|
authorName = &ebook.Author.Fullname
|
||||||
|
authorEmail = &ebook.Author.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ebooksResponse.EbooksResponse{
|
||||||
|
ID: ebook.ID,
|
||||||
|
Title: ebook.Title,
|
||||||
|
Slug: ebook.Slug,
|
||||||
|
Description: ebook.Description,
|
||||||
|
Price: ebook.Price,
|
||||||
|
PdfFilePath: ebook.PdfFilePath,
|
||||||
|
PdfFileName: ebook.PdfFileName,
|
||||||
|
PdfFileSize: ebook.PdfFileSize,
|
||||||
|
ThumbnailPath: ebook.ThumbnailPath,
|
||||||
|
ThumbnailName: ebook.ThumbnailName,
|
||||||
|
AuthorId: ebook.AuthorId,
|
||||||
|
AuthorName: authorName,
|
||||||
|
AuthorEmail: authorEmail,
|
||||||
|
Category: ebook.Category,
|
||||||
|
Tags: ebook.Tags,
|
||||||
|
PageCount: ebook.PageCount,
|
||||||
|
Language: ebook.Language,
|
||||||
|
Isbn: ebook.Isbn,
|
||||||
|
Publisher: ebook.Publisher,
|
||||||
|
PublishedYear: ebook.PublishedYear,
|
||||||
|
DownloadCount: ebook.DownloadCount,
|
||||||
|
PurchaseCount: ebook.PurchaseCount,
|
||||||
|
WishlistCount: ebook.WishlistCount,
|
||||||
|
Rating: ebook.Rating,
|
||||||
|
ReviewCount: ebook.ReviewCount,
|
||||||
|
StatusId: ebook.StatusId,
|
||||||
|
IsActive: ebook.IsActive,
|
||||||
|
IsPublished: ebook.IsPublished,
|
||||||
|
PublishedAt: ebook.PublishedAt,
|
||||||
|
CreatedById: ebook.CreatedById,
|
||||||
|
CreatedAt: ebook.CreatedAt,
|
||||||
|
UpdatedAt: ebook.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToEbooksResponseList(ebooks []*entity.Ebooks) []*ebooksResponse.EbooksResponse {
|
||||||
|
if ebooks == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var responses []*ebooksResponse.EbooksResponse
|
||||||
|
for _, ebook := range ebooks {
|
||||||
|
responses = append(responses, ToEbooksResponse(ebook))
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToEbookWishlistResponse(wishlist *entity.EbookWishlists) *ebooksResponse.EbookWishlistResponse {
|
||||||
|
if wishlist == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ebooksResponse.EbookWishlistResponse{
|
||||||
|
ID: wishlist.ID,
|
||||||
|
UserId: wishlist.UserId,
|
||||||
|
EbookId: wishlist.EbookId,
|
||||||
|
Ebook: ToEbooksResponse(wishlist.Ebook),
|
||||||
|
CreatedAt: wishlist.CreatedAt,
|
||||||
|
UpdatedAt: wishlist.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToEbookWishlistResponseList(wishlists []*entity.EbookWishlists) []*ebooksResponse.EbookWishlistResponse {
|
||||||
|
if wishlists == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var responses []*ebooksResponse.EbookWishlistResponse
|
||||||
|
for _, wishlist := range wishlists {
|
||||||
|
responses = append(responses, ToEbookWishlistResponse(wishlist))
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToEbookPurchaseResponse(purchase *entity.EbookPurchases) *ebooksResponse.EbookPurchaseResponse {
|
||||||
|
if purchase == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var buyerName, buyerEmail *string
|
||||||
|
if purchase.Buyer != nil {
|
||||||
|
buyerName = &purchase.Buyer.Fullname
|
||||||
|
buyerEmail = &purchase.Buyer.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ebooksResponse.EbookPurchaseResponse{
|
||||||
|
ID: purchase.ID,
|
||||||
|
BuyerId: purchase.BuyerId,
|
||||||
|
BuyerName: buyerName,
|
||||||
|
BuyerEmail: buyerEmail,
|
||||||
|
EbookId: purchase.EbookId,
|
||||||
|
Ebook: ToEbooksResponse(purchase.Ebook),
|
||||||
|
PurchasePrice: purchase.PurchasePrice,
|
||||||
|
PaymentMethod: purchase.PaymentMethod,
|
||||||
|
PaymentStatus: purchase.PaymentStatus,
|
||||||
|
TransactionId: purchase.TransactionId,
|
||||||
|
PaymentProof: purchase.PaymentProof,
|
||||||
|
PaymentDate: purchase.PaymentDate,
|
||||||
|
DownloadCount: purchase.DownloadCount,
|
||||||
|
LastDownloadAt: purchase.LastDownloadAt,
|
||||||
|
StatusId: purchase.StatusId,
|
||||||
|
IsActive: purchase.IsActive,
|
||||||
|
CreatedAt: purchase.CreatedAt,
|
||||||
|
UpdatedAt: purchase.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToEbookPurchaseResponseList(purchases []*entity.EbookPurchases) []*ebooksResponse.EbookPurchaseResponse {
|
||||||
|
if purchases == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var responses []*ebooksResponse.EbookPurchaseResponse
|
||||||
|
for _, purchase := range purchases {
|
||||||
|
responses = append(responses, ToEbookPurchaseResponse(purchase))
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ebookPurchasesRepository struct {
|
||||||
|
DB *database.Database
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// EbookPurchasesRepository define interface of IEbookPurchasesRepository
|
||||||
|
type EbookPurchasesRepository interface {
|
||||||
|
GetByBuyerId(buyerId uint, pagination *paginator.Pagination) (purchases []*entity.EbookPurchases, paging paginator.Pagination, err error)
|
||||||
|
FindByBuyerAndEbook(buyerId uint, ebookId uint) (purchase *entity.EbookPurchases, err error)
|
||||||
|
FindOne(id uint) (purchase *entity.EbookPurchases, err error)
|
||||||
|
Create(purchase *entity.EbookPurchases) (purchaseReturn *entity.EbookPurchases, err error)
|
||||||
|
Update(id uint, purchase *entity.EbookPurchases) (err error)
|
||||||
|
UpdateDownloadCount(id uint) (err error)
|
||||||
|
Delete(id uint) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEbookPurchasesRepository(db *database.Database, log zerolog.Logger) EbookPurchasesRepository {
|
||||||
|
return &ebookPurchasesRepository{
|
||||||
|
DB: db,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookPurchasesRepository) GetByBuyerId(buyerId uint, pagination *paginator.Pagination) (purchases []*entity.EbookPurchases, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.EbookPurchases{}).
|
||||||
|
Preload("Ebook").
|
||||||
|
Preload("Ebook.Author").
|
||||||
|
Preload("Buyer").
|
||||||
|
Where("buyer_id = ?", buyerId)
|
||||||
|
|
||||||
|
query.Count(&count)
|
||||||
|
|
||||||
|
pagination.Count = count
|
||||||
|
pagination = paginator.Paging(pagination)
|
||||||
|
|
||||||
|
err = query.Offset(pagination.Offset).Limit(pagination.Limit).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Find(&purchases).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paging = *pagination
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookPurchasesRepository) FindByBuyerAndEbook(buyerId uint, ebookId uint) (purchase *entity.EbookPurchases, err error) {
|
||||||
|
query := _i.DB.DB.Where("buyer_id = ? AND ebook_id = ?", buyerId, ebookId)
|
||||||
|
|
||||||
|
if err := query.First(&purchase).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return purchase, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookPurchasesRepository) FindOne(id uint) (purchase *entity.EbookPurchases, err error) {
|
||||||
|
query := _i.DB.DB.Preload("Ebook").Preload("Buyer")
|
||||||
|
if err := query.First(&purchase, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return purchase, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookPurchasesRepository) Create(purchase *entity.EbookPurchases) (purchaseReturn *entity.EbookPurchases, err error) {
|
||||||
|
result := _i.DB.DB.Create(purchase)
|
||||||
|
return purchase, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookPurchasesRepository) Update(id uint, purchase *entity.EbookPurchases) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.EbookPurchases{}).
|
||||||
|
Where(&entity.EbookPurchases{ID: id}).
|
||||||
|
Updates(purchase).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookPurchasesRepository) UpdateDownloadCount(id uint) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.EbookPurchases{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("download_count", "download_count + 1").Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookPurchasesRepository) Delete(id uint) error {
|
||||||
|
return _i.DB.DB.Delete(&entity.EbookPurchases{}, id).Error
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,243 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/ebooks/request"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ebookRatingsRepository struct {
|
||||||
|
DB *database.Database
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// EbookRatingsRepository define interface of IEbookRatingsRepository
|
||||||
|
type EbookRatingsRepository interface {
|
||||||
|
GetAll(req request.EbookRatingsQueryRequest) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error)
|
||||||
|
GetByEbookId(ebookId uint, pagination *paginator.Pagination) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error)
|
||||||
|
GetByUserId(userId uint, pagination *paginator.Pagination) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error)
|
||||||
|
FindOne(id uint) (rating *entity.EbookRatings, err error)
|
||||||
|
FindByUserAndEbook(userId uint, ebookId uint) (rating *entity.EbookRatings, err error)
|
||||||
|
FindByPurchaseId(purchaseId uint) (rating *entity.EbookRatings, err error)
|
||||||
|
Create(rating *entity.EbookRatings) (ratingReturn *entity.EbookRatings, err error)
|
||||||
|
Update(id uint, rating *entity.EbookRatings) (err error)
|
||||||
|
Delete(id uint) (err error)
|
||||||
|
GetEbookRatingStats(ebookId uint) (totalRatings int, averageRating float64, ratingCounts map[int]int, err error)
|
||||||
|
GetRecentReviews(ebookId uint, limit int) (reviews []*entity.EbookRatings, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEbookRatingsRepository(db *database.Database, log zerolog.Logger) EbookRatingsRepository {
|
||||||
|
return &ebookRatingsRepository{
|
||||||
|
DB: db,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement interface of IEbookRatingsRepository
|
||||||
|
func (_i *ebookRatingsRepository) GetAll(req request.EbookRatingsQueryRequest) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.EbookRatings{}).
|
||||||
|
Preload("User").
|
||||||
|
Preload("Ebook").
|
||||||
|
Preload("Purchase").
|
||||||
|
Where("ebook_ratings.is_active = ?", true)
|
||||||
|
|
||||||
|
if req.EbookId != nil {
|
||||||
|
query = query.Where("ebook_ratings.ebook_id = ?", req.EbookId)
|
||||||
|
}
|
||||||
|
if req.UserId != nil {
|
||||||
|
query = query.Where("ebook_ratings.user_id = ?", req.UserId)
|
||||||
|
}
|
||||||
|
if req.Rating != nil {
|
||||||
|
query = query.Where("ebook_ratings.rating = ?", req.Rating)
|
||||||
|
}
|
||||||
|
if req.IsVerified != nil {
|
||||||
|
query = query.Where("ebook_ratings.is_verified = ?", req.IsVerified)
|
||||||
|
}
|
||||||
|
if req.StatusId != nil {
|
||||||
|
query = query.Where("ebook_ratings.status_id = ?", req.StatusId)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Count(&count)
|
||||||
|
|
||||||
|
if req.Pagination.SortBy != "" {
|
||||||
|
direction := "ASC"
|
||||||
|
if req.Pagination.Sort == "desc" {
|
||||||
|
direction = "DESC"
|
||||||
|
}
|
||||||
|
query.Order("ebook_ratings." + req.Pagination.SortBy + " " + direction)
|
||||||
|
} else {
|
||||||
|
query.Order("ebook_ratings.created_at DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Pagination.Count = count
|
||||||
|
req.Pagination = paginator.Paging(req.Pagination)
|
||||||
|
|
||||||
|
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&ratings).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paging = *req.Pagination
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookRatingsRepository) GetByEbookId(ebookId uint, pagination *paginator.Pagination) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.EbookRatings{}).
|
||||||
|
Preload("User").
|
||||||
|
Preload("Ebook").
|
||||||
|
Preload("Purchase").
|
||||||
|
Where("ebook_ratings.ebook_id = ? AND ebook_ratings.is_active = ?", ebookId, true)
|
||||||
|
|
||||||
|
query.Count(&count)
|
||||||
|
|
||||||
|
pagination.Count = count
|
||||||
|
pagination = paginator.Paging(pagination)
|
||||||
|
|
||||||
|
err = query.Offset(pagination.Offset).Limit(pagination.Limit).
|
||||||
|
Order("ebook_ratings.created_at DESC").
|
||||||
|
Find(&ratings).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paging = *pagination
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookRatingsRepository) GetByUserId(userId uint, pagination *paginator.Pagination) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.EbookRatings{}).
|
||||||
|
Preload("User").
|
||||||
|
Preload("Ebook").
|
||||||
|
Preload("Purchase").
|
||||||
|
Where("ebook_ratings.user_id = ? AND ebook_ratings.is_active = ?", userId, true)
|
||||||
|
|
||||||
|
query.Count(&count)
|
||||||
|
|
||||||
|
pagination.Count = count
|
||||||
|
pagination = paginator.Paging(pagination)
|
||||||
|
|
||||||
|
err = query.Offset(pagination.Offset).Limit(pagination.Limit).
|
||||||
|
Order("ebook_ratings.created_at DESC").
|
||||||
|
Find(&ratings).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paging = *pagination
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookRatingsRepository) FindOne(id uint) (rating *entity.EbookRatings, err error) {
|
||||||
|
query := _i.DB.DB.Preload("User").Preload("Ebook").Preload("Purchase")
|
||||||
|
if err := query.First(&rating, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rating, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookRatingsRepository) FindByUserAndEbook(userId uint, ebookId uint) (rating *entity.EbookRatings, err error) {
|
||||||
|
query := _i.DB.DB.Where("user_id = ? AND ebook_id = ?", userId, ebookId)
|
||||||
|
|
||||||
|
if err := query.First(&rating).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rating, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookRatingsRepository) FindByPurchaseId(purchaseId uint) (rating *entity.EbookRatings, err error) {
|
||||||
|
query := _i.DB.DB.Where("purchase_id = ?", purchaseId)
|
||||||
|
|
||||||
|
if err := query.First(&rating).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rating, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookRatingsRepository) Create(rating *entity.EbookRatings) (ratingReturn *entity.EbookRatings, err error) {
|
||||||
|
result := _i.DB.DB.Create(rating)
|
||||||
|
return rating, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookRatingsRepository) Update(id uint, rating *entity.EbookRatings) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.EbookRatings{}).
|
||||||
|
Where(&entity.EbookRatings{ID: id}).
|
||||||
|
Updates(rating).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookRatingsRepository) Delete(id uint) error {
|
||||||
|
return _i.DB.DB.Delete(&entity.EbookRatings{}, id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookRatingsRepository) GetEbookRatingStats(ebookId uint) (totalRatings int, averageRating float64, ratingCounts map[int]int, err error) {
|
||||||
|
var stats struct {
|
||||||
|
TotalRatings int `json:"totalRatings"`
|
||||||
|
AverageRating float64 `json:"averageRating"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get total ratings and average
|
||||||
|
err = _i.DB.DB.Model(&entity.EbookRatings{}).
|
||||||
|
Select("COUNT(*) as total_ratings, COALESCE(AVG(rating), 0) as average_rating").
|
||||||
|
Where("ebook_id = ? AND is_active = ? AND is_verified = ?", ebookId, true, true).
|
||||||
|
Scan(&stats).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get rating counts
|
||||||
|
var ratingCountsData []struct {
|
||||||
|
Rating int `json:"rating"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.DB.DB.Model(&entity.EbookRatings{}).
|
||||||
|
Select("rating, COUNT(*) as count").
|
||||||
|
Where("ebook_id = ? AND is_active = ? AND is_verified = ?", ebookId, true, true).
|
||||||
|
Group("rating").
|
||||||
|
Scan(&ratingCountsData).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to map
|
||||||
|
ratingCounts = make(map[int]int)
|
||||||
|
for _, rc := range ratingCountsData {
|
||||||
|
ratingCounts[rc.Rating] = rc.Count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all ratings 1-5 are present
|
||||||
|
for i := 1; i <= 5; i++ {
|
||||||
|
if _, exists := ratingCounts[i]; !exists {
|
||||||
|
ratingCounts[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats.TotalRatings, stats.AverageRating, ratingCounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookRatingsRepository) GetRecentReviews(ebookId uint, limit int) (reviews []*entity.EbookRatings, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.EbookRatings{}).
|
||||||
|
Preload("User").
|
||||||
|
Preload("Ebook").
|
||||||
|
Preload("Purchase").
|
||||||
|
Where("ebook_id = ? AND is_active = ? AND is_verified = ? AND review IS NOT NULL AND review != ''", ebookId, true, true).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Limit(limit)
|
||||||
|
|
||||||
|
err = query.Find(&reviews).Error
|
||||||
|
return reviews, err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ebookWishlistsRepository struct {
|
||||||
|
DB *database.Database
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// EbookWishlistsRepository define interface of IEbookWishlistsRepository
|
||||||
|
type EbookWishlistsRepository interface {
|
||||||
|
GetByUserId(userId uint, pagination *paginator.Pagination) (wishlists []*entity.EbookWishlists, paging paginator.Pagination, err error)
|
||||||
|
FindByUserAndEbook(userId uint, ebookId uint) (wishlist *entity.EbookWishlists, err error)
|
||||||
|
Create(wishlist *entity.EbookWishlists) (wishlistReturn *entity.EbookWishlists, err error)
|
||||||
|
Delete(id uint) (err error)
|
||||||
|
DeleteByUserAndEbook(userId uint, ebookId uint) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEbookWishlistsRepository(db *database.Database, log zerolog.Logger) EbookWishlistsRepository {
|
||||||
|
return &ebookWishlistsRepository{
|
||||||
|
DB: db,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookWishlistsRepository) GetByUserId(userId uint, pagination *paginator.Pagination) (wishlists []*entity.EbookWishlists, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.EbookWishlists{}).
|
||||||
|
Preload("Ebook").
|
||||||
|
Preload("Ebook.Author").
|
||||||
|
Where("user_id = ?", userId)
|
||||||
|
|
||||||
|
query.Count(&count)
|
||||||
|
|
||||||
|
pagination.Count = count
|
||||||
|
pagination = paginator.Paging(pagination)
|
||||||
|
|
||||||
|
err = query.Offset(pagination.Offset).Limit(pagination.Limit).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Find(&wishlists).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paging = *pagination
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookWishlistsRepository) FindByUserAndEbook(userId uint, ebookId uint) (wishlist *entity.EbookWishlists, err error) {
|
||||||
|
query := _i.DB.DB.Where("user_id = ? AND ebook_id = ?", userId, ebookId)
|
||||||
|
|
||||||
|
if err := query.First(&wishlist).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wishlist, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookWishlistsRepository) Create(wishlist *entity.EbookWishlists) (wishlistReturn *entity.EbookWishlists, err error) {
|
||||||
|
result := _i.DB.DB.Create(wishlist)
|
||||||
|
return wishlist, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookWishlistsRepository) Delete(id uint) error {
|
||||||
|
return _i.DB.DB.Delete(&entity.EbookWishlists{}, id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebookWishlistsRepository) DeleteByUserAndEbook(userId uint, ebookId uint) error {
|
||||||
|
return _i.DB.DB.Where("user_id = ? AND ebook_id = ?", userId, ebookId).
|
||||||
|
Delete(&entity.EbookWishlists{}).Error
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"narasi-ahli-be/app/database"
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/app/module/ebooks/request"
|
||||||
|
"narasi-ahli-be/app/module/ebooks/response"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilSvc "narasi-ahli-be/utils/service"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ebooksRepository struct {
|
||||||
|
DB *database.Database
|
||||||
|
Log zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// EbooksRepository define interface of IEbooksRepository
|
||||||
|
type EbooksRepository interface {
|
||||||
|
GetAll(req request.EbooksQueryRequest) (ebooks []*entity.Ebooks, paging paginator.Pagination, err error)
|
||||||
|
FindOne(id uint) (ebook *entity.Ebooks, err error)
|
||||||
|
FindBySlug(slug string) (ebook *entity.Ebooks, err error)
|
||||||
|
Create(ebook *entity.Ebooks) (ebookReturn *entity.Ebooks, err error)
|
||||||
|
Update(id uint, ebook *entity.Ebooks) (err error)
|
||||||
|
UpdateSkipNull(id uint, ebook *entity.Ebooks) (err error)
|
||||||
|
Delete(id uint) (err error)
|
||||||
|
UpdateDownloadCount(id uint) (err error)
|
||||||
|
UpdatePurchaseCount(id uint) (err error)
|
||||||
|
UpdateWishlistCount(id uint) (err error)
|
||||||
|
SummaryStats(authorId uint) (ebookSummaryStats *response.EbookSummaryStats, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEbooksRepository(db *database.Database, log zerolog.Logger) EbooksRepository {
|
||||||
|
return &ebooksRepository{
|
||||||
|
DB: db,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement interface of IEbooksRepository
|
||||||
|
func (_i *ebooksRepository) GetAll(req request.EbooksQueryRequest) (ebooks []*entity.Ebooks, paging paginator.Pagination, err error) {
|
||||||
|
var count int64
|
||||||
|
|
||||||
|
query := _i.DB.DB.Model(&entity.Ebooks{}).
|
||||||
|
Preload("Author").
|
||||||
|
Where("ebooks.is_active = ?", true)
|
||||||
|
|
||||||
|
if req.Title != nil && *req.Title != "" {
|
||||||
|
title := strings.ToLower(*req.Title)
|
||||||
|
query = query.Where("LOWER(ebooks.title) LIKE ?", "%"+strings.ToLower(title)+"%")
|
||||||
|
}
|
||||||
|
if req.Description != nil && *req.Description != "" {
|
||||||
|
description := strings.ToLower(*req.Description)
|
||||||
|
query = query.Where("LOWER(ebooks.description) LIKE ?", "%"+strings.ToLower(description)+"%")
|
||||||
|
}
|
||||||
|
if req.AuthorId != nil {
|
||||||
|
query = query.Where("ebooks.author_id = ?", req.AuthorId)
|
||||||
|
}
|
||||||
|
if req.Category != nil && *req.Category != "" {
|
||||||
|
category := strings.ToLower(*req.Category)
|
||||||
|
query = query.Where("LOWER(ebooks.category) LIKE ?", "%"+strings.ToLower(category)+"%")
|
||||||
|
}
|
||||||
|
if req.Tags != nil && *req.Tags != "" {
|
||||||
|
tags := strings.ToLower(*req.Tags)
|
||||||
|
query = query.Where("LOWER(ebooks.tags) LIKE ?", "%"+strings.ToLower(tags)+"%")
|
||||||
|
}
|
||||||
|
if req.MinPrice != nil {
|
||||||
|
query = query.Where("ebooks.price >= ?", req.MinPrice)
|
||||||
|
}
|
||||||
|
if req.MaxPrice != nil {
|
||||||
|
query = query.Where("ebooks.price <= ?", req.MaxPrice)
|
||||||
|
}
|
||||||
|
if req.IsPublished != nil {
|
||||||
|
query = query.Where("ebooks.is_published = ?", req.IsPublished)
|
||||||
|
}
|
||||||
|
if req.StatusId != nil {
|
||||||
|
query = query.Where("ebooks.status_id = ?", req.StatusId)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Count(&count)
|
||||||
|
|
||||||
|
if req.Pagination.SortBy != "" {
|
||||||
|
direction := "ASC"
|
||||||
|
if req.Pagination.Sort == "desc" {
|
||||||
|
direction = "DESC"
|
||||||
|
}
|
||||||
|
query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
|
||||||
|
} else {
|
||||||
|
direction := "DESC"
|
||||||
|
sortBy := "ebooks.created_at"
|
||||||
|
query.Order(fmt.Sprintf("%s %s", sortBy, direction))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Pagination.Count = count
|
||||||
|
req.Pagination = paginator.Paging(req.Pagination)
|
||||||
|
|
||||||
|
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&ebooks).Error
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paging = *req.Pagination
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebooksRepository) FindOne(id uint) (ebook *entity.Ebooks, err error) {
|
||||||
|
query := _i.DB.DB.Preload("Author")
|
||||||
|
if err := query.First(&ebook, id).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ebook, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebooksRepository) FindBySlug(slug string) (ebook *entity.Ebooks, err error) {
|
||||||
|
query := _i.DB.DB.Preload("Author").Where("slug = ?", slug)
|
||||||
|
|
||||||
|
if err := query.First(&ebook).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ebook, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebooksRepository) Create(ebook *entity.Ebooks) (ebookReturn *entity.Ebooks, err error) {
|
||||||
|
result := _i.DB.DB.Create(ebook)
|
||||||
|
return ebook, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebooksRepository) Update(id uint, ebook *entity.Ebooks) (err error) {
|
||||||
|
ebookMap, err := utilSvc.StructToMap(ebook)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return _i.DB.DB.Model(&entity.Ebooks{}).
|
||||||
|
Where(&entity.Ebooks{ID: id}).
|
||||||
|
Updates(ebookMap).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebooksRepository) UpdateSkipNull(id uint, ebook *entity.Ebooks) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.Ebooks{}).
|
||||||
|
Where(&entity.Ebooks{ID: id}).
|
||||||
|
Updates(ebook).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebooksRepository) Delete(id uint) error {
|
||||||
|
return _i.DB.DB.Delete(&entity.Ebooks{}, id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebooksRepository) UpdateDownloadCount(id uint) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.Ebooks{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("download_count", "download_count + 1").Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebooksRepository) UpdatePurchaseCount(id uint) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.Ebooks{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("purchase_count", "purchase_count + 1").Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebooksRepository) UpdateWishlistCount(id uint) (err error) {
|
||||||
|
return _i.DB.DB.Model(&entity.Ebooks{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("wishlist_count", "wishlist_count + 1").Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *ebooksRepository) SummaryStats(authorId uint) (ebookSummaryStats *response.EbookSummaryStats, err error) {
|
||||||
|
query := _i.DB.DB.Model(&entity.Ebooks{}).
|
||||||
|
Select(
|
||||||
|
"COUNT(*) AS total_ebooks, "+
|
||||||
|
"COALESCE(SUM(purchase_count), 0) AS total_sales, "+
|
||||||
|
"COALESCE(SUM(price * purchase_count), 0) AS total_revenue, "+
|
||||||
|
"COALESCE(SUM(download_count), 0) AS total_downloads, "+
|
||||||
|
"COALESCE(SUM(wishlist_count), 0) AS total_wishlists, "+
|
||||||
|
"COALESCE(AVG(rating), 0) AS average_rating").
|
||||||
|
Where("author_id = ?", authorId)
|
||||||
|
|
||||||
|
err = query.Scan(&ebookSummaryStats).Error
|
||||||
|
|
||||||
|
return ebookSummaryStats, err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EbookRatingsGeneric interface {
|
||||||
|
ToEntity()
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookRatingsQueryRequest struct {
|
||||||
|
EbookId *uint `json:"ebookId"`
|
||||||
|
UserId *uint `json:"userId"`
|
||||||
|
Rating *int `json:"rating"`
|
||||||
|
IsVerified *bool `json:"isVerified"`
|
||||||
|
StatusId *int `json:"statusId"`
|
||||||
|
Pagination *paginator.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookRatingsCreateRequest struct {
|
||||||
|
EbookId uint `json:"ebookId" validate:"required"`
|
||||||
|
PurchaseId uint `json:"purchaseId" validate:"required"`
|
||||||
|
Rating int `json:"rating" validate:"required,min=1,max=5"`
|
||||||
|
Review *string `json:"review"`
|
||||||
|
IsAnonymous *bool `json:"isAnonymous"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req EbookRatingsCreateRequest) ToEntity() *entity.EbookRatings {
|
||||||
|
return &entity.EbookRatings{
|
||||||
|
EbookId: req.EbookId,
|
||||||
|
PurchaseId: req.PurchaseId,
|
||||||
|
Rating: req.Rating,
|
||||||
|
Review: req.Review,
|
||||||
|
IsAnonymous: req.IsAnonymous,
|
||||||
|
IsVerified: boolPtr(true),
|
||||||
|
StatusId: intPtr(1),
|
||||||
|
IsActive: boolPtr(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookRatingsUpdateRequest struct {
|
||||||
|
Rating int `json:"rating" validate:"required,min=1,max=5"`
|
||||||
|
Review *string `json:"review"`
|
||||||
|
IsAnonymous *bool `json:"isAnonymous"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req EbookRatingsUpdateRequest) ToEntity() *entity.EbookRatings {
|
||||||
|
return &entity.EbookRatings{
|
||||||
|
Rating: req.Rating,
|
||||||
|
Review: req.Review,
|
||||||
|
IsAnonymous: req.IsAnonymous,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookRatingsQueryRequestContext struct {
|
||||||
|
EbookId string `json:"ebookId"`
|
||||||
|
UserId string `json:"userId"`
|
||||||
|
Rating string `json:"rating"`
|
||||||
|
IsVerified string `json:"isVerified"`
|
||||||
|
StatusId string `json:"statusId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req EbookRatingsQueryRequestContext) ToParamRequest() EbookRatingsQueryRequest {
|
||||||
|
var request EbookRatingsQueryRequest
|
||||||
|
|
||||||
|
if ebookIdStr := req.EbookId; ebookIdStr != "" {
|
||||||
|
ebookId, err := strconv.Atoi(ebookIdStr)
|
||||||
|
if err == nil {
|
||||||
|
ebookIdUint := uint(ebookId)
|
||||||
|
request.EbookId = &ebookIdUint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if userIdStr := req.UserId; userIdStr != "" {
|
||||||
|
userId, err := strconv.Atoi(userIdStr)
|
||||||
|
if err == nil {
|
||||||
|
userIdUint := uint(userId)
|
||||||
|
request.UserId = &userIdUint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ratingStr := req.Rating; ratingStr != "" {
|
||||||
|
rating, err := strconv.Atoi(ratingStr)
|
||||||
|
if err == nil {
|
||||||
|
request.Rating = &rating
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isVerifiedStr := req.IsVerified; isVerifiedStr != "" {
|
||||||
|
isVerified, err := strconv.ParseBool(isVerifiedStr)
|
||||||
|
if err == nil {
|
||||||
|
request.IsVerified = &isVerified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if statusIdStr := req.StatusId; statusIdStr != "" {
|
||||||
|
statusId, err := strconv.Atoi(statusIdStr)
|
||||||
|
if err == nil {
|
||||||
|
request.StatusId = &statusId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
func boolPtr(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
func intPtr(i int) *int {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
package request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"narasi-ahli-be/app/database/entity"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EbooksGeneric interface {
|
||||||
|
ToEntity()
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbooksQueryRequest struct {
|
||||||
|
Title *string `json:"title"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
AuthorId *uint `json:"authorId"`
|
||||||
|
Category *string `json:"category"`
|
||||||
|
Tags *string `json:"tags"`
|
||||||
|
MinPrice *float64 `json:"minPrice"`
|
||||||
|
MaxPrice *float64 `json:"maxPrice"`
|
||||||
|
StatusId *int `json:"statusId"`
|
||||||
|
IsPublished *bool `json:"isPublished"`
|
||||||
|
Pagination *paginator.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbooksCreateRequest struct {
|
||||||
|
Title string `json:"title" validate:"required"`
|
||||||
|
Slug string `json:"slug" validate:"required"`
|
||||||
|
Description string `json:"description" validate:"required"`
|
||||||
|
Price float64 `json:"price" validate:"required,min=0"`
|
||||||
|
Category *string `json:"category"`
|
||||||
|
Tags *string `json:"tags"`
|
||||||
|
PageCount *int `json:"pageCount"`
|
||||||
|
Language *string `json:"language"`
|
||||||
|
Isbn *string `json:"isbn"`
|
||||||
|
Publisher *string `json:"publisher"`
|
||||||
|
PublishedYear *int `json:"publishedYear"`
|
||||||
|
IsPublished *bool `json:"isPublished"`
|
||||||
|
CreatedAt *string `json:"createdAt"`
|
||||||
|
CreatedById *uint `json:"createdById"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req EbooksCreateRequest) ToEntity() *entity.Ebooks {
|
||||||
|
return &entity.Ebooks{
|
||||||
|
Title: req.Title,
|
||||||
|
Slug: req.Slug,
|
||||||
|
Description: req.Description,
|
||||||
|
Price: req.Price,
|
||||||
|
Category: req.Category,
|
||||||
|
Tags: req.Tags,
|
||||||
|
PageCount: req.PageCount,
|
||||||
|
Language: req.Language,
|
||||||
|
Isbn: req.Isbn,
|
||||||
|
Publisher: req.Publisher,
|
||||||
|
PublishedYear: req.PublishedYear,
|
||||||
|
IsPublished: req.IsPublished,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbooksUpdateRequest struct {
|
||||||
|
Title string `json:"title" validate:"required"`
|
||||||
|
Slug string `json:"slug" validate:"required"`
|
||||||
|
Description string `json:"description" validate:"required"`
|
||||||
|
Price float64 `json:"price" validate:"required,min=0"`
|
||||||
|
Category *string `json:"category"`
|
||||||
|
Tags *string `json:"tags"`
|
||||||
|
PageCount *int `json:"pageCount"`
|
||||||
|
Language *string `json:"language"`
|
||||||
|
Isbn *string `json:"isbn"`
|
||||||
|
Publisher *string `json:"publisher"`
|
||||||
|
PublishedYear *int `json:"publishedYear"`
|
||||||
|
IsPublished *bool `json:"isPublished"`
|
||||||
|
StatusId *int `json:"statusId"`
|
||||||
|
CreatedAt *string `json:"createdAt"`
|
||||||
|
CreatedById *uint `json:"createdById"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req EbooksUpdateRequest) ToEntity() *entity.Ebooks {
|
||||||
|
if req.CreatedById == nil {
|
||||||
|
return &entity.Ebooks{
|
||||||
|
Title: req.Title,
|
||||||
|
Slug: req.Slug,
|
||||||
|
Description: req.Description,
|
||||||
|
Price: req.Price,
|
||||||
|
Category: req.Category,
|
||||||
|
Tags: req.Tags,
|
||||||
|
PageCount: req.PageCount,
|
||||||
|
Language: req.Language,
|
||||||
|
Isbn: req.Isbn,
|
||||||
|
Publisher: req.Publisher,
|
||||||
|
PublishedYear: req.PublishedYear,
|
||||||
|
IsPublished: req.IsPublished,
|
||||||
|
StatusId: req.StatusId,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return &entity.Ebooks{
|
||||||
|
Title: req.Title,
|
||||||
|
Slug: req.Slug,
|
||||||
|
Description: req.Description,
|
||||||
|
Price: req.Price,
|
||||||
|
Category: req.Category,
|
||||||
|
Tags: req.Tags,
|
||||||
|
PageCount: req.PageCount,
|
||||||
|
Language: req.Language,
|
||||||
|
Isbn: req.Isbn,
|
||||||
|
Publisher: req.Publisher,
|
||||||
|
PublishedYear: req.PublishedYear,
|
||||||
|
IsPublished: req.IsPublished,
|
||||||
|
StatusId: req.StatusId,
|
||||||
|
CreatedById: req.CreatedById,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbooksQueryRequestContext struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
AuthorId string `json:"authorId"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Tags string `json:"tags"`
|
||||||
|
MinPrice string `json:"minPrice"`
|
||||||
|
MaxPrice string `json:"maxPrice"`
|
||||||
|
StatusId string `json:"statusId"`
|
||||||
|
IsPublished string `json:"isPublished"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req EbooksQueryRequestContext) ToParamRequest() EbooksQueryRequest {
|
||||||
|
var request EbooksQueryRequest
|
||||||
|
|
||||||
|
if title := req.Title; title != "" {
|
||||||
|
request.Title = &title
|
||||||
|
}
|
||||||
|
if description := req.Description; description != "" {
|
||||||
|
request.Description = &description
|
||||||
|
}
|
||||||
|
if category := req.Category; category != "" {
|
||||||
|
request.Category = &category
|
||||||
|
}
|
||||||
|
if tags := req.Tags; tags != "" {
|
||||||
|
request.Tags = &tags
|
||||||
|
}
|
||||||
|
if authorIdStr := req.AuthorId; authorIdStr != "" {
|
||||||
|
authorId, err := strconv.Atoi(authorIdStr)
|
||||||
|
if err == nil {
|
||||||
|
authorIdUint := uint(authorId)
|
||||||
|
request.AuthorId = &authorIdUint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if minPriceStr := req.MinPrice; minPriceStr != "" {
|
||||||
|
minPrice, err := strconv.ParseFloat(minPriceStr, 64)
|
||||||
|
if err == nil {
|
||||||
|
request.MinPrice = &minPrice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxPriceStr := req.MaxPrice; maxPriceStr != "" {
|
||||||
|
maxPrice, err := strconv.ParseFloat(maxPriceStr, 64)
|
||||||
|
if err == nil {
|
||||||
|
request.MaxPrice = &maxPrice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isPublishedStr := req.IsPublished; isPublishedStr != "" {
|
||||||
|
isPublished, err := strconv.ParseBool(isPublishedStr)
|
||||||
|
if err == nil {
|
||||||
|
request.IsPublished = &isPublished
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if statusIdStr := req.StatusId; statusIdStr != "" {
|
||||||
|
statusId, err := strconv.Atoi(statusIdStr)
|
||||||
|
if err == nil {
|
||||||
|
request.StatusId = &statusId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EbookRatingsResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
UserId uint `json:"userId"`
|
||||||
|
UserName *string `json:"userName"`
|
||||||
|
UserEmail *string `json:"userEmail"`
|
||||||
|
EbookId uint `json:"ebookId"`
|
||||||
|
EbookTitle *string `json:"ebookTitle"`
|
||||||
|
PurchaseId uint `json:"purchaseId"`
|
||||||
|
Rating int `json:"rating"`
|
||||||
|
Review *string `json:"review"`
|
||||||
|
IsAnonymous *bool `json:"isAnonymous"`
|
||||||
|
IsVerified *bool `json:"isVerified"`
|
||||||
|
StatusId *int `json:"statusId"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookRatingSummaryResponse struct {
|
||||||
|
EbookId uint `json:"ebookId"`
|
||||||
|
EbookTitle string `json:"ebookTitle"`
|
||||||
|
AverageRating float64 `json:"averageRating"`
|
||||||
|
TotalRatings int `json:"totalRatings"`
|
||||||
|
RatingCounts map[int]int `json:"ratingCounts"`
|
||||||
|
RecentReviews []EbookRatingsResponse `json:"recentReviews"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookRatingStatsResponse struct {
|
||||||
|
TotalRatings int `json:"totalRatings"`
|
||||||
|
AverageRating float64 `json:"averageRating"`
|
||||||
|
RatingCounts map[int]int `json:"ratingCounts"`
|
||||||
|
FiveStarCount int `json:"fiveStarCount"`
|
||||||
|
FourStarCount int `json:"fourStarCount"`
|
||||||
|
ThreeStarCount int `json:"threeStarCount"`
|
||||||
|
TwoStarCount int `json:"twoStarCount"`
|
||||||
|
OneStarCount int `json:"oneStarCount"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package response
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EbooksResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
PdfFilePath *string `json:"pdfFilePath"`
|
||||||
|
PdfFileName *string `json:"pdfFileName"`
|
||||||
|
PdfFileSize *int64 `json:"pdfFileSize"`
|
||||||
|
ThumbnailPath *string `json:"thumbnailPath"`
|
||||||
|
ThumbnailName *string `json:"thumbnailName"`
|
||||||
|
AuthorId uint `json:"authorId"`
|
||||||
|
AuthorName *string `json:"authorName"`
|
||||||
|
AuthorEmail *string `json:"authorEmail"`
|
||||||
|
Category *string `json:"category"`
|
||||||
|
Tags *string `json:"tags"`
|
||||||
|
PageCount *int `json:"pageCount"`
|
||||||
|
Language *string `json:"language"`
|
||||||
|
Isbn *string `json:"isbn"`
|
||||||
|
Publisher *string `json:"publisher"`
|
||||||
|
PublishedYear *int `json:"publishedYear"`
|
||||||
|
DownloadCount *int `json:"downloadCount"`
|
||||||
|
PurchaseCount *int `json:"purchaseCount"`
|
||||||
|
WishlistCount *int `json:"wishlistCount"`
|
||||||
|
Rating *float64 `json:"rating"`
|
||||||
|
ReviewCount *int `json:"reviewCount"`
|
||||||
|
StatusId *int `json:"statusId"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
IsPublished *bool `json:"isPublished"`
|
||||||
|
PublishedAt *time.Time `json:"publishedAt"`
|
||||||
|
CreatedById *uint `json:"createdById"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookWishlistResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
UserId uint `json:"userId"`
|
||||||
|
EbookId uint `json:"ebookId"`
|
||||||
|
Ebook *EbooksResponse `json:"ebook"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookPurchaseResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
BuyerId uint `json:"buyerId"`
|
||||||
|
BuyerName *string `json:"buyerName"`
|
||||||
|
BuyerEmail *string `json:"buyerEmail"`
|
||||||
|
EbookId uint `json:"ebookId"`
|
||||||
|
Ebook *EbooksResponse `json:"ebook"`
|
||||||
|
PurchasePrice float64 `json:"purchasePrice"`
|
||||||
|
PaymentMethod *string `json:"paymentMethod"`
|
||||||
|
PaymentStatus *string `json:"paymentStatus"`
|
||||||
|
TransactionId *string `json:"transactionId"`
|
||||||
|
PaymentProof *string `json:"paymentProof"`
|
||||||
|
PaymentDate *time.Time `json:"paymentDate"`
|
||||||
|
DownloadCount *int `json:"downloadCount"`
|
||||||
|
LastDownloadAt *time.Time `json:"lastDownloadAt"`
|
||||||
|
StatusId *int `json:"statusId"`
|
||||||
|
IsActive *bool `json:"isActive"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EbookSummaryStats struct {
|
||||||
|
TotalEbooks int `json:"totalEbooks"`
|
||||||
|
TotalSales int `json:"totalSales"`
|
||||||
|
TotalRevenue float64 `json:"totalRevenue"`
|
||||||
|
TotalDownloads int `json:"totalDownloads"`
|
||||||
|
TotalWishlists int `json:"totalWishlists"`
|
||||||
|
AverageRating float64 `json:"averageRating"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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/response"
|
||||||
|
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilSvc "narasi-ahli-be/utils/service"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EbookPurchasesService
|
||||||
|
type ebookPurchasesService struct {
|
||||||
|
Repo repository.EbookPurchasesRepository
|
||||||
|
EbooksRepo repository.EbooksRepository
|
||||||
|
Log zerolog.Logger
|
||||||
|
UsersRepo usersRepository.UsersRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// EbookPurchasesService define interface of IEbookPurchasesService
|
||||||
|
type EbookPurchasesService interface {
|
||||||
|
GetByBuyerId(authToken string, pagination *paginator.Pagination) (purchases []*response.EbookPurchaseResponse, paging paginator.Pagination, err error)
|
||||||
|
PurchaseEbook(authToken string, ebookId uint, paymentMethod string) (purchase *response.EbookPurchaseResponse, err error)
|
||||||
|
UpdatePaymentStatus(purchaseId uint, paymentStatus string, transactionId string, paymentProof string) (err error)
|
||||||
|
GetPurchaseById(purchaseId uint) (purchase *response.EbookPurchaseResponse, err error)
|
||||||
|
ConfirmPayment(purchaseId uint) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEbookPurchasesService init EbookPurchasesService
|
||||||
|
func NewEbookPurchasesService(
|
||||||
|
repo repository.EbookPurchasesRepository,
|
||||||
|
ebooksRepo repository.EbooksRepository,
|
||||||
|
log zerolog.Logger,
|
||||||
|
usersRepo usersRepository.UsersRepository) EbookPurchasesService {
|
||||||
|
|
||||||
|
return &ebookPurchasesService{
|
||||||
|
Repo: repo,
|
||||||
|
EbooksRepo: ebooksRepo,
|
||||||
|
Log: log,
|
||||||
|
UsersRepo: usersRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByBuyerId implement interface of EbookPurchasesService
|
||||||
|
func (_i *ebookPurchasesService) GetByBuyerId(authToken string, pagination *paginator.Pagination) (purchases []*response.EbookPurchaseResponse, paging paginator.Pagination, err error) {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return nil, paginator.Pagination{}, errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
purchasesData, paging, err := _i.Repo.GetByBuyerId(user.ID, pagination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, paging, err
|
||||||
|
}
|
||||||
|
|
||||||
|
purchases = mapper.ToEbookPurchaseResponseList(purchasesData)
|
||||||
|
|
||||||
|
return purchases, paging, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PurchaseEbook implement interface of EbookPurchasesService
|
||||||
|
func (_i *ebookPurchasesService) PurchaseEbook(authToken string, ebookId uint, paymentMethod string) (purchase *response.EbookPurchaseResponse, err error) {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return nil, errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ebook exists
|
||||||
|
ebook, err := _i.EbooksRepo.FindOne(ebookId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("ebook not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already purchased
|
||||||
|
existingPurchase, err := _i.Repo.FindByBuyerAndEbook(user.ID, ebookId)
|
||||||
|
if err == nil && existingPurchase != nil {
|
||||||
|
return nil, errors.New("ebook already purchased")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create purchase record
|
||||||
|
purchaseEntity := &entity.EbookPurchases{
|
||||||
|
BuyerId: user.ID,
|
||||||
|
EbookId: ebookId,
|
||||||
|
PurchasePrice: ebook.Price,
|
||||||
|
PaymentMethod: &paymentMethod,
|
||||||
|
PaymentStatus: stringPtr("pending"),
|
||||||
|
StatusId: intPtr(1),
|
||||||
|
IsActive: boolPtr(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
purchaseData, err := _i.Repo.Create(purchaseEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update purchase count
|
||||||
|
err = _i.EbooksRepo.UpdatePurchaseCount(ebookId)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Msg("Failed to update purchase count")
|
||||||
|
}
|
||||||
|
|
||||||
|
purchase = mapper.ToEbookPurchaseResponse(purchaseData)
|
||||||
|
|
||||||
|
return purchase, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePaymentStatus implement interface of EbookPurchasesService
|
||||||
|
func (_i *ebookPurchasesService) UpdatePaymentStatus(purchaseId uint, paymentStatus string, transactionId string, paymentProof string) (err error) {
|
||||||
|
_, err = _i.Repo.FindOne(purchaseId)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("purchase not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
updateData := &entity.EbookPurchases{
|
||||||
|
PaymentStatus: &paymentStatus,
|
||||||
|
TransactionId: &transactionId,
|
||||||
|
PaymentProof: &paymentProof,
|
||||||
|
}
|
||||||
|
|
||||||
|
if paymentStatus == "paid" {
|
||||||
|
now := time.Now()
|
||||||
|
updateData.PaymentDate = &now
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.Repo.Update(purchaseId, updateData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPurchaseById implement interface of EbookPurchasesService
|
||||||
|
func (_i *ebookPurchasesService) GetPurchaseById(purchaseId uint) (purchase *response.EbookPurchaseResponse, err error) {
|
||||||
|
purchaseData, err := _i.Repo.FindOne(purchaseId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
purchase = mapper.ToEbookPurchaseResponse(purchaseData)
|
||||||
|
|
||||||
|
return purchase, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfirmPayment implement interface of EbookPurchasesService
|
||||||
|
func (_i *ebookPurchasesService) ConfirmPayment(purchaseId uint) (err error) {
|
||||||
|
purchase, err := _i.Repo.FindOne(purchaseId)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("purchase not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if purchase.PaymentStatus == nil || *purchase.PaymentStatus != "paid" {
|
||||||
|
return errors.New("payment not completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update status to confirmed
|
||||||
|
updateData := &entity.EbookPurchases{
|
||||||
|
StatusId: intPtr(2), // Assuming 2 is confirmed status
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.Repo.Update(purchaseId, updateData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
func stringPtr(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func intPtr(i int) *int {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolPtr(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,280 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilSvc "narasi-ahli-be/utils/service"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EbookRatingsService
|
||||||
|
type ebookRatingsService struct {
|
||||||
|
Repo repository.EbookRatingsRepository
|
||||||
|
EbooksRepo repository.EbooksRepository
|
||||||
|
EbookPurchasesRepo repository.EbookPurchasesRepository
|
||||||
|
Log zerolog.Logger
|
||||||
|
UsersRepo usersRepository.UsersRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// EbookRatingsService define interface of IEbookRatingsService
|
||||||
|
type EbookRatingsService interface {
|
||||||
|
GetAll(req request.EbookRatingsQueryRequest) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error)
|
||||||
|
GetByEbookId(ebookId uint, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error)
|
||||||
|
GetByUserId(authToken string, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error)
|
||||||
|
GetEbookRatingSummary(ebookId uint) (summary *response.EbookRatingSummaryResponse, err error)
|
||||||
|
Create(req request.EbookRatingsCreateRequest, authToken string) (rating *response.EbookRatingsResponse, err error)
|
||||||
|
Update(id uint, req request.EbookRatingsUpdateRequest, authToken string) (err error)
|
||||||
|
Delete(id uint, authToken string) error
|
||||||
|
GetRatingStats(ebookId uint) (stats *response.EbookRatingStatsResponse, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEbookRatingsService init EbookRatingsService
|
||||||
|
func NewEbookRatingsService(
|
||||||
|
repo repository.EbookRatingsRepository,
|
||||||
|
ebooksRepo repository.EbooksRepository,
|
||||||
|
ebookPurchasesRepo repository.EbookPurchasesRepository,
|
||||||
|
log zerolog.Logger,
|
||||||
|
usersRepo usersRepository.UsersRepository) EbookRatingsService {
|
||||||
|
|
||||||
|
return &ebookRatingsService{
|
||||||
|
Repo: repo,
|
||||||
|
EbooksRepo: ebooksRepo,
|
||||||
|
EbookPurchasesRepo: ebookPurchasesRepo,
|
||||||
|
Log: log,
|
||||||
|
UsersRepo: usersRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll implement interface of EbookRatingsService
|
||||||
|
func (_i *ebookRatingsService) GetAll(req request.EbookRatingsQueryRequest) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) {
|
||||||
|
ratingsData, paging, err := _i.Repo.GetAll(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, paging, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ratings = mapper.ToEbookRatingsResponseList(ratingsData)
|
||||||
|
|
||||||
|
return ratings, paging, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByEbookId implement interface of EbookRatingsService
|
||||||
|
func (_i *ebookRatingsService) GetByEbookId(ebookId uint, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) {
|
||||||
|
ratingsData, paging, err := _i.Repo.GetByEbookId(ebookId, pagination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, paging, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ratings = mapper.ToEbookRatingsResponseList(ratingsData)
|
||||||
|
|
||||||
|
return ratings, paging, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByUserId implement interface of EbookRatingsService
|
||||||
|
func (_i *ebookRatingsService) GetByUserId(authToken string, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return nil, paginator.Pagination{}, errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
ratingsData, paging, err := _i.Repo.GetByUserId(user.ID, pagination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, paging, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ratings = mapper.ToEbookRatingsResponseList(ratingsData)
|
||||||
|
|
||||||
|
return ratings, paging, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEbookRatingSummary implement interface of EbookRatingsService
|
||||||
|
func (_i *ebookRatingsService) GetEbookRatingSummary(ebookId uint) (summary *response.EbookRatingSummaryResponse, err error) {
|
||||||
|
// Get ebook info
|
||||||
|
ebook, err := _i.EbooksRepo.FindOne(ebookId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("ebook not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get rating stats
|
||||||
|
totalRatings, averageRating, ratingCounts, err := _i.Repo.GetEbookRatingStats(ebookId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get recent reviews
|
||||||
|
recentReviews, err := _i.Repo.GetRecentReviews(ebookId, 5)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
summary = mapper.ToEbookRatingSummaryResponse(ebookId, ebook.Title, averageRating, totalRatings, ratingCounts, recentReviews)
|
||||||
|
|
||||||
|
return summary, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create implement interface of EbookRatingsService
|
||||||
|
func (_i *ebookRatingsService) Create(req request.EbookRatingsCreateRequest, authToken string) (rating *response.EbookRatingsResponse, err error) {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return nil, errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ebook exists
|
||||||
|
_, err = _i.EbooksRepo.FindOne(req.EbookId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("ebook not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if purchase exists and belongs to user
|
||||||
|
purchase, err := _i.EbookPurchasesRepo.FindOne(req.PurchaseId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("purchase not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if purchase.BuyerId != user.ID {
|
||||||
|
return nil, errors.New("purchase does not belong to user")
|
||||||
|
}
|
||||||
|
|
||||||
|
if purchase.EbookId != req.EbookId {
|
||||||
|
return nil, errors.New("purchase does not match ebook")
|
||||||
|
}
|
||||||
|
|
||||||
|
if purchase.PaymentStatus == nil || *purchase.PaymentStatus != "paid" {
|
||||||
|
return nil, errors.New("payment not completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user already rated this ebook
|
||||||
|
existingRating, err := _i.Repo.FindByUserAndEbook(user.ID, req.EbookId)
|
||||||
|
if err == nil && existingRating != nil {
|
||||||
|
return nil, errors.New("user already rated this ebook")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user already rated this purchase
|
||||||
|
existingPurchaseRating, err := _i.Repo.FindByPurchaseId(req.PurchaseId)
|
||||||
|
if err == nil && existingPurchaseRating != nil {
|
||||||
|
return nil, errors.New("user already rated this purchase")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create rating
|
||||||
|
ratingEntity := req.ToEntity()
|
||||||
|
ratingEntity.UserId = user.ID
|
||||||
|
|
||||||
|
ratingData, err := _i.Repo.Create(ratingEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update ebook rating stats
|
||||||
|
err = _i.updateEbookRatingStats(req.EbookId)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Msg("Failed to update ebook rating stats")
|
||||||
|
}
|
||||||
|
|
||||||
|
rating = mapper.ToEbookRatingsResponse(ratingData)
|
||||||
|
|
||||||
|
return rating, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implement interface of EbookRatingsService
|
||||||
|
func (_i *ebookRatingsService) Update(id uint, req request.EbookRatingsUpdateRequest, authToken string) (err error) {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if rating exists and belongs to user
|
||||||
|
existingRating, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("rating not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingRating.UserId != user.ID {
|
||||||
|
return errors.New("rating does not belong to user")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update rating
|
||||||
|
ratingEntity := req.ToEntity()
|
||||||
|
err = _i.Repo.Update(id, ratingEntity)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update ebook rating stats
|
||||||
|
err = _i.updateEbookRatingStats(existingRating.EbookId)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Msg("Failed to update ebook rating stats")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implement interface of EbookRatingsService
|
||||||
|
func (_i *ebookRatingsService) Delete(id uint, authToken string) error {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if rating exists and belongs to user
|
||||||
|
existingRating, err := _i.Repo.FindOne(id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("rating not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingRating.UserId != user.ID {
|
||||||
|
return errors.New("rating does not belong to user")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete rating
|
||||||
|
err = _i.Repo.Delete(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update ebook rating stats
|
||||||
|
err = _i.updateEbookRatingStats(existingRating.EbookId)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Msg("Failed to update ebook rating stats")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRatingStats implement interface of EbookRatingsService
|
||||||
|
func (_i *ebookRatingsService) GetRatingStats(ebookId uint) (stats *response.EbookRatingStatsResponse, err error) {
|
||||||
|
totalRatings, averageRating, ratingCounts, err := _i.Repo.GetEbookRatingStats(ebookId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stats = mapper.ToEbookRatingStatsResponse(totalRatings, averageRating, ratingCounts)
|
||||||
|
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateEbookRatingStats updates the rating statistics in the ebook table
|
||||||
|
func (_i *ebookRatingsService) updateEbookRatingStats(ebookId uint) error {
|
||||||
|
totalRatings, averageRating, _, err := _i.Repo.GetEbookRatingStats(ebookId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update ebook with new rating stats
|
||||||
|
ebookUpdate := &entity.Ebooks{
|
||||||
|
Rating: &averageRating,
|
||||||
|
ReviewCount: &totalRatings,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _i.EbooksRepo.UpdateSkipNull(ebookId, ebookUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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/response"
|
||||||
|
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||||
|
"narasi-ahli-be/utils/paginator"
|
||||||
|
utilSvc "narasi-ahli-be/utils/service"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EbookWishlistsService
|
||||||
|
type ebookWishlistsService struct {
|
||||||
|
Repo repository.EbookWishlistsRepository
|
||||||
|
EbooksRepo repository.EbooksRepository
|
||||||
|
Log zerolog.Logger
|
||||||
|
UsersRepo usersRepository.UsersRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// EbookWishlistsService define interface of IEbookWishlistsService
|
||||||
|
type EbookWishlistsService interface {
|
||||||
|
GetByUserId(authToken string, pagination *paginator.Pagination) (wishlists []*response.EbookWishlistResponse, paging paginator.Pagination, err error)
|
||||||
|
AddToWishlist(authToken string, ebookId uint) (err error)
|
||||||
|
RemoveFromWishlist(authToken string, ebookId uint) (err error)
|
||||||
|
IsInWishlist(authToken string, ebookId uint) (isInWishlist bool, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEbookWishlistsService init EbookWishlistsService
|
||||||
|
func NewEbookWishlistsService(
|
||||||
|
repo repository.EbookWishlistsRepository,
|
||||||
|
ebooksRepo repository.EbooksRepository,
|
||||||
|
log zerolog.Logger,
|
||||||
|
usersRepo usersRepository.UsersRepository) EbookWishlistsService {
|
||||||
|
|
||||||
|
return &ebookWishlistsService{
|
||||||
|
Repo: repo,
|
||||||
|
EbooksRepo: ebooksRepo,
|
||||||
|
Log: log,
|
||||||
|
UsersRepo: usersRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByUserId implement interface of EbookWishlistsService
|
||||||
|
func (_i *ebookWishlistsService) GetByUserId(authToken string, pagination *paginator.Pagination) (wishlists []*response.EbookWishlistResponse, paging paginator.Pagination, err error) {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return nil, paginator.Pagination{}, errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
wishlistsData, paging, err := _i.Repo.GetByUserId(user.ID, pagination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, paging, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wishlists = mapper.ToEbookWishlistResponseList(wishlistsData)
|
||||||
|
|
||||||
|
return wishlists, paging, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToWishlist implement interface of EbookWishlistsService
|
||||||
|
func (_i *ebookWishlistsService) AddToWishlist(authToken string, ebookId uint) (err error) {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ebook exists
|
||||||
|
_, err = _i.EbooksRepo.FindOne(ebookId)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("ebook not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already in wishlist
|
||||||
|
existingWishlist, err := _i.Repo.FindByUserAndEbook(user.ID, ebookId)
|
||||||
|
if err == nil && existingWishlist != nil {
|
||||||
|
return errors.New("ebook already in wishlist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to wishlist
|
||||||
|
wishlist := &entity.EbookWishlists{
|
||||||
|
UserId: user.ID,
|
||||||
|
EbookId: ebookId,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = _i.Repo.Create(wishlist)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update wishlist count
|
||||||
|
err = _i.EbooksRepo.UpdateWishlistCount(ebookId)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Msg("Failed to update wishlist count")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFromWishlist implement interface of EbookWishlistsService
|
||||||
|
func (_i *ebookWishlistsService) RemoveFromWishlist(authToken string, ebookId uint) (err error) {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if in wishlist
|
||||||
|
existingWishlist, err := _i.Repo.FindByUserAndEbook(user.ID, ebookId)
|
||||||
|
if err != nil || existingWishlist == nil {
|
||||||
|
return errors.New("ebook not in wishlist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from wishlist
|
||||||
|
err = _i.Repo.Delete(existingWishlist.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInWishlist implement interface of EbookWishlistsService
|
||||||
|
func (_i *ebookWishlistsService) IsInWishlist(authToken string, ebookId uint) (isInWishlist bool, err error) {
|
||||||
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
if user == nil {
|
||||||
|
return false, errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
existingWishlist, err := _i.Repo.FindByUserAndEbook(user.ID, ebookId)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil // Not in wishlist
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingWishlist != nil, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,365 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"narasi-ahli-be/app/module/cities"
|
"narasi-ahli-be/app/module/cities"
|
||||||
"narasi-ahli-be/app/module/custom_static_pages"
|
"narasi-ahli-be/app/module/custom_static_pages"
|
||||||
"narasi-ahli-be/app/module/districts"
|
"narasi-ahli-be/app/module/districts"
|
||||||
|
"narasi-ahli-be/app/module/ebooks"
|
||||||
"narasi-ahli-be/app/module/education_history"
|
"narasi-ahli-be/app/module/education_history"
|
||||||
"narasi-ahli-be/app/module/feedbacks"
|
"narasi-ahli-be/app/module/feedbacks"
|
||||||
"narasi-ahli-be/app/module/magazine_files"
|
"narasi-ahli-be/app/module/magazine_files"
|
||||||
|
|
@ -50,6 +51,7 @@ type Router struct {
|
||||||
CitiesRouter *cities.CitiesRouter
|
CitiesRouter *cities.CitiesRouter
|
||||||
CustomStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter
|
CustomStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter
|
||||||
DistrictsRouter *districts.DistrictsRouter
|
DistrictsRouter *districts.DistrictsRouter
|
||||||
|
EbooksRouter *ebooks.EbooksRouter
|
||||||
FeedbacksRouter *feedbacks.FeedbacksRouter
|
FeedbacksRouter *feedbacks.FeedbacksRouter
|
||||||
MagazineFilesRouter *magazine_files.MagazineFilesRouter
|
MagazineFilesRouter *magazine_files.MagazineFilesRouter
|
||||||
MagazinesRouter *magazines.MagazinesRouter
|
MagazinesRouter *magazines.MagazinesRouter
|
||||||
|
|
@ -82,6 +84,7 @@ func NewRouter(
|
||||||
citiesRouter *cities.CitiesRouter,
|
citiesRouter *cities.CitiesRouter,
|
||||||
customStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter,
|
customStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter,
|
||||||
districtsRouter *districts.DistrictsRouter,
|
districtsRouter *districts.DistrictsRouter,
|
||||||
|
ebooksRouter *ebooks.EbooksRouter,
|
||||||
feedbacksRouter *feedbacks.FeedbacksRouter,
|
feedbacksRouter *feedbacks.FeedbacksRouter,
|
||||||
magazineFilesRouter *magazine_files.MagazineFilesRouter,
|
magazineFilesRouter *magazine_files.MagazineFilesRouter,
|
||||||
magazinesRouter *magazines.MagazinesRouter,
|
magazinesRouter *magazines.MagazinesRouter,
|
||||||
|
|
@ -112,6 +115,7 @@ func NewRouter(
|
||||||
CitiesRouter: citiesRouter,
|
CitiesRouter: citiesRouter,
|
||||||
CustomStaticPagesRouter: customStaticPagesRouter,
|
CustomStaticPagesRouter: customStaticPagesRouter,
|
||||||
DistrictsRouter: districtsRouter,
|
DistrictsRouter: districtsRouter,
|
||||||
|
EbooksRouter: ebooksRouter,
|
||||||
FeedbacksRouter: feedbacksRouter,
|
FeedbacksRouter: feedbacksRouter,
|
||||||
MagazineFilesRouter: magazineFilesRouter,
|
MagazineFilesRouter: magazineFilesRouter,
|
||||||
MagazinesRouter: magazinesRouter,
|
MagazinesRouter: magazinesRouter,
|
||||||
|
|
@ -152,6 +156,7 @@ func (r *Router) Register() {
|
||||||
r.CitiesRouter.RegisterCitiesRoutes()
|
r.CitiesRouter.RegisterCitiesRoutes()
|
||||||
r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes()
|
r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes()
|
||||||
r.DistrictsRouter.RegisterDistrictsRoutes()
|
r.DistrictsRouter.RegisterDistrictsRoutes()
|
||||||
|
r.EbooksRouter.RegisterEbooksRoutes()
|
||||||
r.FeedbacksRouter.RegisterFeedbacksRoutes()
|
r.FeedbacksRouter.RegisterFeedbacksRoutes()
|
||||||
r.MagazinesRouter.RegisterMagazinesRoutes()
|
r.MagazinesRouter.RegisterMagazinesRoutes()
|
||||||
r.MagazineFilesRouter.RegisterMagazineFilesRoutes()
|
r.MagazineFilesRouter.RegisterMagazineFilesRoutes()
|
||||||
|
|
|
||||||
1999
docs/swagger/docs.go
1999
docs/swagger/docs.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2
main.go
2
main.go
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"narasi-ahli-be/app/module/cities"
|
"narasi-ahli-be/app/module/cities"
|
||||||
"narasi-ahli-be/app/module/custom_static_pages"
|
"narasi-ahli-be/app/module/custom_static_pages"
|
||||||
"narasi-ahli-be/app/module/districts"
|
"narasi-ahli-be/app/module/districts"
|
||||||
|
"narasi-ahli-be/app/module/ebooks"
|
||||||
"narasi-ahli-be/app/module/education_history"
|
"narasi-ahli-be/app/module/education_history"
|
||||||
"narasi-ahli-be/app/module/feedbacks"
|
"narasi-ahli-be/app/module/feedbacks"
|
||||||
"narasi-ahli-be/app/module/magazine_files"
|
"narasi-ahli-be/app/module/magazine_files"
|
||||||
|
|
@ -76,6 +77,7 @@ func main() {
|
||||||
cities.NewCitiesModule,
|
cities.NewCitiesModule,
|
||||||
custom_static_pages.NewCustomStaticPagesModule,
|
custom_static_pages.NewCustomStaticPagesModule,
|
||||||
districts.NewDistrictsModule,
|
districts.NewDistrictsModule,
|
||||||
|
ebooks.NewEbooksModule,
|
||||||
feedbacks.NewFeedbacksModule,
|
feedbacks.NewFeedbacksModule,
|
||||||
magazines.NewMagazinesModule,
|
magazines.NewMagazinesModule,
|
||||||
magazine_files.NewMagazineFilesModule,
|
magazine_files.NewMagazineFilesModule,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue