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.AIChatMessages{},
|
||||
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/custom_static_pages"
|
||||
"narasi-ahli-be/app/module/districts"
|
||||
"narasi-ahli-be/app/module/ebooks"
|
||||
"narasi-ahli-be/app/module/education_history"
|
||||
"narasi-ahli-be/app/module/feedbacks"
|
||||
"narasi-ahli-be/app/module/magazine_files"
|
||||
|
|
@ -50,6 +51,7 @@ type Router struct {
|
|||
CitiesRouter *cities.CitiesRouter
|
||||
CustomStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter
|
||||
DistrictsRouter *districts.DistrictsRouter
|
||||
EbooksRouter *ebooks.EbooksRouter
|
||||
FeedbacksRouter *feedbacks.FeedbacksRouter
|
||||
MagazineFilesRouter *magazine_files.MagazineFilesRouter
|
||||
MagazinesRouter *magazines.MagazinesRouter
|
||||
|
|
@ -82,6 +84,7 @@ func NewRouter(
|
|||
citiesRouter *cities.CitiesRouter,
|
||||
customStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter,
|
||||
districtsRouter *districts.DistrictsRouter,
|
||||
ebooksRouter *ebooks.EbooksRouter,
|
||||
feedbacksRouter *feedbacks.FeedbacksRouter,
|
||||
magazineFilesRouter *magazine_files.MagazineFilesRouter,
|
||||
magazinesRouter *magazines.MagazinesRouter,
|
||||
|
|
@ -112,6 +115,7 @@ func NewRouter(
|
|||
CitiesRouter: citiesRouter,
|
||||
CustomStaticPagesRouter: customStaticPagesRouter,
|
||||
DistrictsRouter: districtsRouter,
|
||||
EbooksRouter: ebooksRouter,
|
||||
FeedbacksRouter: feedbacksRouter,
|
||||
MagazineFilesRouter: magazineFilesRouter,
|
||||
MagazinesRouter: magazinesRouter,
|
||||
|
|
@ -152,6 +156,7 @@ func (r *Router) Register() {
|
|||
r.CitiesRouter.RegisterCitiesRoutes()
|
||||
r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes()
|
||||
r.DistrictsRouter.RegisterDistrictsRoutes()
|
||||
r.EbooksRouter.RegisterEbooksRoutes()
|
||||
r.FeedbacksRouter.RegisterFeedbacksRoutes()
|
||||
r.MagazinesRouter.RegisterMagazinesRoutes()
|
||||
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/custom_static_pages"
|
||||
"narasi-ahli-be/app/module/districts"
|
||||
"narasi-ahli-be/app/module/ebooks"
|
||||
"narasi-ahli-be/app/module/education_history"
|
||||
"narasi-ahli-be/app/module/feedbacks"
|
||||
"narasi-ahli-be/app/module/magazine_files"
|
||||
|
|
@ -76,6 +77,7 @@ func main() {
|
|||
cities.NewCitiesModule,
|
||||
custom_static_pages.NewCustomStaticPagesModule,
|
||||
districts.NewDistrictsModule,
|
||||
ebooks.NewEbooksModule,
|
||||
feedbacks.NewFeedbacksModule,
|
||||
magazines.NewMagazinesModule,
|
||||
magazine_files.NewMagazineFilesModule,
|
||||
|
|
|
|||
Loading…
Reference in New Issue