refactor: rename bulk operations from /api/books/ to /api/media-items/

- Rename routes: /api/books/bulk-{delete,update} → /api/media-items/bulk-{delete,update}
- Rename route: /api/books/:uuid/download → /api/media-items/:uuid/download
- Update request fields: book_ids → media_item_ids
- Update response fields: success → deleted/updated
- Update result fields: book_id → media_item_id
- Update collection handler: success → added

This change improves API semantic correctness as the system handles
multiple media types (ebooks, comics, manga), not just books.

BREAKING CHANGE: All bulk operation endpoints and field names renamed
This commit is contained in:
2026-02-10 19:56:59 -05:00
parent f72c552241
commit 03225cf6e2
3 changed files with 78 additions and 78 deletions
+1 -1
View File
@@ -810,7 +810,7 @@ func (h *CollectionHandler) HandleBulkAddBooks(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]interface{}{
"results": results,
"total": len(results),
"success": successCount,
"added": successCount,
"failed": failedCount,
})
}
+72 -72
View File
@@ -341,156 +341,156 @@ func (h *MediaHandler) ClearShelf(c echo.Context) error {
// Bulk Operations
// POST /api/books/bulk-delete
// Bulk delete books
// POST /api/media-items/bulk-delete
// Bulk delete media items
func (h *MediaHandler) HandleBulkDelete(c echo.Context) error {
var req struct {
BookIDs []string `json:"book_ids" validate:"required"`
MediaItemIDs []string `json:"media_item_ids" validate:"required"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request"})
}
if len(req.BookIDs) == 0 {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "book_ids required"})
if len(req.MediaItemIDs) == 0 {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "media_item_ids required"})
}
results := make([]map[string]interface{}, 0)
successCount := 0
failedCount := 0
for _, bookIDStr := range req.BookIDs {
bookID, err := uuid.Parse(bookIDStr)
for _, mediaIDStr := range req.MediaItemIDs {
mediaID, err := uuid.Parse(mediaIDStr)
if err != nil {
results = append(results, map[string]interface{}{
"book_id": bookIDStr,
"status": "error",
"error": "Invalid UUID",
"media_item_id": mediaIDStr,
"status": "error",
"error": "Invalid UUID",
})
failedCount++
continue
}
bookUUID := pgtype.UUID{Bytes: bookID, Valid: true}
mediaUUID := pgtype.UUID{Bytes: mediaID, Valid: true}
book, err := h.db.GetMediaItem(c.Request().Context(), bookUUID)
media, err := h.db.GetMediaItem(c.Request().Context(), mediaUUID)
if err != nil {
results = append(results, map[string]interface{}{
"book_id": bookIDStr,
"status": "error",
"error": "Book not found",
"media_item_id": mediaIDStr,
"status": "error",
"error": "Media item not found",
})
failedCount++
continue
}
err = h.db.DeleteMediaItem(c.Request().Context(), bookUUID)
err = h.db.DeleteMediaItem(c.Request().Context(), mediaUUID)
if err != nil {
results = append(results, map[string]interface{}{
"book_id": bookIDStr,
"status": "error",
"error": err.Error(),
"media_item_id": mediaIDStr,
"status": "error",
"error": err.Error(),
})
failedCount++
continue
}
if book.FilePath != "" {
os.Remove(book.FilePath)
if media.FilePath != "" {
os.Remove(media.FilePath)
}
results = append(results, map[string]interface{}{
"book_id": bookIDStr,
"status": "success",
"media_item_id": mediaIDStr,
"status": "success",
})
successCount++
}
return c.JSON(http.StatusOK, map[string]interface{}{
"results": results,
"total": len(req.BookIDs),
"success": successCount,
"total": len(req.MediaItemIDs),
"deleted": successCount,
"failed": failedCount,
})
}
// POST /api/books/bulk-update
// Bulk update book metadata
// POST /api/media-items/bulk-update
// Bulk update media item metadata
func (h *MediaHandler) HandleBulkUpdate(c echo.Context) error {
var req struct {
Updates []struct {
BookID string `json:"book_id" validate:"required"`
Updates struct {
MediaItemUpdates []struct {
MediaItemID string `json:"media_item_id" validate:"required"`
Updates struct {
Title *string `json:"title,omitempty"`
Author *string `json:"author,omitempty"`
Genre *string `json:"genre,omitempty"`
Language *string `json:"language,omitempty"`
Tags []string `json:"tags,omitempty"`
} `json:"updates"`
} `json:"updates" validate:"required"`
} `json:"media_item_updates" validate:"required"`
}
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request"})
}
if len(req.Updates) == 0 {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "updates required"})
if len(req.MediaItemUpdates) == 0 {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "media_item_updates required"})
}
results := make([]map[string]interface{}, 0)
successCount := 0
failedCount := 0
for _, update := range req.Updates {
bookID, err := uuid.Parse(update.BookID)
for _, update := range req.MediaItemUpdates {
mediaID, err := uuid.Parse(update.MediaItemID)
if err != nil {
results = append(results, map[string]interface{}{
"book_id": update.BookID,
"status": "error",
"error": "Invalid UUID",
"media_item_id": update.MediaItemID,
"status": "error",
"error": "Invalid UUID",
})
failedCount++
continue
}
bookUUID := pgtype.UUID{Bytes: bookID, Valid: true}
mediaUUID := pgtype.UUID{Bytes: mediaID, Valid: true}
existingBook, err := h.db.GetMediaItem(c.Request().Context(), bookUUID)
existingMedia, err := h.db.GetMediaItem(c.Request().Context(), mediaUUID)
if err != nil {
results = append(results, map[string]interface{}{
"book_id": update.BookID,
"status": "error",
"error": "Book not found",
"media_item_id": update.MediaItemID,
"status": "error",
"error": "Media item not found",
})
failedCount++
continue
}
updateParams := database.UpdateMediaItemParams{
ID: bookUUID,
Title: existingBook.Title,
Author: existingBook.Author,
Genre: existingBook.Genre,
Language: existingBook.Language,
Tags: existingBook.Tags,
TagsSearch: existingBook.TagsSearch,
Description: existingBook.Description,
Publisher: existingBook.Publisher,
CopyrightYear: existingBook.CopyrightYear,
Isbn: existingBook.Isbn,
Series: existingBook.Series,
SeriesNumber: existingBook.SeriesNumber,
Asin: existingBook.Asin,
DatePublished: existingBook.DatePublished,
Contributors: existingBook.Contributors,
ContributorsSearch: existingBook.ContributorsSearch,
Edition: existingBook.Edition,
PageCount: existingBook.PageCount,
GoodreadsID: existingBook.GoodreadsID,
OpenlibraryID: existingBook.OpenlibraryID,
CoverImagePath: existingBook.CoverImagePath,
ID: mediaUUID,
Title: existingMedia.Title,
Author: existingMedia.Author,
Genre: existingMedia.Genre,
Language: existingMedia.Language,
Tags: existingMedia.Tags,
TagsSearch: existingMedia.TagsSearch,
Description: existingMedia.Description,
Publisher: existingMedia.Publisher,
CopyrightYear: existingMedia.CopyrightYear,
Isbn: existingMedia.Isbn,
Series: existingMedia.Series,
SeriesNumber: existingMedia.SeriesNumber,
Asin: existingMedia.Asin,
DatePublished: existingMedia.DatePublished,
Contributors: existingMedia.Contributors,
ContributorsSearch: existingMedia.ContributorsSearch,
Edition: existingMedia.Edition,
PageCount: existingMedia.PageCount,
GoodreadsID: existingMedia.GoodreadsID,
OpenlibraryID: existingMedia.OpenlibraryID,
CoverImagePath: existingMedia.CoverImagePath,
}
if update.Updates.Title != nil {
@@ -515,25 +515,25 @@ func (h *MediaHandler) HandleBulkUpdate(c echo.Context) error {
_, err = h.db.UpdateMediaItem(c.Request().Context(), updateParams)
if err != nil {
results = append(results, map[string]interface{}{
"book_id": update.BookID,
"status": "error",
"error": err.Error(),
"media_item_id": update.MediaItemID,
"status": "error",
"error": err.Error(),
})
failedCount++
continue
}
results = append(results, map[string]interface{}{
"book_id": update.BookID,
"status": "success",
"media_item_id": update.MediaItemID,
"status": "success",
})
successCount++
}
return c.JSON(http.StatusOK, map[string]interface{}{
"results": results,
"total": len(req.Updates),
"success": successCount,
"total": len(req.MediaItemUpdates),
"updated": successCount,
"failed": failedCount,
})
}
+5 -5
View File
@@ -48,7 +48,7 @@ func registerMediaRoutes(cfg *Config) {
admin.DELETE("/media-items/:id", cfg.MediaHandler.DeleteMediaItem)
// Download route (public)
e.GET("/api/books/:uuid/download", cfg.MediaHandler.DownloadBook)
e.GET("/api/media-items/:uuid/download", cfg.MediaHandler.DownloadBook)
// Shelf management (protected)
protected.POST("/devices/:id/shelves", cfg.MediaHandler.AddToShelf)
@@ -56,8 +56,8 @@ func registerMediaRoutes(cfg *Config) {
protected.DELETE("/devices/:id/shelves", cfg.MediaHandler.RemoveFromShelf)
protected.DELETE("/devices/:id/shelves/clear", cfg.MediaHandler.ClearShelf)
// Bulk book operations (protected)
books := protected.Group("/books")
books.POST("/bulk-delete", cfg.MediaHandler.HandleBulkDelete)
books.POST("/bulk-update", cfg.MediaHandler.HandleBulkUpdate)
// Bulk media operations (protected)
mediaItems := protected.Group("/media-items")
mediaItems.POST("/bulk-delete", cfg.MediaHandler.HandleBulkDelete)
mediaItems.POST("/bulk-update", cfg.MediaHandler.HandleBulkUpdate)
}