feat(api): add POST /api/media-items/:id/rescan endpoint
Admin-only endpoint that re-extracts metadata and cover art for a single book from the file on disk via MediaScanner.RescanMediaItem and returns the updated media item. Normal library scans skip unchanged files, so this gives a targeted way to backfill covers for previously imported books. Includes a Bruno request alongside the existing media-items collection.
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
info:
|
||||||
|
name: Rescan Media Item
|
||||||
|
type: http
|
||||||
|
seq: 9
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: POST
|
||||||
|
url: "{{base_url}}/api/media-items/{{media_item_id}}/rescan"
|
||||||
|
headers:
|
||||||
|
- name: ""
|
||||||
|
value: application/json
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
scripts:
|
||||||
|
- type: tests
|
||||||
|
code: |-
|
||||||
|
test_rescan_media_item_success(status, headers, body) {
|
||||||
|
if (status !== 200) {
|
||||||
|
throw new Error("Expected status 200, got " + status);
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
|
|
||||||
|
docs: |-
|
||||||
|
## Rescan Media Item
|
||||||
|
|
||||||
|
Re-extracts metadata and cover art for a single media item from the file on disk.
|
||||||
|
|
||||||
|
**Method:** POST
|
||||||
|
|
||||||
|
**Endpoint:** /api/media-items/{id}/rescan
|
||||||
@@ -1309,6 +1309,42 @@ func (mh *MediaHandler) UpdateMediaItem(c *echo.Context) error {
|
|||||||
return c.JSON(http.StatusOK, item)
|
return c.JSON(http.StatusOK, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RescanMediaItem handles POST /api/media-items/:id/rescan (admin only)
|
||||||
|
func (mh *MediaHandler) RescanMediaItem(c *echo.Context) error {
|
||||||
|
user := MustGetAuthenticatedUser(c)
|
||||||
|
|
||||||
|
if user.Role != "admin" {
|
||||||
|
return c.JSON(http.StatusForbidden, map[string]string{"error": "admin access required"})
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaID := c.Param("id")
|
||||||
|
mediaUUID, err := uuid.Parse(mediaID)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid media item id"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := mh.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: mediaUUID, Valid: true}); err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
return c.JSON(http.StatusNotFound, map[string]string{"error": "media item not found"})
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := services.NewMediaScanner(mh.db)
|
||||||
|
defer scanner.Close()
|
||||||
|
|
||||||
|
if err := scanner.RescanMediaItem(c.Request().Context(), pgtype.UUID{Bytes: mediaUUID, Valid: true}); err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := mh.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: mediaUUID, Valid: true})
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, item)
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteMediaItem handles DELETE /api/media-items/:id (admin only)
|
// DeleteMediaItem handles DELETE /api/media-items/:id (admin only)
|
||||||
func (mh *MediaHandler) DeleteMediaItem(c *echo.Context) error {
|
func (mh *MediaHandler) DeleteMediaItem(c *echo.Context) error {
|
||||||
user := MustGetAuthenticatedUser(c)
|
user := MustGetAuthenticatedUser(c)
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ func registerMediaRoutes(cfg *Config) {
|
|||||||
// Admin-only media routes
|
// Admin-only media routes
|
||||||
admin.POST("/media-items", cfg.MediaHandler.CreateMediaItem)
|
admin.POST("/media-items", cfg.MediaHandler.CreateMediaItem)
|
||||||
admin.PUT("/media-items/:id", cfg.MediaHandler.UpdateMediaItem)
|
admin.PUT("/media-items/:id", cfg.MediaHandler.UpdateMediaItem)
|
||||||
|
admin.POST("/media-items/:id/rescan", cfg.MediaHandler.RescanMediaItem)
|
||||||
admin.DELETE("/media-items/:id", cfg.MediaHandler.DeleteMediaItem)
|
admin.DELETE("/media-items/:id", cfg.MediaHandler.DeleteMediaItem)
|
||||||
|
|
||||||
// Shelf management (protected)
|
// Shelf management (protected)
|
||||||
|
|||||||
Reference in New Issue
Block a user