added ratings and updated ebook struct

This commit is contained in:
2026-01-22 20:54:39 -05:00
parent e32fda6b65
commit e5ca12117c
15 changed files with 668 additions and 26 deletions
+157
View File
@@ -4,6 +4,7 @@ import (
"bookmann/internal/database"
"net/http"
"strconv"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
@@ -20,6 +21,17 @@ func NewHandler(db *database.Queries) *Handler {
}
}
// parseDate parses a date string in YYYY-MM-DD format
func parseDate(dateStr string) time.Time {
if dateStr == "" {
return time.Time{}
}
if t, err := time.Parse("2006-01-02", dateStr); err == nil {
return t
}
return time.Time{}
}
func SetupRoutes(g *echo.Group, db *database.Queries) {
h := NewHandler(db)
@@ -31,6 +43,12 @@ func SetupRoutes(g *echo.Group, db *database.Queries) {
g.GET("/ebooks/:id/progress", h.GetReadingProgress)
g.PUT("/ebooks/:id/progress", h.UpdateReadingProgress)
g.GET("/ebooks/:id/rating", h.GetEbookRating)
g.POST("/ebooks/:id/rating", h.CreateOrUpdateEbookRating)
g.PUT("/ebooks/:id/rating", h.CreateOrUpdateEbookRating)
g.DELETE("/ebooks/:id/rating", h.DeleteEbookRating)
g.GET("/ebooks/:id/ratings", h.GetEbookRatings)
}
// ListEbooks handles GET /api/ebooks
@@ -89,6 +107,13 @@ type CreateEbookRequest struct {
FileSize int64 `json:"file_size" validate:"required,min=1"`
MimeType string `json:"mime_type" validate:"required"`
CoverImagePath string `json:"cover_image_path"`
Series string `json:"series"`
SeriesNumber int32 `json:"series_number"`
Tags string `json:"tags"`
ASIN string `json:"asin"`
DatePublished string `json:"date_published"`
Publisher string `json:"publisher"`
Contributors string `json:"contributors"`
}
// CreateEbook handles POST /api/ebooks
@@ -111,6 +136,13 @@ func (h *Handler) CreateEbook(c echo.Context) error {
FileSize: pgtype.Int8{Int64: req.FileSize, Valid: req.FileSize > 0},
MimeType: pgtype.Text{String: req.MimeType, Valid: req.MimeType != ""},
CoverImagePath: pgtype.Text{String: req.CoverImagePath, Valid: req.CoverImagePath != ""},
Series: pgtype.Text{String: req.Series, Valid: req.Series != ""},
SeriesNumber: pgtype.Int4{Int32: req.SeriesNumber, Valid: req.SeriesNumber > 0},
Tags: pgtype.Text{String: req.Tags, Valid: req.Tags != ""},
Asin: pgtype.Text{String: req.ASIN, Valid: req.ASIN != ""},
DatePublished: pgtype.Date{Time: parseDate(req.DatePublished), Valid: req.DatePublished != ""},
Publisher: pgtype.Text{String: req.Publisher, Valid: req.Publisher != ""},
Contributors: pgtype.Text{String: req.Contributors, Valid: req.Contributors != ""},
})
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
@@ -126,6 +158,13 @@ type UpdateEbookRequest struct {
ISBN string `json:"isbn"`
Description string `json:"description"`
CoverImagePath string `json:"cover_image_path"`
Series string `json:"series"`
SeriesNumber int32 `json:"series_number"`
Tags string `json:"tags"`
ASIN string `json:"asin"`
DatePublished string `json:"date_published"`
Publisher string `json:"publisher"`
Contributors string `json:"contributors"`
}
// UpdateEbook handles PUT /api/ebooks/:id
@@ -152,6 +191,13 @@ func (h *Handler) UpdateEbook(c echo.Context) error {
Isbn: pgtype.Text{String: req.ISBN, Valid: req.ISBN != ""},
Description: pgtype.Text{String: req.Description, Valid: req.Description != ""},
CoverImagePath: pgtype.Text{String: req.CoverImagePath, Valid: req.CoverImagePath != ""},
Series: pgtype.Text{String: req.Series, Valid: req.Series != ""},
SeriesNumber: pgtype.Int4{Int32: req.SeriesNumber, Valid: req.SeriesNumber > 0},
Tags: pgtype.Text{String: req.Tags, Valid: req.Tags != ""},
Asin: pgtype.Text{String: req.ASIN, Valid: req.ASIN != ""},
DatePublished: pgtype.Date{Time: parseDate(req.DatePublished), Valid: req.DatePublished != ""},
Publisher: pgtype.Text{String: req.Publisher, Valid: req.Publisher != ""},
Contributors: pgtype.Text{String: req.Contributors, Valid: req.Contributors != ""},
})
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
@@ -250,3 +296,114 @@ func (h *Handler) UpdateReadingProgress(c echo.Context) error {
return c.JSON(http.StatusOK, progress)
}
// CreateOrUpdateEbookRatingRequest represents the request for creating/updating an ebook rating
type CreateOrUpdateEbookRatingRequest struct {
Rating int32 `json:"rating" validate:"required,min=1,max=5"`
}
// GetEbookRating handles GET /api/ebooks/:id/rating
func (h *Handler) GetEbookRating(c echo.Context) error {
ebookIdStr := c.Param("id")
userID := c.Get("user_id").(string)
ebookId, err := uuid.Parse(ebookIdStr)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid ebook id"})
}
userUUID, err := uuid.Parse(userID)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
}
rating, err := h.db.GetEbookRating(c.Request().Context(), database.GetEbookRatingParams{
EbookID: pgtype.UUID{Bytes: ebookId, Valid: true},
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
})
if err != nil {
// If no rating found, return 404
return c.JSON(http.StatusNotFound, map[string]string{"error": "rating not found"})
}
return c.JSON(http.StatusOK, rating)
}
// CreateOrUpdateEbookRating handles POST/PUT /api/ebooks/:id/rating
func (h *Handler) CreateOrUpdateEbookRating(c echo.Context) error {
ebookIdStr := c.Param("id")
userID := c.Get("user_id").(string)
ebookId, err := uuid.Parse(ebookIdStr)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid ebook id"})
}
userUUID, err := uuid.Parse(userID)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
}
var req CreateOrUpdateEbookRatingRequest
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
}
if err := c.Validate(&req); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}
rating, err := h.db.CreateEbookRating(c.Request().Context(), database.CreateEbookRatingParams{
EbookID: pgtype.UUID{Bytes: ebookId, Valid: true},
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
Rating: req.Rating,
})
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return c.JSON(http.StatusOK, rating)
}
// DeleteEbookRating handles DELETE /api/ebooks/:id/rating
func (h *Handler) DeleteEbookRating(c echo.Context) error {
ebookIdStr := c.Param("id")
userID := c.Get("user_id").(string)
ebookId, err := uuid.Parse(ebookIdStr)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid ebook id"})
}
userUUID, err := uuid.Parse(userID)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
}
err = h.db.DeleteEbookRating(c.Request().Context(), database.DeleteEbookRatingParams{
EbookID: pgtype.UUID{Bytes: ebookId, Valid: true},
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
})
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return c.NoContent(http.StatusNoContent)
}
// GetEbookRatings handles GET /api/ebooks/:id/ratings
func (h *Handler) GetEbookRatings(c echo.Context) error {
ebookIdStr := c.Param("id")
ebookId, err := uuid.Parse(ebookIdStr)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid ebook id"})
}
ratings, err := h.db.GetEbookRatings(c.Request().Context(), pgtype.UUID{Bytes: ebookId, Valid: true})
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return c.JSON(http.StatusOK, ratings)
}