328 lines
9.5 KiB
Go
328 lines
9.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"Yimaru-Backend/internal/domain"
|
|
"strconv"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
type submitRatingReq struct {
|
|
TargetType string `json:"target_type" validate:"required"`
|
|
TargetID int64 `json:"target_id"`
|
|
Stars int16 `json:"stars" validate:"required,min=1,max=5"`
|
|
Review *string `json:"review"`
|
|
}
|
|
|
|
type ratingRes struct {
|
|
ID int64 `json:"id"`
|
|
UserID int64 `json:"user_id"`
|
|
TargetType string `json:"target_type"`
|
|
TargetID int64 `json:"target_id"`
|
|
Stars int16 `json:"stars"`
|
|
Review *string `json:"review"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
type ratingSummaryRes struct {
|
|
TotalCount int64 `json:"total_count"`
|
|
AverageStars float64 `json:"average_stars"`
|
|
}
|
|
|
|
func mapRatingToRes(r domain.Rating) ratingRes {
|
|
return ratingRes{
|
|
ID: r.ID,
|
|
UserID: r.UserID,
|
|
TargetType: string(r.TargetType),
|
|
TargetID: r.TargetID,
|
|
Stars: r.Stars,
|
|
Review: r.Review,
|
|
CreatedAt: r.CreatedAt.String(),
|
|
UpdatedAt: r.UpdatedAt.String(),
|
|
}
|
|
}
|
|
|
|
func isValidTargetType(t string) bool {
|
|
switch domain.RatingTargetType(t) {
|
|
case domain.RatingTargetApp, domain.RatingTargetCourse, domain.RatingTargetSubCourse:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SubmitRating godoc
|
|
// @Summary Submit a rating
|
|
// @Description Submit a rating for an app, course, or sub-course
|
|
// @Tags ratings
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body submitRatingReq true "Submit rating payload"
|
|
// @Success 201 {object} domain.Response
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/ratings [post]
|
|
func (h *Handler) SubmitRating(c *fiber.Ctx) error {
|
|
userID := c.Locals("user_id").(int64)
|
|
|
|
var req submitRatingReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid request body",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
if !isValidTargetType(req.TargetType) {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid target_type, must be one of: app, course, sub_course",
|
|
})
|
|
}
|
|
|
|
targetType := domain.RatingTargetType(req.TargetType)
|
|
targetID := req.TargetID
|
|
if targetType == domain.RatingTargetApp {
|
|
targetID = 0
|
|
}
|
|
|
|
rating, err := h.ratingSvc.SubmitRating(c.Context(), userID, targetType, targetID, req.Stars, req.Review)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to submit rating",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
|
Message: "Rating submitted successfully",
|
|
Data: mapRatingToRes(rating),
|
|
})
|
|
}
|
|
|
|
// GetMyRating godoc
|
|
// @Summary Get my rating for a target
|
|
// @Description Returns the current user's rating for a specific target
|
|
// @Tags ratings
|
|
// @Produce json
|
|
// @Param target_type query string true "Target type (app, course, sub_course)"
|
|
// @Param target_id query int true "Target ID (0 for app)"
|
|
// @Success 200 {object} domain.Response
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 404 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/ratings/me [get]
|
|
func (h *Handler) GetMyRating(c *fiber.Ctx) error {
|
|
userID := c.Locals("user_id").(int64)
|
|
|
|
targetType := c.Query("target_type")
|
|
if !isValidTargetType(targetType) {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid target_type, must be one of: app, course, sub_course",
|
|
})
|
|
}
|
|
|
|
targetID, err := strconv.ParseInt(c.Query("target_id", "0"), 10, 64)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid target_id",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
if domain.RatingTargetType(targetType) == domain.RatingTargetApp {
|
|
targetID = 0
|
|
}
|
|
|
|
rating, err := h.ratingSvc.GetMyRating(c.Context(), userID, domain.RatingTargetType(targetType), targetID)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
|
Message: "Rating not found",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(domain.Response{
|
|
Message: "Rating retrieved successfully",
|
|
Data: mapRatingToRes(rating),
|
|
})
|
|
}
|
|
|
|
// GetRatingsByTarget godoc
|
|
// @Summary Get ratings for a target
|
|
// @Description Returns paginated ratings for a specific target
|
|
// @Tags ratings
|
|
// @Produce json
|
|
// @Param target_type query string true "Target type (app, course, sub_course)"
|
|
// @Param target_id query int true "Target ID (0 for app)"
|
|
// @Param limit query int false "Limit (default 20)"
|
|
// @Param offset query int false "Offset (default 0)"
|
|
// @Success 200 {object} domain.Response
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/ratings [get]
|
|
func (h *Handler) GetRatingsByTarget(c *fiber.Ctx) error {
|
|
targetType := c.Query("target_type")
|
|
if !isValidTargetType(targetType) {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid target_type, must be one of: app, course, sub_course",
|
|
})
|
|
}
|
|
|
|
targetID, err := strconv.ParseInt(c.Query("target_id", "0"), 10, 64)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid target_id",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
if domain.RatingTargetType(targetType) == domain.RatingTargetApp {
|
|
targetID = 0
|
|
}
|
|
|
|
limit, err := strconv.ParseInt(c.Query("limit", "20"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid limit",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
offset, err := strconv.ParseInt(c.Query("offset", "0"), 10, 32)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid offset",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
ratings, err := h.ratingSvc.GetRatingsByTarget(c.Context(), domain.RatingTargetType(targetType), targetID, int32(limit), int32(offset))
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to retrieve ratings",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
res := make([]ratingRes, len(ratings))
|
|
for i, r := range ratings {
|
|
res[i] = mapRatingToRes(r)
|
|
}
|
|
|
|
return c.JSON(domain.Response{
|
|
Message: "Ratings retrieved successfully",
|
|
Data: res,
|
|
})
|
|
}
|
|
|
|
// GetRatingSummary godoc
|
|
// @Summary Get rating summary for a target
|
|
// @Description Returns the total count and average stars for a specific target
|
|
// @Tags ratings
|
|
// @Produce json
|
|
// @Param target_type query string true "Target type (app, course, sub_course)"
|
|
// @Param target_id query int true "Target ID (0 for app)"
|
|
// @Success 200 {object} domain.Response
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/ratings/summary [get]
|
|
func (h *Handler) GetRatingSummary(c *fiber.Ctx) error {
|
|
targetType := c.Query("target_type")
|
|
if !isValidTargetType(targetType) {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid target_type, must be one of: app, course, sub_course",
|
|
})
|
|
}
|
|
|
|
targetID, err := strconv.ParseInt(c.Query("target_id", "0"), 10, 64)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid target_id",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
if domain.RatingTargetType(targetType) == domain.RatingTargetApp {
|
|
targetID = 0
|
|
}
|
|
|
|
summary, err := h.ratingSvc.GetRatingSummary(c.Context(), domain.RatingTargetType(targetType), targetID)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to retrieve rating summary",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(domain.Response{
|
|
Message: "Rating summary retrieved successfully",
|
|
Data: ratingSummaryRes{
|
|
TotalCount: summary.TotalCount,
|
|
AverageStars: summary.AverageStars,
|
|
},
|
|
})
|
|
}
|
|
|
|
// GetMyRatings godoc
|
|
// @Summary Get all my ratings
|
|
// @Description Returns all ratings submitted by the current user
|
|
// @Tags ratings
|
|
// @Produce json
|
|
// @Success 200 {object} domain.Response
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/ratings/me/all [get]
|
|
func (h *Handler) GetMyRatings(c *fiber.Ctx) error {
|
|
userID := c.Locals("user_id").(int64)
|
|
|
|
ratings, err := h.ratingSvc.GetUserRatings(c.Context(), userID)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to retrieve ratings",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
res := make([]ratingRes, len(ratings))
|
|
for i, r := range ratings {
|
|
res[i] = mapRatingToRes(r)
|
|
}
|
|
|
|
return c.JSON(domain.Response{
|
|
Message: "Ratings retrieved successfully",
|
|
Data: res,
|
|
})
|
|
}
|
|
|
|
// DeleteRating godoc
|
|
// @Summary Delete a rating
|
|
// @Description Deletes a rating by ID for the current user
|
|
// @Tags ratings
|
|
// @Produce json
|
|
// @Param id path int true "Rating ID"
|
|
// @Success 200 {object} domain.Response
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/ratings/{id} [delete]
|
|
func (h *Handler) DeleteRating(c *fiber.Ctx) error {
|
|
userID := c.Locals("user_id").(int64)
|
|
|
|
ratingID, err := strconv.ParseInt(c.Params("id"), 10, 64)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid rating ID",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
if err := h.ratingSvc.DeleteRating(c.Context(), ratingID, userID); err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to delete rating",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(domain.Response{
|
|
Message: "Rating deleted successfully",
|
|
})
|
|
}
|