refactor(handlers): Phase 7 - cleanup ebook.go, remove duplicate methods
- Remove 24 duplicate media CRUD methods from ebook.go (884 lines removed) - Keep 12 scanner/watch/scheduler methods on Handler - Move request type declarations to media.go: * CreateMediaItemRequest * UpdateMediaItemRequest * CreateMediaNoteRequest * UpdateMediaNoteRequest * CreateMediaHighlightRequest * UpdateMediaHighlightRequest - Remove unused imports from ebook.go (strconv, pgx) - Fix library.go to use MediaHandler.ListMediaItems instead of Handler ebook.go reduced from 1266 lines to 382 lines (70% reduction) Handler now has focused responsibility: scanner and scheduler operations only This completes Phase 7 of the ebook.go refactoring plan. Result: Clean separation of concerns with no duplicate code
This commit is contained in:
@@ -7,12 +7,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@@ -382,884 +380,3 @@ func (h *Handler) StartScheduler() {
|
||||
func (h *Handler) StopScheduler() {
|
||||
h.scheduler.Stop()
|
||||
}
|
||||
|
||||
// Media Item handlers for new library system
|
||||
|
||||
// ListMediaItems handles GET /api/media-items
|
||||
func (h *Handler) ListMediaItems(c echo.Context) error {
|
||||
libraryID := c.QueryParam("library_id")
|
||||
sort := c.QueryParam("sort")
|
||||
limit, _ := strconv.Atoi(c.QueryParam("limit"))
|
||||
offset, _ := strconv.Atoi(c.QueryParam("offset"))
|
||||
|
||||
if limit == 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
if sort == "" {
|
||||
sort = "created_at DESC"
|
||||
}
|
||||
|
||||
allowedSorts := map[string]bool{
|
||||
"created_at ASC": true,
|
||||
"created_at DESC": true,
|
||||
"title ASC": true,
|
||||
"title DESC": true,
|
||||
"author ASC": true,
|
||||
"author DESC": true,
|
||||
"series ASC": true,
|
||||
"series DESC": true,
|
||||
"date_published ASC": true,
|
||||
"date_published DESC": true,
|
||||
"copyright_year ASC": true,
|
||||
"copyright_year DESC": true,
|
||||
"page_count ASC": true,
|
||||
"page_count DESC": true,
|
||||
"genre ASC": true,
|
||||
"genre DESC": true,
|
||||
}
|
||||
|
||||
if !allowedSorts[sort] {
|
||||
sort = "created_at DESC"
|
||||
}
|
||||
|
||||
// Enforce maximum limit
|
||||
if limit > maxPaginationLimit {
|
||||
limit = maxPaginationLimit
|
||||
}
|
||||
|
||||
if offset < 0 {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "offset cannot be negative"})
|
||||
}
|
||||
|
||||
if libraryID != "" {
|
||||
libUUID, err := uuid.Parse(libraryID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
|
||||
}
|
||||
|
||||
items, err := h.db.ListMediaItemsSorted(c.Request().Context(), database.ListMediaItemsSortedParams{
|
||||
LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
|
||||
Sort: pgtype.Text{String: sort, Valid: true},
|
||||
Limit: pgtype.Int4{Int32: int32(limit), Valid: true},
|
||||
Offset: pgtype.Int4{Int32: int32(offset), Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": items})
|
||||
}
|
||||
|
||||
items, err := h.db.ListMediaItems(c.Request().Context(), database.ListMediaItemsParams{
|
||||
Limit: int32(limit),
|
||||
Offset: int32(offset),
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": items})
|
||||
}
|
||||
|
||||
// GetMediaItem handles GET /api/media-items/:id
|
||||
func (h *Handler) GetMediaItem(c echo.Context) error {
|
||||
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"})
|
||||
}
|
||||
|
||||
item, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: mediaUUID, Valid: true})
|
||||
if err != nil {
|
||||
if 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()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, item)
|
||||
}
|
||||
|
||||
// ListMediaItemsFiltered handles GET /api/media-items/filtered
|
||||
func (h *Handler) ListMediaItemsFiltered(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
libraryID := c.QueryParam("library_id")
|
||||
sort := c.QueryParam("sort")
|
||||
limit, _ := strconv.Atoi(c.QueryParam("limit"))
|
||||
offset, _ := strconv.Atoi(c.QueryParam("offset"))
|
||||
|
||||
authorFilter := c.QueryParam("author_filter")
|
||||
seriesFilter := c.QueryParam("series_filter")
|
||||
genreFilter := c.QueryParam("genre_filter")
|
||||
languageFilter := c.QueryParam("language_filter")
|
||||
yearMin, _ := strconv.Atoi(c.QueryParam("year_min"))
|
||||
yearMax, _ := strconv.Atoi(c.QueryParam("year_max"))
|
||||
hasCover, _ := strconv.ParseBool(c.QueryParam("has_cover"))
|
||||
|
||||
if limit == 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
if sort == "" {
|
||||
sort = "created_at DESC"
|
||||
}
|
||||
|
||||
// Enforce maximum limit
|
||||
if limit > maxPaginationLimit {
|
||||
limit = maxPaginationLimit
|
||||
}
|
||||
|
||||
libUUID, err := uuid.Parse(libraryID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library id"})
|
||||
}
|
||||
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"})
|
||||
}
|
||||
|
||||
items, err := h.db.ListMediaItemsFiltered(c.Request().Context(), database.ListMediaItemsFilteredParams{
|
||||
LibraryID: pgtype.UUID{Bytes: libUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
AuthorFilter: pgtype.Text{String: authorFilter, Valid: true},
|
||||
SeriesFilter: pgtype.Text{String: seriesFilter, Valid: true},
|
||||
GenreFilter: pgtype.Text{String: genreFilter, Valid: true},
|
||||
LanguageFilter: pgtype.Text{String: languageFilter, Valid: true},
|
||||
YearMin: pgtype.Int4{Int32: int32(yearMin), Valid: true},
|
||||
YearMax: pgtype.Int4{Int32: int32(yearMax), Valid: true},
|
||||
HasCover: pgtype.Bool{Bool: hasCover, Valid: true},
|
||||
Sort: pgtype.Text{String: sort, Valid: true},
|
||||
Limit: pgtype.Int4{Int32: int32(limit), Valid: true},
|
||||
Offset: pgtype.Int4{Int32: int32(offset), Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": items})
|
||||
}
|
||||
|
||||
// CreateMediaRating handles POST /api/media-items/:id/rating
|
||||
func (h *Handler) CreateMediaRating(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Rating int32 `json:"rating" validate:"required,min=1,max=10"`
|
||||
}
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
}
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
rating, err := h.db.CreateMediaRating(c.Request().Context(), database.CreateMediaRatingParams{
|
||||
MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
Rating: req.Rating,
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusCreated, rating)
|
||||
}
|
||||
|
||||
// GetMediaRating handles GET /api/media-items/:id/rating
|
||||
func (h *Handler) GetMediaRating(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
rating, err := h.db.GetMediaRating(c.Request().Context(), database.GetMediaRatingParams{
|
||||
MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"rating": nil})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, rating)
|
||||
}
|
||||
|
||||
// UpdateMediaRating handles PUT /api/media-items/:id/rating
|
||||
func (h *Handler) UpdateMediaRating(c echo.Context) error {
|
||||
return h.CreateMediaRating(c) // Same logic as create due to upsert
|
||||
}
|
||||
|
||||
// DeleteMediaRating handles DELETE /api/media-items/:id/rating
|
||||
func (h *Handler) DeleteMediaRating(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
err = h.db.DeleteMediaRating(c.Request().Context(), database.DeleteMediaRatingParams{
|
||||
MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]string{"message": "rating deleted"})
|
||||
}
|
||||
|
||||
// GetMediaReadingProgress handles GET /api/media-items/:id/progress
|
||||
func (h *Handler) GetMediaReadingProgress(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
progress, err := h.db.GetReadingProgress(c.Request().Context(), database.GetReadingProgressParams{
|
||||
MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"current_page": 0,
|
||||
"total_pages": nil,
|
||||
})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, progress)
|
||||
}
|
||||
|
||||
// UpdateMediaReadingProgress handles PUT /api/media-items/:id/progress
|
||||
func (h *Handler) UpdateMediaReadingProgress(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
var req struct {
|
||||
CurrentPage int32 `json:"current_page"`
|
||||
TotalPages int32 `json:"total_pages"`
|
||||
}
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
}
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
progress, err := h.db.UpdateReadingProgress(c.Request().Context(), database.UpdateReadingProgressParams{
|
||||
MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
CurrentPage: pgtype.Int4{Int32: req.CurrentPage, Valid: true},
|
||||
TotalPages: pgtype.Int4{Int32: req.TotalPages, Valid: req.TotalPages > 0},
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, progress)
|
||||
}
|
||||
|
||||
// DeleteMediaReadingProgress handles DELETE /api/media-items/:id/progress
|
||||
func (h *Handler) DeleteMediaReadingProgress(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
err = h.db.DeleteReadingProgress(c.Request().Context(), database.DeleteReadingProgressParams{
|
||||
MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]string{"message": "reading progress deleted"})
|
||||
}
|
||||
|
||||
// Admin Media Items handlers
|
||||
|
||||
// CreateMediaItemRequest represents the request for creating a media item
|
||||
type CreateMediaItemRequest struct {
|
||||
LibraryID uuid.UUID `json:"library_id" validate:"required"`
|
||||
Title string `json:"title" validate:"required,min=1,max=500"`
|
||||
Author string `json:"author"`
|
||||
ISBN string `json:"isbn"`
|
||||
Description string `json:"description"`
|
||||
FilePath string `json:"file_path" validate:"required"`
|
||||
FileSize int64 `json:"file_size" validate:"required,min=1"`
|
||||
MimeType string `json:"mime_type" validate:"required"`
|
||||
CoverImagePath string `json:"cover_image_path"`
|
||||
Series string `json:"series"`
|
||||
SeriesNumber int32 `json:"series_number"`
|
||||
Tags string `json:"tags"`
|
||||
ASIN string `json:"asin"`
|
||||
DatePublished string `json:"date_published"`
|
||||
Publisher string `json:"publisher"`
|
||||
Contributors string `json:"contributors"`
|
||||
}
|
||||
|
||||
// CreateMediaItem handles POST /api/media-items (admin only)
|
||||
func (h *Handler) CreateMediaItem(c echo.Context) error {
|
||||
user := MustGetAuthenticatedUser(c)
|
||||
|
||||
// Verify user is admin
|
||||
if user.Role != "admin" {
|
||||
return c.JSON(http.StatusForbidden, map[string]string{"error": "admin access required"})
|
||||
}
|
||||
|
||||
var req CreateMediaItemRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
}
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Validate library exists
|
||||
_, err := h.db.GetLibrary(c.Request().Context(), pgtype.UUID{Bytes: req.LibraryID, Valid: true})
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "library not found"})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
mediaItemID, err := h.db.CreateMediaItem(c.Request().Context(), database.CreateMediaItemParams{
|
||||
LibraryID: pgtype.UUID{Bytes: req.LibraryID, Valid: true},
|
||||
Title: req.Title,
|
||||
Author: pgtype.Text{String: req.Author, Valid: req.Author != ""},
|
||||
Isbn: pgtype.Text{String: req.ISBN, Valid: req.ISBN != ""},
|
||||
Description: pgtype.Text{String: req.Description, Valid: req.Description != ""},
|
||||
FilePath: req.FilePath,
|
||||
FileSize: pgtype.Int8{Int64: req.FileSize, Valid: req.FileSize > 0},
|
||||
MimeType: pgtype.Text{String: req.MimeType, Valid: req.MimeType != ""},
|
||||
CoverImagePath: pgtype.Text{String: req.CoverImagePath, Valid: req.CoverImagePath != ""},
|
||||
Series: pgtype.Text{String: req.Series, Valid: req.Series != ""},
|
||||
SeriesNumber: pgtype.Int4{Int32: req.SeriesNumber, Valid: req.SeriesNumber > 0},
|
||||
Tags: pgtype.Text{String: req.Tags, Valid: req.Tags != ""},
|
||||
Asin: pgtype.Text{String: req.ASIN, Valid: req.ASIN != ""},
|
||||
DatePublished: pgtype.Date{Time: parseDate(req.DatePublished), Valid: req.DatePublished != ""},
|
||||
Publisher: pgtype.Text{String: req.Publisher, Valid: req.Publisher != ""},
|
||||
Contributors: pgtype.Text{String: req.Contributors, Valid: req.Contributors != ""},
|
||||
AddedByAdminID: user.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
// Return the created item info
|
||||
item, err := h.db.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: mediaItemID.ID.Bytes, Valid: true})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusCreated, item)
|
||||
}
|
||||
|
||||
// UpdateMediaItemRequest represents the request for updating a media item
|
||||
type UpdateMediaItemRequest struct {
|
||||
Title string `json:"title" validate:"required,min=1,max=500"`
|
||||
Author string `json:"author"`
|
||||
ISBN string `json:"isbn"`
|
||||
Description string `json:"description"`
|
||||
CoverImagePath string `json:"cover_image_path"`
|
||||
Series string `json:"series"`
|
||||
SeriesNumber int32 `json:"series_number"`
|
||||
Tags string `json:"tags"`
|
||||
ASIN string `json:"asin"`
|
||||
DatePublished string `json:"date_published"`
|
||||
Publisher string `json:"publisher"`
|
||||
Contributors string `json:"contributors"`
|
||||
}
|
||||
|
||||
// UpdateMediaItem handles PUT /api/media-items/:id (admin only)
|
||||
func (h *Handler) UpdateMediaItem(c echo.Context) error {
|
||||
user := MustGetAuthenticatedUser(c)
|
||||
|
||||
// Verify user is admin
|
||||
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"})
|
||||
}
|
||||
|
||||
var req UpdateMediaItemRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
}
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
item, err := h.db.UpdateMediaItem(c.Request().Context(), database.UpdateMediaItemParams{
|
||||
ID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
Title: req.Title,
|
||||
Author: pgtype.Text{String: req.Author, Valid: req.Author != ""},
|
||||
Isbn: pgtype.Text{String: req.ISBN, Valid: req.ISBN != ""},
|
||||
Description: pgtype.Text{String: req.Description, Valid: req.Description != ""},
|
||||
CoverImagePath: pgtype.Text{String: req.CoverImagePath, Valid: req.CoverImagePath != ""},
|
||||
Series: pgtype.Text{String: req.Series, Valid: req.Series != ""},
|
||||
SeriesNumber: pgtype.Int4{Int32: req.SeriesNumber, Valid: req.SeriesNumber > 0},
|
||||
Tags: pgtype.Text{String: req.Tags, Valid: req.Tags != ""},
|
||||
Asin: pgtype.Text{String: req.ASIN, Valid: req.ASIN != ""},
|
||||
DatePublished: pgtype.Date{Time: parseDate(req.DatePublished), Valid: req.DatePublished != ""},
|
||||
Publisher: pgtype.Text{String: req.Publisher, Valid: req.Publisher != ""},
|
||||
Contributors: pgtype.Text{String: req.Contributors, Valid: req.Contributors != ""},
|
||||
})
|
||||
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)
|
||||
func (h *Handler) DeleteMediaItem(c echo.Context) error {
|
||||
user := MustGetAuthenticatedUser(c)
|
||||
|
||||
// Verify user is admin
|
||||
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"})
|
||||
}
|
||||
|
||||
err = h.db.DeleteMediaItem(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.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// Media Notes handlers
|
||||
|
||||
// GetMediaNotes handles GET /api/media-items/:id/notes
|
||||
func (h *Handler) GetMediaNotes(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
notes, err := h.db.GetMediaNotes(c.Request().Context(), database.GetMediaNotesParams{
|
||||
MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, notes)
|
||||
}
|
||||
|
||||
// CreateMediaNoteRequest represents the request for creating a media note
|
||||
type CreateMediaNoteRequest struct {
|
||||
Content string `json:"content" validate:"required,min=1,max=10000"`
|
||||
Position string `json:"position"`
|
||||
}
|
||||
|
||||
// CreateMediaNote handles POST /api/media-items/:id/notes
|
||||
func (h *Handler) CreateMediaNote(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
var req CreateMediaNoteRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
}
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
note, err := h.db.CreateMediaNote(c.Request().Context(), database.CreateMediaNoteParams{
|
||||
MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
Content: req.Content,
|
||||
Position: pgtype.Text{String: req.Position, Valid: req.Position != ""},
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusCreated, note)
|
||||
}
|
||||
|
||||
// GetMediaNote handles GET /api/media-items/:id/notes/:noteId
|
||||
func (h *Handler) GetMediaNote(c echo.Context) error {
|
||||
noteID := c.Param("noteId")
|
||||
noteUUID, err := uuid.Parse(noteID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid note id"})
|
||||
}
|
||||
|
||||
note, err := h.db.GetMediaNote(c.Request().Context(), pgtype.UUID{Bytes: noteUUID, Valid: true})
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return c.JSON(http.StatusNotFound, map[string]string{"error": "note not found"})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, note)
|
||||
}
|
||||
|
||||
// UpdateMediaNoteRequest represents the request for updating a media note
|
||||
type UpdateMediaNoteRequest struct {
|
||||
Content string `json:"content" validate:"required,min=1,max=10000"`
|
||||
Position string `json:"position"`
|
||||
}
|
||||
|
||||
// UpdateMediaNote handles PUT /api/media-items/:id/notes/:noteId
|
||||
func (h *Handler) UpdateMediaNote(c echo.Context) error {
|
||||
noteID := c.Param("noteId")
|
||||
noteUUID, err := uuid.Parse(noteID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid note id"})
|
||||
}
|
||||
|
||||
var req UpdateMediaNoteRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
}
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
note, err := h.db.UpdateMediaNote(c.Request().Context(), database.UpdateMediaNoteParams{
|
||||
ID: pgtype.UUID{Bytes: noteUUID, Valid: true},
|
||||
Content: req.Content,
|
||||
Position: pgtype.Text{String: req.Position, Valid: req.Position != ""},
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, note)
|
||||
}
|
||||
|
||||
// DeleteMediaNote handles DELETE /api/media-items/:id/notes/:noteId
|
||||
func (h *Handler) DeleteMediaNote(c echo.Context) error {
|
||||
noteID := c.Param("noteId")
|
||||
noteUUID, err := uuid.Parse(noteID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid note id"})
|
||||
}
|
||||
|
||||
err = h.db.DeleteMediaNote(c.Request().Context(), pgtype.UUID{Bytes: noteUUID, Valid: true})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// Media Highlights handlers
|
||||
|
||||
// GetMediaHighlights handles GET /api/media-items/:id/highlights
|
||||
func (h *Handler) GetMediaHighlights(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
highlights, err := h.db.GetMediaHighlights(c.Request().Context(), database.GetMediaHighlightsParams{
|
||||
MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, highlights)
|
||||
}
|
||||
|
||||
// CreateMediaHighlightRequest represents the request for creating a media highlight
|
||||
type CreateMediaHighlightRequest struct {
|
||||
SelectionText string `json:"selection_text" validate:"required,min=1,max=5000"`
|
||||
StartPosition string `json:"start_position" validate:"required,max=100"`
|
||||
EndPosition string `json:"end_position" validate:"required,max=100"`
|
||||
Color string `json:"color" validate:"omitempty,len=7"` // hex color
|
||||
NoteID string `json:"note_id"`
|
||||
}
|
||||
|
||||
// CreateMediaHighlight handles POST /api/media-items/:id/highlights
|
||||
func (h *Handler) CreateMediaHighlight(c echo.Context) error {
|
||||
userID := c.Get("user_id").(string)
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user"})
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
var req CreateMediaHighlightRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
}
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
var noteUUID pgtype.UUID
|
||||
if req.NoteID != "" {
|
||||
if noteID, err := uuid.Parse(req.NoteID); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid note id"})
|
||||
} else {
|
||||
noteUUID = pgtype.UUID{Bytes: noteID, Valid: true}
|
||||
}
|
||||
}
|
||||
|
||||
color := "#ffff00" // default yellow
|
||||
if req.Color != "" {
|
||||
color = req.Color
|
||||
}
|
||||
|
||||
highlight, err := h.db.CreateMediaHighlight(c.Request().Context(), database.CreateMediaHighlightParams{
|
||||
MediaItemID: pgtype.UUID{Bytes: mediaUUID, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
SelectionText: req.SelectionText,
|
||||
StartPosition: pgtype.Text{String: req.StartPosition, Valid: true},
|
||||
EndPosition: pgtype.Text{String: req.EndPosition, Valid: true},
|
||||
Color: pgtype.Text{String: color, Valid: true},
|
||||
NoteID: noteUUID,
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusCreated, highlight)
|
||||
}
|
||||
|
||||
// GetMediaHighlight handles GET /api/media-items/:id/highlights/:highlightId
|
||||
func (h *Handler) GetMediaHighlight(c echo.Context) error {
|
||||
highlightID := c.Param("highlightId")
|
||||
highlightUUID, err := uuid.Parse(highlightID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid highlight id"})
|
||||
}
|
||||
|
||||
highlight, err := h.db.GetMediaHighlight(c.Request().Context(), pgtype.UUID{Bytes: highlightUUID, Valid: true})
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return c.JSON(http.StatusNotFound, map[string]string{"error": "highlight not found"})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, highlight)
|
||||
}
|
||||
|
||||
// UpdateMediaHighlightRequest represents the request for updating a media highlight
|
||||
type UpdateMediaHighlightRequest struct {
|
||||
SelectionText string `json:"selection_text" validate:"required,min=1,max=5000"`
|
||||
StartPosition string `json:"start_position" validate:"required,max=100"`
|
||||
EndPosition string `json:"end_position" validate:"required,max=100"`
|
||||
Color string `json:"color" validate:"omitempty,len=7"` // hex color
|
||||
NoteID string `json:"note_id"`
|
||||
}
|
||||
|
||||
// UpdateMediaHighlight handles PUT /api/media-items/:id/highlights/:highlightId
|
||||
func (h *Handler) UpdateMediaHighlight(c echo.Context) error {
|
||||
highlightID := c.Param("highlightId")
|
||||
highlightUUID, err := uuid.Parse(highlightID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid highlight id"})
|
||||
}
|
||||
|
||||
var req UpdateMediaHighlightRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
}
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
var noteUUID pgtype.UUID
|
||||
if req.NoteID != "" {
|
||||
if noteID, err := uuid.Parse(req.NoteID); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid note id"})
|
||||
} else {
|
||||
noteUUID = pgtype.UUID{Bytes: noteID, Valid: true}
|
||||
}
|
||||
}
|
||||
|
||||
color := "#ffff00" // default yellow
|
||||
if req.Color != "" {
|
||||
color = req.Color
|
||||
}
|
||||
|
||||
highlight, err := h.db.UpdateMediaHighlight(c.Request().Context(), database.UpdateMediaHighlightParams{
|
||||
ID: pgtype.UUID{Bytes: highlightUUID, Valid: true},
|
||||
SelectionText: req.SelectionText,
|
||||
StartPosition: pgtype.Text{String: req.StartPosition, Valid: true},
|
||||
EndPosition: pgtype.Text{String: req.EndPosition, Valid: true},
|
||||
Color: pgtype.Text{String: color, Valid: true},
|
||||
NoteID: noteUUID,
|
||||
})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, highlight)
|
||||
}
|
||||
|
||||
// DeleteMediaHighlight handles DELETE /api/media-items/:id/highlights/:highlightId
|
||||
func (h *Handler) DeleteMediaHighlight(c echo.Context) error {
|
||||
highlightID := c.Param("highlightId")
|
||||
highlightUUID, err := uuid.Parse(highlightID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid highlight id"})
|
||||
}
|
||||
|
||||
err = h.db.DeleteMediaHighlight(c.Request().Context(), pgtype.UUID{Bytes: highlightUUID, Valid: true})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// SearchMediaItems handles GET /api/media-items/search
|
||||
// Performs partial matching search with fuzzy fallback if no results found
|
||||
func (h *Handler) SearchMediaItems(c echo.Context) error {
|
||||
query := c.QueryParam("q")
|
||||
userID := c.Get("user_id").(string)
|
||||
|
||||
if query == "" {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "query parameter 'q' is required"})
|
||||
}
|
||||
|
||||
userUUID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user id"})
|
||||
}
|
||||
|
||||
limit := int32(50)
|
||||
offset := int32(0)
|
||||
|
||||
searchPattern := "%" + query + "%"
|
||||
|
||||
partialResults, err := h.db.SearchMediaItems(c.Request().Context(), database.SearchMediaItemsParams{
|
||||
SearchPattern: pgtype.Text{String: searchPattern, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
Limit: pgtype.Int4{Int32: limit, Valid: true},
|
||||
Offset: pgtype.Int4{Int32: offset, Valid: true},
|
||||
})
|
||||
|
||||
if err != nil && err != pgx.ErrNoRows {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
if len(partialResults) > 0 {
|
||||
return c.JSON(http.StatusOK, partialResults)
|
||||
}
|
||||
|
||||
fuzzyResults, err := h.db.SearchMediaItemsFuzzy(c.Request().Context(), database.SearchMediaItemsFuzzyParams{
|
||||
SearchQuery: pgtype.Text{String: query, Valid: true},
|
||||
UserID: pgtype.UUID{Bytes: userUUID, Valid: true},
|
||||
Limit: pgtype.Int4{Int32: limit, Valid: true},
|
||||
Offset: pgtype.Int4{Int32: offset, Valid: true},
|
||||
})
|
||||
|
||||
if err != nil && err != pgx.ErrNoRows {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
if len(fuzzyResults) == 0 {
|
||||
return c.JSON(http.StatusNotFound, map[string]interface{}{
|
||||
"error": "no results found",
|
||||
"query": query,
|
||||
"results": []interface{}{},
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, fuzzyResults)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,72 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// CreateMediaItemRequest represents the request for creating a media item
|
||||
type CreateMediaItemRequest struct {
|
||||
LibraryID uuid.UUID `json:"library_id" validate:"required"`
|
||||
Title string `json:"title" validate:"required,min=1,max=500"`
|
||||
Author string `json:"author"`
|
||||
ISBN string `json:"isbn"`
|
||||
Description string `json:"description"`
|
||||
FilePath string `json:"file_path" validate:"required"`
|
||||
FileSize int64 `json:"file_size" validate:"required,min=1"`
|
||||
MimeType string `json:"mime_type" validate:"required"`
|
||||
CoverImagePath string `json:"cover_image_path"`
|
||||
Series string `json:"series"`
|
||||
SeriesNumber int32 `json:"series_number"`
|
||||
Tags string `json:"tags"`
|
||||
ASIN string `json:"asin"`
|
||||
DatePublished string `json:"date_published"`
|
||||
Publisher string `json:"publisher"`
|
||||
Contributors string `json:"contributors"`
|
||||
}
|
||||
|
||||
// UpdateMediaItemRequest represents the request for updating a media item
|
||||
type UpdateMediaItemRequest struct {
|
||||
Title string `json:"title" validate:"required,min=1,max=500"`
|
||||
Author string `json:"author"`
|
||||
ISBN string `json:"isbn"`
|
||||
Description string `json:"description"`
|
||||
CoverImagePath string `json:"cover_image_path"`
|
||||
Series string `json:"series"`
|
||||
SeriesNumber int32 `json:"series_number"`
|
||||
Tags string `json:"tags"`
|
||||
ASIN string `json:"asin"`
|
||||
DatePublished string `json:"date_published"`
|
||||
Publisher string `json:"publisher"`
|
||||
Contributors string `json:"contributors"`
|
||||
}
|
||||
|
||||
// CreateMediaNoteRequest represents the request for creating a media note
|
||||
type CreateMediaNoteRequest struct {
|
||||
Content string `json:"content" validate:"required,min=1,max=10000"`
|
||||
Position string `json:"position"`
|
||||
}
|
||||
|
||||
// UpdateMediaNoteRequest represents the request for updating a media note
|
||||
type UpdateMediaNoteRequest struct {
|
||||
Content string `json:"content" validate:"required,min=1,max=10000"`
|
||||
Position string `json:"position"`
|
||||
}
|
||||
|
||||
// CreateMediaHighlightRequest represents the request for creating a media highlight
|
||||
type CreateMediaHighlightRequest struct {
|
||||
SelectionText string `json:"selection_text" validate:"required,min=1,max=5000"`
|
||||
StartPosition string `json:"start_position" validate:"required,max=100"`
|
||||
EndPosition string `json:"end_position" validate:"required,max=100"`
|
||||
Color string `json:"color" validate:"omitempty,len=7"`
|
||||
NoteID string `json:"note_id"`
|
||||
}
|
||||
|
||||
// UpdateMediaHighlightRequest represents the request for updating a media highlight
|
||||
type UpdateMediaHighlightRequest struct {
|
||||
SelectionText string `json:"selection_text" validate:"required,min=1,max=5000"`
|
||||
StartPosition string `json:"start_position" validate:"required,max=100"`
|
||||
EndPosition string `json:"end_position" validate:"required,max=100"`
|
||||
Color string `json:"color" validate:"omitempty,len=7"`
|
||||
NoteID string `json:"note_id"`
|
||||
}
|
||||
|
||||
type MediaHandler struct {
|
||||
db *database.Queries
|
||||
worker *services.Worker
|
||||
|
||||
@@ -46,7 +46,7 @@ func registerLibraryRoutes(cfg *Config) {
|
||||
adminLibrary.GET("/:id/media-items", func(c echo.Context) error {
|
||||
libraryID := c.Param("id")
|
||||
c.QueryParams().Set("library_id", libraryID)
|
||||
return h.ListMediaItems(c)
|
||||
return cfg.MediaHandler.ListMediaItems(c)
|
||||
})
|
||||
|
||||
// User library visibility control
|
||||
|
||||
Reference in New Issue
Block a user