Add library-based scanning and media item endpoints

- Add POST /api/libraries/:id/scan endpoint for admin library scanning
- Add GET /api/libraries/:id/media-items endpoint for library media items
- Move /api/libraries/types to public endpoint (no auth required)
- Update ScanEbooks handler to support library_id parameter
This commit is contained in:
2026-01-30 20:16:22 -05:00
parent 95fe849eeb
commit a9fdd44471
2 changed files with 57 additions and 12 deletions
+20 -4
View File
@@ -115,6 +115,13 @@ func main() {
// Protected routes
protected := e.Group("/api", jwtMiddleware)
// Setup ebook handler routes first (so we can use it for library scan)
h := handlers.SetupRoutes(protected, queries)
// Public library types endpoint (no authentication required)
e.GET("/api/libraries/types", libraryHandler.GetLibraryTypes)
protected.GET("/auth/profile", authHandler.GetProfile)
protected.PUT("/auth/profile", authHandler.UpdateProfile)
protected.POST("/auth/refresh", authHandler.RefreshAccessToken)
@@ -126,7 +133,6 @@ func main() {
// Library management routes
library := protected.Group("/libraries")
library.GET("/types", libraryHandler.GetLibraryTypes)
// Admin-only library routes
adminLibrary := library.Group("", handlers.AdminMiddleware)
@@ -139,6 +145,19 @@ func main() {
adminLibrary.GET("/:id/folders", libraryHandler.GetLibraryFolders)
adminLibrary.DELETE("/:id/folders", libraryHandler.DeleteLibraryFolder)
adminLibrary.GET("/:id/stats", libraryHandler.GetLibraryStats)
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.ScanEbooks(c)
})
adminLibrary.GET("/:id/media-items", func(c echo.Context) error {
libraryID := c.Param("id")
c.QueryParams().Set("library_id", libraryID)
return h.ListMediaItems(c)
})
// User library visibility control
protected.POST("/libraries/visibility", libraryHandler.SetLibraryVisibility)
@@ -173,9 +192,6 @@ func main() {
// Static files
e.Static("/static", "web/static")
// Routes
h := handlers.SetupRoutes(protected, queries)
// Start scheduler for auto-scanning
go h.StartScheduler()
defer h.StopScheduler()
+37 -8
View File
@@ -124,12 +124,22 @@ func SetupRoutes(g *echo.Group, db *database.Queries) *Handler {
// ScanEbooksRequest represents the request for scanning ebooks
type ScanEbooksRequest struct {
FolderPaths []string `json:"folder_paths,omitempty"`
LibraryID string `json:"library_id,omitempty"`
}
// ScanEbooks handles POST /api/scanner/scan (now runs in background)
func (h *Handler) ScanEbooks(c echo.Context) error {
var req ScanEbooksRequest
if err := c.Bind(&req); err != nil {
// Check if scan_request is set in context (from library scan route)
if scanReq, ok := c.Get("scan_request").(map[string]interface{}); ok {
if libraryID, ok := scanReq["library_id"].(string); ok {
req.LibraryID = libraryID
}
}
// Bind request body if provided (for direct scanner/scan calls)
if err := c.Bind(&req); err != nil && req.LibraryID == "" {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
}
@@ -142,14 +152,33 @@ func (h *Handler) ScanEbooks(c echo.Context) error {
var folderPaths []string
// If folder paths provided in request, use them
// Otherwise, use user's saved folders
if len(req.FolderPaths) > 0 {
// If library_id is provided, fetch folders from library
if req.LibraryID != "" {
libraryUUID, err := uuid.Parse(req.LibraryID)
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
}
// Fetch library folders from database
libraryFolders, err := h.db.GetLibraryFolders(c.Request().Context(), pgtype.UUID{Bytes: [16]byte(libraryUUID), Valid: true})
if err != nil {
return c.JSON(http.StatusNotFound, map[string]string{"error": "library not found or has no folders"})
}
// Extract folder paths
for _, folder := range libraryFolders {
folderPaths = append(folderPaths, folder.FolderPath)
}
if len(folderPaths) == 0 {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "library has no folders configured"})
}
} else if len(req.FolderPaths) > 0 {
// Use folder paths from request
folderPaths = req.FolderPaths
} else {
// TODO: Replace with library-based folder scanning
// For now, require folder paths in request
return c.JSON(http.StatusBadRequest, map[string]string{"error": "folder_paths required for scanning"})
// Neither library_id nor folder_paths provided
return c.JSON(http.StatusBadRequest, map[string]string{"error": "either library_id or folder_paths required for scanning"})
}
jobID := uuid.New().String()
@@ -158,7 +187,7 @@ func (h *Handler) ScanEbooks(c echo.Context) error {
ID: jobID,
Type: services.JobTypeScan,
Params: map[string]interface{}{
"library_id": userID,
"library_id": req.LibraryID,
"folders": folderPaths,
"admin_id": userUUID.String(),
"db": h.db,