Files
bookhoard/internal/router/media.go
T
John O'Keefe aac7c72900 feat(admin): archived-items page with purge dates, restore, and purge
Admins need to see what the archive lifecycle is holding: a dedicated
/admin/archived page listing every hidden item (archived or still in
the missing grace window) with library, file path, status, and - when
retention is enabled - the exact date it will be permanently deleted
(archived_at + ARCHIVE_RETENTION_DAYS).

Per row: Restore (POST /api/media-items/:id/unarchive, clears the
archive state so it reappears; if the file is still gone the next scan
hides it again) and Delete (existing DELETE endpoint for single-row
purge with its reading history). Purge All Archived reuses the existing
bulk button. The library admin banner links to the page and keeps its
purge button; row actions use data attributes with delegated listeners
in admin.ts since this templ version has no JSFunctionCall helper.

Verified live: page 200 with purge dates shown, unarchive returned 204
and reset the row, per-item delete and bulk purge ({purged:1}) both
removed their rows with no leftovers.
2026-09-13 12:01:05 -04:00

85 lines
4.5 KiB
Go

package router
import (
"bookhoard/internal/handlers"
)
func registerMediaRoutes(cfg *Config) {
e := cfg.Echo
// JWT middleware for protected routes
jwtMiddleware := createJWTMiddleware(cfg)
protected := e.Group("/api", jwtMiddleware)
admin := protected.Group("", handlers.AdminMiddleware)
// Media item routes (all authenticated users)
protected.GET("/media-items", cfg.MediaHandler.ListMediaItems)
protected.GET("/media-items/:id", cfg.MediaHandler.GetMediaItem)
// Book download endpoint - same ServeFile flow as /uploads/library-:id/*
// (JWT + library-visibility gated), addressed by media item ID.
protected.GET("/media-items/:id/download", cfg.MediaHandler.ServeFile)
// Media rating routes (all authenticated users)
protected.POST("/media-items/:id/rating", cfg.MediaHandler.CreateMediaRating)
protected.GET("/media-items/:id/rating", cfg.MediaHandler.GetMediaRating)
protected.PUT("/media-items/:id/rating", cfg.MediaHandler.UpdateMediaRating)
protected.DELETE("/media-items/:id/rating", cfg.MediaHandler.DeleteMediaRating)
// Progress routes (all authenticated users)
protected.GET("/media-items/:id/progress", cfg.MediaHandler.GetMediaReadingProgress)
protected.PUT("/media-items/:id/progress", cfg.MediaHandler.UpdateMediaReadingProgress)
protected.DELETE("/media-items/:id/progress", cfg.MediaHandler.DeleteMediaReadingProgress)
// Notes routes (all authenticated users)
protected.GET("/media-items/:id/notes", cfg.MediaHandler.GetMediaNotes)
protected.POST("/media-items/:id/notes", cfg.MediaHandler.CreateMediaNote)
protected.GET("/media-items/:id/notes/:noteId", cfg.MediaHandler.GetMediaNote)
protected.PUT("/media-items/:id/notes/:noteId", cfg.MediaHandler.UpdateMediaNote)
protected.DELETE("/media-items/:id/notes/:noteId", cfg.MediaHandler.DeleteMediaNote)
// Highlights routes (all authenticated users)
protected.GET("/media-items/:id/highlights", cfg.MediaHandler.GetMediaHighlights)
protected.POST("/media-items/:id/highlights", cfg.MediaHandler.CreateMediaHighlight)
protected.GET("/media-items/:id/highlights/:highlightId", cfg.MediaHandler.GetMediaHighlight)
protected.PUT("/media-items/:id/highlights/:highlightId", cfg.MediaHandler.UpdateMediaHighlight)
protected.DELETE("/media-items/:id/highlights/:highlightId", cfg.MediaHandler.DeleteMediaHighlight)
// Bookmark routes (all authenticated users)
protected.GET("/media-items/:id/bookmarks", cfg.MediaHandler.GetMediaBookmarks)
protected.POST("/media-items/:id/bookmarks", cfg.MediaHandler.CreateMediaBookmark)
protected.PUT("/media-items/:id/bookmarks/:bookmarkId", cfg.MediaHandler.UpdateMediaBookmark)
protected.DELETE("/media-items/:id/bookmarks/:bookmarkId", cfg.MediaHandler.DeleteMediaBookmark)
// Deleted-annotation history (all authenticated users): tombstoned
// highlights/notes/bookmarks restorable or permanently removable from the
// book page's "recently deleted" list.
protected.GET("/media-items/:id/annotations/deleted", cfg.MediaHandler.GetDeletedAnnotations)
protected.POST("/media-items/:id/annotations/:annotationId/restore", cfg.MediaHandler.RestoreDeletedAnnotation)
protected.DELETE("/media-items/:id/annotations/:annotationId", cfg.MediaHandler.PurgeDeletedAnnotation)
// Admin-only media routes
admin.POST("/media-items", cfg.MediaHandler.CreateMediaItem)
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.POST("/media-items/purge-archived", cfg.MediaHandler.PurgeArchivedMediaItems)
admin.POST("/media-items/:id/unarchive", cfg.MediaHandler.UnarchiveMediaItem)
// Shelf management (protected)
protected.POST("/devices/:id/shelves", cfg.MediaHandler.AddToShelf)
protected.GET("/devices/:id/shelves", cfg.MediaHandler.GetShelf)
protected.DELETE("/devices/:id/shelves", cfg.MediaHandler.RemoveFromShelf)
protected.DELETE("/devices/:id/shelves/clear", cfg.MediaHandler.ClearShelf)
// Bulk media operations (protected)
mediaItems := protected.Group("/media-items")
mediaItems.POST("/bulk-delete", cfg.MediaHandler.HandleBulkDelete)
mediaItems.POST("/bulk-update", cfg.MediaHandler.HandleBulkUpdate)
// File serving - authenticated (registered on Echo to avoid /api prefix)
// Create a group with JWT middleware for routes outside /api
authenticated := e.Group("", createJWTMiddleware(cfg))
// Note: Must be registered LAST as it's a wildcard route
authenticated.GET("/uploads/library-:id/*", cfg.MediaHandler.ServeFile)
}