Phase 2b + 3 of the metadata overhaul (plan §2.4, §3.1-3.7). Auto-playlist collections (§2.4): the scanner turns obviously-playlist sidecar series (explicit '(Reading Order)'/chronological/publication/ in-order/timeline markers only — precision over recall) into curated static collections: upsert by view_settings.imported_from_series then name (rename-safe), position set ONLY on insert (clean integers; 1.5/1a stay unpositioned rather than guessing), membership add-if-missing, never removed or re-ordered by rescans, provenance in description + view_settings, show_on_dashboard true (dashboard-eligible by design), 'scanner.auto_playlist_collections' setting (default on, Settings Registry + schema seed). Ordering (§3.1-3.2): collection listings order position ASC NULLS LAST then added_at ASC (was added_at DESC — walked playlists backwards); PUT /api/collections/:id/items/order applies full-order rewrites (owner-only, service-gated). hide_finished + Up Next (§3.3-3.4): GetCollectionItemsForDashboard gains viewer_user_id + hide_finished (LEFT JOIN reading_progress, COALESCE percentage < 0.999) and viewer_percentage rides the row for card graying; GetCollectionUpNext serves the collection's reading-queue head with the family-lens visible-library array enforced. Dashboard (§3.5): user collections are dashboard-ELIGIBLE BY DEFAULT — the show_on_dashboard gate (never writable for user rows since the column shipped) is gone from GetUserCollectionsForDashboard; hiding stays dashboard-prefs (hidden_collections) territory; hide_finished filtered-everything now emits a Complete section instead of silently vanishing; sections JSON carries is_playlist/complete. Series page ordering hardening (§3.6): GetSeriesBooks tiebreaks by title ASC — NULL/tied numbers previously returned heap (scan) order, which is what the user saw. Frontend custom-section-builder note: the builder has sent show_on_dashboard: true since its creation; the API never bound it. User-row visibility is now intentionally prefs-governed, so the field stays unbound by design (documented in code). Pins widened (docs/contract_pins.md): collection detail + up_next; dashboard sections + is_playlist/complete; new reorder endpoint recorded. Verified: TestPlaylistCollectionsEndToEnd (new, full e2e: sidecar series split, collection auto-creation with position 7, rescan idempotency, owner-only reorder, finished-filter + completion marker + Up Next drain) plus the full affected integration sweep green.
39 lines
1.9 KiB
Go
39 lines
1.9 KiB
Go
package router
|
|
|
|
func registerCollectionsRoutes(cfg *Config) {
|
|
e := cfg.Echo
|
|
|
|
// JWT middleware for protected routes
|
|
jwtMiddleware := createJWTMiddleware(cfg)
|
|
protected := e.Group("/api", jwtMiddleware)
|
|
|
|
// Collections API routes
|
|
collections := protected.Group("/collections")
|
|
collections.GET("", cfg.CollectionHandler.GetCollections)
|
|
collections.POST("", cfg.CollectionHandler.CreateCollection)
|
|
collections.GET("/:id", cfg.CollectionHandler.GetCollection)
|
|
collections.PUT("/:id", cfg.CollectionHandler.UpdateCollection)
|
|
collections.DELETE("/:id", cfg.CollectionHandler.DeleteCollection)
|
|
collections.GET("/:id/books", cfg.CollectionHandler.GetBookCollections)
|
|
collections.POST("/:id/books", cfg.CollectionHandler.AddBooks)
|
|
collections.PUT("/:id/items/order", cfg.CollectionHandler.ReorderItems)
|
|
collections.DELETE("/:id/books/:bookId", cfg.CollectionHandler.RemoveBook)
|
|
collections.POST("/:id/books/bulk-remove", cfg.CollectionHandler.BulkRemoveBooks)
|
|
collections.POST("/bulk-add-books", cfg.CollectionHandler.HandleBulkAddBooks)
|
|
collections.POST("/test-rules", cfg.CollectionHandler.TestRules)
|
|
collections.POST("/preview", cfg.CollectionHandler.PreviewCollection)
|
|
|
|
// Smart collection rule CRUD (the rules builder persists through
|
|
// these; type follows rules presence — first rule = smart).
|
|
collections.POST("/:id/rules", cfg.CollectionHandler.AddRule)
|
|
collections.PUT("/:id/rules/:ruleId", cfg.CollectionHandler.UpdateRule)
|
|
collections.DELETE("/:id/rules/:ruleId", cfg.CollectionHandler.RemoveRule)
|
|
|
|
// Device shelf mapping routes
|
|
deviceCollections := protected.Group("/devices/:id/collections")
|
|
deviceCollections.GET("", cfg.CollectionHandler.GetDeviceMappings)
|
|
deviceCollections.POST("", cfg.CollectionHandler.CreateDeviceMapping)
|
|
deviceCollections.PUT("/:collectionId", cfg.CollectionHandler.UpdateDeviceMapping)
|
|
deviceCollections.DELETE("/:collectionId", cfg.CollectionHandler.DeleteDeviceMapping)
|
|
}
|