feat: Add admin-only protection to ebook operations

- Update ebook handlers to require admin role for CRUD operations
- Modify folder operations to be admin-only
- Update ebook scanner to associate books with admin who added them
- Add admin ID tracking to CreateEbook operations
- Restrict scanner operations to admin users only
This commit is contained in:
2026-01-26 16:55:42 -05:00
parent f5bfac996d
commit 734bad4697
2 changed files with 39 additions and 8 deletions
+33 -8
View File
@@ -47,25 +47,29 @@ func parseDate(dateStr string) time.Time {
func SetupRoutes(g *echo.Group, db *database.Queries) {
h := NewHandler(db)
// Public routes (all authenticated users)
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)
// User-specific routes
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)
// Scanner routes
g.POST("/scanner/scan", h.ScanEbooks)
g.POST("/scanner/start", h.StartScanner)
g.POST("/scanner/stop", h.StopScanner)
// Admin-only routes
admin := g.Group("", AdminMiddleware)
admin.POST("/ebooks", h.CreateEbook)
admin.PUT("/ebooks/:id", h.UpdateEbook)
admin.DELETE("/ebooks/:id", h.DeleteEbook)
// Scanner routes (admin only)
admin.POST("/scanner/scan", h.ScanEbooks)
admin.POST("/scanner/start", h.StartScanner)
admin.POST("/scanner/stop", h.StopScanner)
}
// ListEbooks handles GET /api/ebooks
@@ -147,6 +151,13 @@ func (h *Handler) CreateEbook(c echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
}
// Get admin ID from JWT token
userID := c.Get("user_id").(string)
userUUID, err := uuid.Parse(userID)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"})
}
ebook, err := h.db.CreateEbook(c.Request().Context(), database.CreateEbookParams{
Title: req.Title,
Author: pgtype.Text{String: req.Author, Valid: req.Author != ""},
@@ -163,6 +174,7 @@ func (h *Handler) CreateEbook(c echo.Context) error {
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 != ""},
AddedByAdminID: pgtype.UUID{Bytes: userUUID, Valid: true},
})
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
@@ -486,6 +498,9 @@ func (h *Handler) ScanEbooks(c echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid folder paths: " + err.Error()})
}
// Set the admin ID for ebook association
h.scanner.SetAdminID(pgtype.UUID{Bytes: userUUID, Valid: true})
// Perform the scan
if err := h.scanner.ScanFolders(h.ctx); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "scan failed: " + err.Error()})
@@ -504,11 +519,21 @@ func (h *Handler) StartScanner(c echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
}
// Get admin ID from JWT token
userID := c.Get("user_id").(string)
userUUID, err := uuid.Parse(userID)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"})
}
// Set the folder paths
if err := h.scanner.SetFolders(req.FolderPaths); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid folder paths: " + err.Error()})
}
// Set the admin ID for ebook association
h.scanner.SetAdminID(pgtype.UUID{Bytes: userUUID, Valid: true})
// Start watching for changes
h.scanner.WatchChanges(h.ctx)
+6
View File
@@ -36,6 +36,7 @@ type EbookScanner struct {
db *database.Queries
watcher *fsnotify.Watcher
folders []string
adminID pgtype.UUID
}
func NewEbookScanner(db *database.Queries) *EbookScanner {
@@ -50,6 +51,10 @@ func NewEbookScanner(db *database.Queries) *EbookScanner {
}
}
func (s *EbookScanner) SetAdminID(adminID pgtype.UUID) {
s.adminID = adminID
}
func (s *EbookScanner) SetFolders(folders []string) error {
s.folders = folders
@@ -291,6 +296,7 @@ func (s *EbookScanner) processEbookFile(ctx context.Context, path string) error
DatePublished: pgtype.Date{Time: metadata.PublishDate, Valid: !metadata.PublishDate.IsZero()},
Contributors: pgtype.Text{String: metadata.Contributors, Valid: metadata.Contributors != ""},
Tags: pgtype.Text{String: metadata.Tags, Valid: metadata.Tags != ""},
AddedByAdminID: s.adminID,
})
return err