package handlers import ( "bookmann/internal/database" "net/http" "strconv" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" "github.com/labstack/echo/v4" ) type Handler struct { db *database.Queries } func NewHandler(db *database.Queries) *Handler { return &Handler{ db: db, } } func SetupRoutes(g *echo.Group, db *database.Queries) { h := NewHandler(db) g.GET("/ebooks", h.ListEbooks) g.GET("/ebooks/:id", h.GetEbook) g.POST("/ebooks", h.CreateEbook) g.PUT("/ebooks/:id", h.UpdateEbook) g.DELETE("/ebooks/:id", h.DeleteEbook) g.GET("/ebooks/:id/progress", h.GetReadingProgress) g.PUT("/ebooks/:id/progress", h.UpdateReadingProgress) } // ListEbooks handles GET /api/ebooks func (h *Handler) ListEbooks(c echo.Context) error { limitStr := c.QueryParam("limit") offsetStr := c.QueryParam("offset") limit := int32(20) // default if limitStr != "" { if l, err := strconv.Atoi(limitStr); err == nil { limit = int32(l) } } offset := int32(0) if offsetStr != "" { if o, err := strconv.Atoi(offsetStr); err == nil { offset = int32(o) } } ebooks, err := h.db.ListEbooks(c.Request().Context(), database.ListEbooksParams{ Limit: limit, Offset: offset, }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, ebooks) } // GetEbook handles GET /api/ebooks/:id func (h *Handler) GetEbook(c echo.Context) error { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid id"}) } ebook, err := h.db.GetEbook(c.Request().Context(), pgtype.UUID{Bytes: id, Valid: true}) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, ebook) } // CreateEbookRequest represents the request for creating an ebook type CreateEbookRequest struct { Title string `json:"title" validate:"required,min=1,max=500"` Author string `json:"author"` ISBN string `json:"isbn"` Description string `json:"description"` FilePath string `json:"file_path" validate:"required"` FileSize int64 `json:"file_size" validate:"required,min=1"` MimeType string `json:"mime_type" validate:"required"` CoverImagePath string `json:"cover_image_path"` } // CreateEbook handles POST /api/ebooks func (h *Handler) CreateEbook(c echo.Context) error { var req CreateEbookRequest 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()}) } ebook, err := h.db.CreateEbook(c.Request().Context(), database.CreateEbookParams{ Title: req.Title, Author: pgtype.Text{String: req.Author, Valid: req.Author != ""}, Isbn: pgtype.Text{String: req.ISBN, Valid: req.ISBN != ""}, Description: pgtype.Text{String: req.Description, Valid: req.Description != ""}, FilePath: req.FilePath, 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 != ""}, }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusCreated, ebook) } // UpdateEbookRequest represents the request for updating an ebook type UpdateEbookRequest struct { Title string `json:"title" validate:"required,min=1,max=500"` Author string `json:"author"` ISBN string `json:"isbn"` Description string `json:"description"` CoverImagePath string `json:"cover_image_path"` } // UpdateEbook handles PUT /api/ebooks/:id func (h *Handler) UpdateEbook(c echo.Context) error { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid id"}) } var req UpdateEbookRequest 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()}) } ebook, err := h.db.UpdateEbook(c.Request().Context(), database.UpdateEbookParams{ ID: pgtype.UUID{Bytes: id, Valid: true}, Title: req.Title, Author: pgtype.Text{String: req.Author, Valid: req.Author != ""}, 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 != ""}, }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, ebook) } // DeleteEbook handles DELETE /api/ebooks/:id func (h *Handler) DeleteEbook(c echo.Context) error { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid id"}) } err = h.db.DeleteEbook(c.Request().Context(), pgtype.UUID{Bytes: id, Valid: true}) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.NoContent(http.StatusNoContent) } // GetReadingProgress handles GET /api/ebooks/:id/progress func (h *Handler) GetReadingProgress(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"}) } progress, err := h.db.GetReadingProgress(c.Request().Context(), database.GetReadingProgressParams{ EbookID: pgtype.UUID{Bytes: ebookId, Valid: true}, UserID: pgtype.UUID{Bytes: userUUID, Valid: true}, }) if err != nil { // If no progress found, return default return c.JSON(http.StatusOK, map[string]interface{}{ "ebook_id": ebookIdStr, "user_id": userID, "current_page": 0, "total_pages": nil, }) } return c.JSON(http.StatusOK, progress) } // UpdateReadingProgressRequest represents the request for updating reading progress type UpdateReadingProgressRequest struct { CurrentPage int32 `json:"current_page" validate:"required,min=0"` TotalPages int32 `json:"total_pages" validate:"omitempty,min=1"` } // UpdateReadingProgress handles PUT /api/ebooks/:id/progress func (h *Handler) UpdateReadingProgress(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 UpdateReadingProgressRequest 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()}) } progress, err := h.db.UpdateReadingProgress(c.Request().Context(), database.UpdateReadingProgressParams{ EbookID: pgtype.UUID{Bytes: ebookId, Valid: true}, UserID: pgtype.UUID{Bytes: userUUID, Valid: true}, CurrentPage: pgtype.Int4{Int32: req.CurrentPage, Valid: true}, TotalPages: pgtype.Int4{Int32: req.TotalPages, Valid: req.TotalPages > 0}, }) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, progress) }