Files
bookhoard/internal/router/library.go
T
John O'Keefe 6f37be59fc feat(issues): retry a processing issue through the individual scanner
POST /api/libraries/:id/issues/:issueId/retry (admin, family-lens gated
via the existing gateIssue) re-runs the work that produced an open issue
— deliberately NOT a library-wide rescan. Two shapes mirror the two
ledger entry kinds:

- item-backed issues rescan their media item through the book-detail
  RescanMediaItem mechanism (self-resolving folders, multi-file
  audiobook routing, write-after-success guards) and then close the
  issue (RetryMediaItemIssue);
- row-less import_failed issues re-import the single file through the
  watcher's standalone processMediaFile path (RetryFailedImport: folders
  resolved RescanMediaItem-style so paths stay library-root-relative),
  whose create-path self-heal plus an explicit path resolve closes the
  issue even when the content was already imported by a prior attempt.

Failures return the REAL underlying error so the UI can show why the
issue still exists; a vanished file is a 409 (ErrRetryFileMissing);
already-resolved issues reject re-retry.

Docs + bruno mirrors regenerated for the new route (the generator's seq
renumbering touches three unrelated bruno files as fallout; openapi
gains the path and the date bump), and the docs schema registry gains
the retry entry so TestDocsMatchRoutes passes.
2026-10-27 13:22:31 -04:00

68 lines
2.6 KiB
Go

package router
import (
"bookhoard/internal/handlers"
"github.com/labstack/echo/v5"
)
func registerLibraryRoutes(cfg *Config) {
e := cfg.Echo
// JWT middleware for protected routes
jwtMiddleware := createJWTMiddleware(cfg)
// Protected routes group
protected := e.Group("/api", jwtMiddleware)
// Create handler for library-specific convenience routes
h := cfg.ScannerHandler
// Public library types endpoint
e.GET("/api/libraries/types", cfg.LibraryHandler.GetLibraryTypes)
// Library management routes
library := protected.Group("/libraries")
// Admin-only library routes
adminLibrary := library.Group("", handlers.AdminMiddleware)
adminLibrary.POST("", cfg.LibraryHandler.CreateLibrary)
adminLibrary.GET("", cfg.LibraryHandler.ListLibraries)
adminLibrary.GET("/browse", cfg.LibraryHandler.BrowseDirectories)
adminLibrary.GET("/:id", cfg.LibraryHandler.GetLibrary)
adminLibrary.PUT("/:id", cfg.LibraryHandler.UpdateLibrary)
adminLibrary.DELETE("/:id", cfg.LibraryHandler.DeleteLibrary)
adminLibrary.POST("/:id/folders", cfg.LibraryHandler.AddLibraryFolder)
adminLibrary.GET("/:id/folders", cfg.LibraryHandler.GetLibraryFolders)
adminLibrary.DELETE("/:id/folders", cfg.LibraryHandler.DeleteLibraryFolder)
adminLibrary.GET("/:id/stats", cfg.LibraryHandler.GetLibraryStats)
adminLibrary.GET("/:id/issues/list", cfg.ProcessingIssuesHandler.ListProcessingIssues)
adminLibrary.GET("/:id/issues/stats", cfg.ProcessingIssuesHandler.GetProcessingIssueStats)
adminLibrary.POST("/:id/issues/:issueId/:mediaItemId/resolve", cfg.ProcessingIssuesHandler.ResolveProcessingIssue)
adminLibrary.POST("/:id/issues/:issueId/retry", cfg.ProcessingIssuesHandler.RetryProcessingIssue)
adminLibrary.DELETE("/:id/issues/:issueId", cfg.ProcessingIssuesHandler.DeleteProcessingIssue)
adminLibrary.POST("/:id/scan", func(c *echo.Context) error {
libraryID := c.Param("id")
scanReq := map[string]interface{}{
"library_id": libraryID,
}
c.Set("scan_request", scanReq)
return h.ScanLibrary(c)
})
adminLibrary.GET("/:id/media-items", func(c *echo.Context) error {
libraryID := c.Param("id")
c.QueryParams().Set("library_id", libraryID)
return cfg.MediaHandler.ListMediaItems(c)
})
// System scan settings (admin-only)
adminLibrary.GET("/scan-settings", cfg.SystemSettingsHandler.GetScanSettings)
adminLibrary.PUT("/scan-settings", cfg.SystemSettingsHandler.UpdateScanSettings)
// User library visibility control
userLibrary := library.Group("/visibility")
userLibrary.GET("", cfg.LibraryHandler.GetUserVisibleLibraries)
userLibrary.POST("", cfg.LibraryHandler.SetLibraryVisibility)
// Note: Remove endpoint may not exist - check handlers
}