From 21c68096c35930917a270be886170e25d872c8a2 Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Sun, 21 Sep 2025 23:10:08 +0700 Subject: [PATCH] feat: add ebook features --- app/database/entity/ebook_purchases.entity.go | 26 + app/database/entity/ebook_ratings.entity.go | 24 + app/database/entity/ebook_wishlists.entity.go | 16 + app/database/entity/ebooks.entity.go | 40 + app/database/index.database.go | 6 + .../controller/ebook_purchases.controller.go | 210 ++ .../controller/ebook_ratings.controller.go | 313 +++ .../controller/ebook_wishlists.controller.go | 160 ++ .../ebooks/controller/ebooks.controller.go | 350 +++ app/module/ebooks/ebooks.module.go | 125 ++ .../ebooks/mapper/ebook_ratings.mapper.go | 94 + app/module/ebooks/mapper/ebooks.mapper.go | 140 ++ .../repository/ebook_purchases.repository.go | 98 + .../repository/ebook_ratings.repository.go | 243 ++ .../repository/ebook_wishlists.repository.go | 79 + .../ebooks/repository/ebooks.repository.go | 186 ++ .../ebooks/request/ebook_ratings.request.go | 113 + app/module/ebooks/request/ebooks.request.go | 178 ++ .../ebooks/response/ebook_ratings.response.go | 43 + app/module/ebooks/response/ebooks.response.go | 79 + .../ebooks/service/ebook_purchases.service.go | 185 ++ .../ebooks/service/ebook_ratings.service.go | 280 +++ .../ebooks/service/ebook_wishlists.service.go | 138 ++ app/module/ebooks/service/ebooks.service.go | 365 +++ app/router/api.go | 5 + docs/swagger/docs.go | 1999 +++++++++++++++++ docs/swagger/swagger.json | 1999 +++++++++++++++++ docs/swagger/swagger.yaml | 1279 +++++++++++ main.go | 2 + 29 files changed, 8775 insertions(+) create mode 100644 app/database/entity/ebook_purchases.entity.go create mode 100644 app/database/entity/ebook_ratings.entity.go create mode 100644 app/database/entity/ebook_wishlists.entity.go create mode 100644 app/database/entity/ebooks.entity.go create mode 100644 app/module/ebooks/controller/ebook_purchases.controller.go create mode 100644 app/module/ebooks/controller/ebook_ratings.controller.go create mode 100644 app/module/ebooks/controller/ebook_wishlists.controller.go create mode 100644 app/module/ebooks/controller/ebooks.controller.go create mode 100644 app/module/ebooks/ebooks.module.go create mode 100644 app/module/ebooks/mapper/ebook_ratings.mapper.go create mode 100644 app/module/ebooks/mapper/ebooks.mapper.go create mode 100644 app/module/ebooks/repository/ebook_purchases.repository.go create mode 100644 app/module/ebooks/repository/ebook_ratings.repository.go create mode 100644 app/module/ebooks/repository/ebook_wishlists.repository.go create mode 100644 app/module/ebooks/repository/ebooks.repository.go create mode 100644 app/module/ebooks/request/ebook_ratings.request.go create mode 100644 app/module/ebooks/request/ebooks.request.go create mode 100644 app/module/ebooks/response/ebook_ratings.response.go create mode 100644 app/module/ebooks/response/ebooks.response.go create mode 100644 app/module/ebooks/service/ebook_purchases.service.go create mode 100644 app/module/ebooks/service/ebook_ratings.service.go create mode 100644 app/module/ebooks/service/ebook_wishlists.service.go create mode 100644 app/module/ebooks/service/ebooks.service.go diff --git a/app/database/entity/ebook_purchases.entity.go b/app/database/entity/ebook_purchases.entity.go new file mode 100644 index 0000000..978381b --- /dev/null +++ b/app/database/entity/ebook_purchases.entity.go @@ -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()"` +} diff --git a/app/database/entity/ebook_ratings.entity.go b/app/database/entity/ebook_ratings.entity.go new file mode 100644 index 0000000..84c9ae6 --- /dev/null +++ b/app/database/entity/ebook_ratings.entity.go @@ -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()"` +} diff --git a/app/database/entity/ebook_wishlists.entity.go b/app/database/entity/ebook_wishlists.entity.go new file mode 100644 index 0000000..be45133 --- /dev/null +++ b/app/database/entity/ebook_wishlists.entity.go @@ -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()"` +} diff --git a/app/database/entity/ebooks.entity.go b/app/database/entity/ebooks.entity.go new file mode 100644 index 0000000..869ec6d --- /dev/null +++ b/app/database/entity/ebooks.entity.go @@ -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()"` +} diff --git a/app/database/index.database.go b/app/database/index.database.go index 2b91014..2f28364 100644 --- a/app/database/index.database.go +++ b/app/database/index.database.go @@ -125,6 +125,12 @@ func Models() []interface{} { entity.AIChatSessions{}, entity.AIChatMessages{}, entity.AIChatLogs{}, + + // Ebook entities + entity.Ebooks{}, + entity.EbookWishlists{}, + entity.EbookPurchases{}, + entity.EbookRatings{}, } } diff --git a/app/module/ebooks/controller/ebook_purchases.controller.go b/app/module/ebooks/controller/ebook_purchases.controller.go new file mode 100644 index 0000000..6268f12 --- /dev/null +++ b/app/module/ebooks/controller/ebook_purchases.controller.go @@ -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"` +} diff --git a/app/module/ebooks/controller/ebook_ratings.controller.go b/app/module/ebooks/controller/ebook_ratings.controller.go new file mode 100644 index 0000000..d065e19 --- /dev/null +++ b/app/module/ebooks/controller/ebook_ratings.controller.go @@ -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 ) +// @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 ) +// @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 ) +// @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 ) +// @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, + }) +} diff --git a/app/module/ebooks/controller/ebook_wishlists.controller.go b/app/module/ebooks/controller/ebook_wishlists.controller.go new file mode 100644 index 0000000..82bdf6c --- /dev/null +++ b/app/module/ebooks/controller/ebook_wishlists.controller.go @@ -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}, + }) +} diff --git a/app/module/ebooks/controller/ebooks.controller.go b/app/module/ebooks/controller/ebooks.controller.go new file mode 100644 index 0000000..ac36172 --- /dev/null +++ b/app/module/ebooks/controller/ebooks.controller.go @@ -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 ) +// @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 ) +// @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 +} diff --git a/app/module/ebooks/ebooks.module.go b/app/module/ebooks/ebooks.module.go new file mode 100644 index 0000000..6235caa --- /dev/null +++ b/app/module/ebooks/ebooks.module.go @@ -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) + }) +} diff --git a/app/module/ebooks/mapper/ebook_ratings.mapper.go b/app/module/ebooks/mapper/ebook_ratings.mapper.go new file mode 100644 index 0000000..f29b96b --- /dev/null +++ b/app/module/ebooks/mapper/ebook_ratings.mapper.go @@ -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 +} diff --git a/app/module/ebooks/mapper/ebooks.mapper.go b/app/module/ebooks/mapper/ebooks.mapper.go new file mode 100644 index 0000000..f1a4392 --- /dev/null +++ b/app/module/ebooks/mapper/ebooks.mapper.go @@ -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 +} diff --git a/app/module/ebooks/repository/ebook_purchases.repository.go b/app/module/ebooks/repository/ebook_purchases.repository.go new file mode 100644 index 0000000..657bd17 --- /dev/null +++ b/app/module/ebooks/repository/ebook_purchases.repository.go @@ -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 +} diff --git a/app/module/ebooks/repository/ebook_ratings.repository.go b/app/module/ebooks/repository/ebook_ratings.repository.go new file mode 100644 index 0000000..8161c7d --- /dev/null +++ b/app/module/ebooks/repository/ebook_ratings.repository.go @@ -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 +} diff --git a/app/module/ebooks/repository/ebook_wishlists.repository.go b/app/module/ebooks/repository/ebook_wishlists.repository.go new file mode 100644 index 0000000..d9f69d1 --- /dev/null +++ b/app/module/ebooks/repository/ebook_wishlists.repository.go @@ -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 +} diff --git a/app/module/ebooks/repository/ebooks.repository.go b/app/module/ebooks/repository/ebooks.repository.go new file mode 100644 index 0000000..7452507 --- /dev/null +++ b/app/module/ebooks/repository/ebooks.repository.go @@ -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 +} diff --git a/app/module/ebooks/request/ebook_ratings.request.go b/app/module/ebooks/request/ebook_ratings.request.go new file mode 100644 index 0000000..66af901 --- /dev/null +++ b/app/module/ebooks/request/ebook_ratings.request.go @@ -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 +} diff --git a/app/module/ebooks/request/ebooks.request.go b/app/module/ebooks/request/ebooks.request.go new file mode 100644 index 0000000..25d78e4 --- /dev/null +++ b/app/module/ebooks/request/ebooks.request.go @@ -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 +} diff --git a/app/module/ebooks/response/ebook_ratings.response.go b/app/module/ebooks/response/ebook_ratings.response.go new file mode 100644 index 0000000..124f466 --- /dev/null +++ b/app/module/ebooks/response/ebook_ratings.response.go @@ -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"` +} diff --git a/app/module/ebooks/response/ebooks.response.go b/app/module/ebooks/response/ebooks.response.go new file mode 100644 index 0000000..714e258 --- /dev/null +++ b/app/module/ebooks/response/ebooks.response.go @@ -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"` +} diff --git a/app/module/ebooks/service/ebook_purchases.service.go b/app/module/ebooks/service/ebook_purchases.service.go new file mode 100644 index 0000000..b6ffccb --- /dev/null +++ b/app/module/ebooks/service/ebook_purchases.service.go @@ -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 +} diff --git a/app/module/ebooks/service/ebook_ratings.service.go b/app/module/ebooks/service/ebook_ratings.service.go new file mode 100644 index 0000000..c72663a --- /dev/null +++ b/app/module/ebooks/service/ebook_ratings.service.go @@ -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 +} diff --git a/app/module/ebooks/service/ebook_wishlists.service.go b/app/module/ebooks/service/ebook_wishlists.service.go new file mode 100644 index 0000000..ec3fc59 --- /dev/null +++ b/app/module/ebooks/service/ebook_wishlists.service.go @@ -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 +} diff --git a/app/module/ebooks/service/ebooks.service.go b/app/module/ebooks/service/ebooks.service.go new file mode 100644 index 0000000..d756614 --- /dev/null +++ b/app/module/ebooks/service/ebooks.service.go @@ -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) +} diff --git a/app/router/api.go b/app/router/api.go index 514c801..4e8ec70 100644 --- a/app/router/api.go +++ b/app/router/api.go @@ -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() diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 79672f6..3acd167 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -5437,6 +5437,1831 @@ const docTemplate = `{ } } }, + "/ebook-ratings": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Ebook Ratings", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get all Ebook Ratings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "ebookId", + "in": "query" + }, + { + "type": "boolean", + "name": "isVerified", + "in": "query" + }, + { + "type": "integer", + "name": "rating", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Create ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbookRatingsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/ebook/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ratings by ebook ID", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ratings by ebook ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/stats/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ebook rating statistics", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ebook rating statistics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/summary/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ebook rating summary with stats and recent reviews", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ebook rating summary", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user ratings", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get user ratings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Update ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbookRatingsUpdateRequest" + } + }, + { + "type": "integer", + "description": "Rating ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Delete ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Rating ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Ebooks", + "tags": [ + "Ebooks" + ], + "summary": "Get all Ebooks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "authorId", + "in": "query" + }, + { + "type": "string", + "name": "category", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublished", + "in": "query" + }, + { + "type": "number", + "name": "maxPrice", + "in": "query" + }, + { + "type": "number", + "name": "minPrice", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "tags", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Create Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbooksCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/download/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Download PDF of Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Download PDF Ebook", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/pdf/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save PDF File of Ebook", + "produces": [ + "application/json" + ], + "tags": [ + "Ebooks" + ], + "summary": "Save PDF File Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload PDF file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchase/{ebookId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for purchasing ebook", + "tags": [ + "Ebook Purchase" + ], + "summary": "Purchase ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Payment Method", + "name": "paymentMethod", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user purchases", + "tags": [ + "Ebook Purchase" + ], + "summary": "Get user purchases", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting purchase by ID", + "tags": [ + "Ebook Purchase" + ], + "summary": "Get purchase by ID", + "parameters": [ + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}/confirm": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for confirming payment", + "tags": [ + "Ebook Purchase" + ], + "summary": "Confirm payment", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}/payment": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating payment status", + "tags": [ + "Ebook Purchase" + ], + "summary": "Update payment status", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.PaymentStatusUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Ebook by slug", + "tags": [ + "Ebooks" + ], + "summary": "Get one Ebook by slug", + "parameters": [ + { + "type": "string", + "description": "Ebook Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/statistic/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Summary Stats of Ebook", + "tags": [ + "Ebooks" + ], + "summary": "SummaryStats Ebook", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Ebook", + "produces": [ + "application/json" + ], + "tags": [ + "Ebooks" + ], + "summary": "Save Thumbnail Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Get user wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist/check/{ebookId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for checking if ebook is in wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Check if ebook is in wishlist", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist/{ebookId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding ebook to wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Add ebook to wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for removing ebook from wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Remove ebook from wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Get one Ebook", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Update Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbooksUpdateRequest" + } + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Delete Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/education-history": { "get": { "security": [ @@ -11445,6 +13270,23 @@ const docTemplate = `{ } }, "definitions": { + "controller.PaymentStatusUpdateRequest": { + "type": "object", + "required": [ + "paymentStatus" + ], + "properties": { + "paymentProof": { + "type": "string" + }, + "paymentStatus": { + "type": "string" + }, + "transactionId": { + "type": "string" + } + } + }, "paginator.Pagination": { "type": "object", "properties": { @@ -12066,6 +13908,163 @@ const docTemplate = `{ } } }, + "request.EbookRatingsCreateRequest": { + "type": "object", + "required": [ + "ebookId", + "purchaseId", + "rating" + ], + "properties": { + "ebookId": { + "type": "integer" + }, + "isAnonymous": { + "type": "boolean" + }, + "purchaseId": { + "type": "integer" + }, + "rating": { + "type": "integer", + "maximum": 5, + "minimum": 1 + }, + "review": { + "type": "string" + } + } + }, + "request.EbookRatingsUpdateRequest": { + "type": "object", + "required": [ + "rating" + ], + "properties": { + "isAnonymous": { + "type": "boolean" + }, + "rating": { + "type": "integer", + "maximum": 5, + "minimum": 1 + }, + "review": { + "type": "string" + } + } + }, + "request.EbooksCreateRequest": { + "type": "object", + "required": [ + "description", + "price", + "slug", + "title" + ], + "properties": { + "category": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublished": { + "type": "boolean" + }, + "isbn": { + "type": "string" + }, + "language": { + "type": "string" + }, + "pageCount": { + "type": "integer" + }, + "price": { + "type": "number", + "minimum": 0 + }, + "publishedYear": { + "type": "integer" + }, + "publisher": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.EbooksUpdateRequest": { + "type": "object", + "required": [ + "description", + "price", + "slug", + "title" + ], + "properties": { + "category": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublished": { + "type": "boolean" + }, + "isbn": { + "type": "string" + }, + "language": { + "type": "string" + }, + "pageCount": { + "type": "integer" + }, + "price": { + "type": "number", + "minimum": 0 + }, + "publishedYear": { + "type": "integer" + }, + "publisher": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, "request.EducationHistoryCreateRequest": { "type": "object", "required": [ diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index f9871fa..8578321 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -5426,6 +5426,1831 @@ } } }, + "/ebook-ratings": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Ebook Ratings", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get all Ebook Ratings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "ebookId", + "in": "query" + }, + { + "type": "boolean", + "name": "isVerified", + "in": "query" + }, + { + "type": "integer", + "name": "rating", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Create ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbookRatingsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/ebook/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ratings by ebook ID", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ratings by ebook ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/stats/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ebook rating statistics", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ebook rating statistics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/summary/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ebook rating summary with stats and recent reviews", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ebook rating summary", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user ratings", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get user ratings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Update ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbookRatingsUpdateRequest" + } + }, + { + "type": "integer", + "description": "Rating ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Delete ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Rating ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Ebooks", + "tags": [ + "Ebooks" + ], + "summary": "Get all Ebooks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "authorId", + "in": "query" + }, + { + "type": "string", + "name": "category", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublished", + "in": "query" + }, + { + "type": "number", + "name": "maxPrice", + "in": "query" + }, + { + "type": "number", + "name": "minPrice", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "tags", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Create Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbooksCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/download/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Download PDF of Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Download PDF Ebook", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/pdf/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save PDF File of Ebook", + "produces": [ + "application/json" + ], + "tags": [ + "Ebooks" + ], + "summary": "Save PDF File Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload PDF file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchase/{ebookId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for purchasing ebook", + "tags": [ + "Ebook Purchase" + ], + "summary": "Purchase ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Payment Method", + "name": "paymentMethod", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user purchases", + "tags": [ + "Ebook Purchase" + ], + "summary": "Get user purchases", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting purchase by ID", + "tags": [ + "Ebook Purchase" + ], + "summary": "Get purchase by ID", + "parameters": [ + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}/confirm": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for confirming payment", + "tags": [ + "Ebook Purchase" + ], + "summary": "Confirm payment", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}/payment": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating payment status", + "tags": [ + "Ebook Purchase" + ], + "summary": "Update payment status", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.PaymentStatusUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Ebook by slug", + "tags": [ + "Ebooks" + ], + "summary": "Get one Ebook by slug", + "parameters": [ + { + "type": "string", + "description": "Ebook Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/statistic/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Summary Stats of Ebook", + "tags": [ + "Ebooks" + ], + "summary": "SummaryStats Ebook", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Ebook", + "produces": [ + "application/json" + ], + "tags": [ + "Ebooks" + ], + "summary": "Save Thumbnail Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Get user wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist/check/{ebookId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for checking if ebook is in wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Check if ebook is in wishlist", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist/{ebookId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding ebook to wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Add ebook to wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for removing ebook from wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Remove ebook from wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Get one Ebook", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Update Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbooksUpdateRequest" + } + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Delete Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/education-history": { "get": { "security": [ @@ -11434,6 +13259,23 @@ } }, "definitions": { + "controller.PaymentStatusUpdateRequest": { + "type": "object", + "required": [ + "paymentStatus" + ], + "properties": { + "paymentProof": { + "type": "string" + }, + "paymentStatus": { + "type": "string" + }, + "transactionId": { + "type": "string" + } + } + }, "paginator.Pagination": { "type": "object", "properties": { @@ -12055,6 +13897,163 @@ } } }, + "request.EbookRatingsCreateRequest": { + "type": "object", + "required": [ + "ebookId", + "purchaseId", + "rating" + ], + "properties": { + "ebookId": { + "type": "integer" + }, + "isAnonymous": { + "type": "boolean" + }, + "purchaseId": { + "type": "integer" + }, + "rating": { + "type": "integer", + "maximum": 5, + "minimum": 1 + }, + "review": { + "type": "string" + } + } + }, + "request.EbookRatingsUpdateRequest": { + "type": "object", + "required": [ + "rating" + ], + "properties": { + "isAnonymous": { + "type": "boolean" + }, + "rating": { + "type": "integer", + "maximum": 5, + "minimum": 1 + }, + "review": { + "type": "string" + } + } + }, + "request.EbooksCreateRequest": { + "type": "object", + "required": [ + "description", + "price", + "slug", + "title" + ], + "properties": { + "category": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublished": { + "type": "boolean" + }, + "isbn": { + "type": "string" + }, + "language": { + "type": "string" + }, + "pageCount": { + "type": "integer" + }, + "price": { + "type": "number", + "minimum": 0 + }, + "publishedYear": { + "type": "integer" + }, + "publisher": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.EbooksUpdateRequest": { + "type": "object", + "required": [ + "description", + "price", + "slug", + "title" + ], + "properties": { + "category": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublished": { + "type": "boolean" + }, + "isbn": { + "type": "string" + }, + "language": { + "type": "string" + }, + "pageCount": { + "type": "integer" + }, + "price": { + "type": "number", + "minimum": 0 + }, + "publishedYear": { + "type": "integer" + }, + "publisher": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, "request.EducationHistoryCreateRequest": { "type": "object", "required": [ diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index d37d00e..e8a0447 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -1,4 +1,15 @@ definitions: + controller.PaymentStatusUpdateRequest: + properties: + paymentProof: + type: string + paymentStatus: + type: string + transactionId: + type: string + required: + - paymentStatus + type: object paginator.Pagination: properties: count: @@ -425,6 +436,114 @@ definitions: - slug - title type: object + request.EbookRatingsCreateRequest: + properties: + ebookId: + type: integer + isAnonymous: + type: boolean + purchaseId: + type: integer + rating: + maximum: 5 + minimum: 1 + type: integer + review: + type: string + required: + - ebookId + - purchaseId + - rating + type: object + request.EbookRatingsUpdateRequest: + properties: + isAnonymous: + type: boolean + rating: + maximum: 5 + minimum: 1 + type: integer + review: + type: string + required: + - rating + type: object + request.EbooksCreateRequest: + properties: + category: + type: string + createdAt: + type: string + createdById: + type: integer + description: + type: string + isPublished: + type: boolean + isbn: + type: string + language: + type: string + pageCount: + type: integer + price: + minimum: 0 + type: number + publishedYear: + type: integer + publisher: + type: string + slug: + type: string + tags: + type: string + title: + type: string + required: + - description + - price + - slug + - title + type: object + request.EbooksUpdateRequest: + properties: + category: + type: string + createdAt: + type: string + createdById: + type: integer + description: + type: string + isPublished: + type: boolean + isbn: + type: string + language: + type: string + pageCount: + type: integer + price: + minimum: 0 + type: number + publishedYear: + type: integer + publisher: + type: string + slug: + type: string + statusId: + type: integer + tags: + type: string + title: + type: string + required: + - description + - price + - slug + - title + type: object request.EducationHistoryCreateRequest: properties: certificateImage: @@ -4531,6 +4650,1166 @@ paths: summary: Update Districts tags: - Untags + /ebook-ratings: + get: + description: API for getting all Ebook Ratings + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: ebookId + type: integer + - in: query + name: isVerified + type: boolean + - in: query + name: rating + type: integer + - in: query + name: statusId + type: integer + - in: query + name: userId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Ebook Ratings + tags: + - Ebook Ratings + post: + description: API for creating ebook rating + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.EbookRatingsCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create ebook rating + tags: + - Ebook Ratings + /ebook-ratings/{id}: + delete: + description: API for deleting ebook rating + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Rating ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete ebook rating + tags: + - Ebook Ratings + put: + description: API for updating ebook rating + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.EbookRatingsUpdateRequest' + - description: Rating ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update ebook rating + tags: + - Ebook Ratings + /ebook-ratings/ebook/{id}: + get: + description: API for getting ratings by ebook ID + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Ebook ID + in: path + name: id + required: true + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get ratings by ebook ID + tags: + - Ebook Ratings + /ebook-ratings/stats/{id}: + get: + description: API for getting ebook rating statistics + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get ebook rating statistics + tags: + - Ebook Ratings + /ebook-ratings/summary/{id}: + get: + description: API for getting ebook rating summary with stats and recent reviews + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get ebook rating summary + tags: + - Ebook Ratings + /ebook-ratings/user: + get: + description: API for getting user ratings + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get user ratings + tags: + - Ebook Ratings + /ebooks: + get: + description: API for getting all Ebooks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: authorId + type: integer + - in: query + name: category + type: string + - in: query + name: description + type: string + - in: query + name: isPublished + type: boolean + - in: query + name: maxPrice + type: number + - in: query + name: minPrice + type: number + - in: query + name: statusId + type: integer + - in: query + name: tags + type: string + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Ebooks + tags: + - Ebooks + post: + description: API for create Ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.EbooksCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create Ebook + tags: + - Ebooks + /ebooks/{id}: + delete: + description: API for delete Ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete Ebook + tags: + - Ebooks + get: + description: API for getting one Ebook + parameters: + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Ebook + tags: + - Ebooks + put: + description: API for update Ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.EbooksUpdateRequest' + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update Ebook + tags: + - Ebooks + /ebooks/download/{id}: + get: + description: API for Download PDF of Ebook + parameters: + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Download PDF Ebook + tags: + - Ebooks + /ebooks/pdf/{id}: + post: + description: API for Save PDF File of Ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Upload PDF file + in: formData + name: file + required: true + type: file + - description: Ebook ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Save PDF File Ebook + tags: + - Ebooks + /ebooks/purchase/{ebookId}: + post: + description: API for purchasing ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Ebook ID + in: path + name: ebookId + required: true + type: integer + - description: Payment Method + in: query + name: paymentMethod + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Purchase ebook + tags: + - Ebook Purchase + /ebooks/purchases: + get: + description: API for getting user purchases + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get user purchases + tags: + - Ebook Purchase + /ebooks/purchases/{id}: + get: + description: API for getting purchase by ID + parameters: + - description: Purchase ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get purchase by ID + tags: + - Ebook Purchase + /ebooks/purchases/{id}/confirm: + put: + description: API for confirming payment + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Purchase ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Confirm payment + tags: + - Ebook Purchase + /ebooks/purchases/{id}/payment: + put: + description: API for updating payment status + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Purchase ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/controller.PaymentStatusUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update payment status + tags: + - Ebook Purchase + /ebooks/slug/{slug}: + get: + description: API for getting one Ebook by slug + parameters: + - description: Ebook Slug + in: path + name: slug + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Ebook by slug + tags: + - Ebooks + /ebooks/statistic/summary: + get: + description: API for Summary Stats of Ebook + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: SummaryStats Ebook + tags: + - Ebooks + /ebooks/thumbnail/{id}: + post: + description: API for Save Thumbnail of Ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Upload thumbnail + in: formData + name: files + required: true + type: file + - description: Ebook ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Save Thumbnail Ebook + tags: + - Ebooks + /ebooks/wishlist: + get: + description: API for getting user wishlist + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get user wishlist + tags: + - Ebook Wishlist + /ebooks/wishlist/{ebookId}: + delete: + description: API for removing ebook from wishlist + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Ebook ID + in: path + name: ebookId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Remove ebook from wishlist + tags: + - Ebook Wishlist + post: + description: API for adding ebook to wishlist + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Ebook ID + in: path + name: ebookId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Add ebook to wishlist + tags: + - Ebook Wishlist + /ebooks/wishlist/check/{ebookId}: + get: + description: API for checking if ebook is in wishlist + parameters: + - description: Ebook ID + in: path + name: ebookId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Check if ebook is in wishlist + tags: + - Ebook Wishlist /education-history: get: description: API for getting all Education History for authenticated user diff --git a/main.go b/main.go index 41f89f5..201a16a 100644 --- a/main.go +++ b/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,