Simplify page number and chapter number pointer creation in reader route registration by using inline new() instead of local variable + address-of.
183 lines
6.3 KiB
Go
183 lines
6.3 KiB
Go
package router
|
|
|
|
import (
|
|
"bookhoard/internal/database"
|
|
"bookhoard/internal/handlers"
|
|
"bookhoard/internal/services"
|
|
"bookhoard/templates"
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/labstack/echo/v5"
|
|
)
|
|
|
|
func registerReaderRoutes(cfg *Config) {
|
|
e := cfg.Echo
|
|
|
|
// Create reader service and handler
|
|
readerService := services.NewReaderService(cfg.Queries, cfg.Worker)
|
|
cfg.ReaderHandler = handlers.NewReaderHandler(
|
|
cfg.Queries,
|
|
services.NewLibraryService(cfg.Queries),
|
|
readerService,
|
|
cfg.Worker,
|
|
)
|
|
|
|
jwtMiddleware := createJWTMiddleware(cfg)
|
|
|
|
// Reader page routes (SSR + API)
|
|
reader := e.Group("/readers", jwtMiddleware)
|
|
|
|
// Reader page (SSR)
|
|
reader.GET("/:mediaItemId", func(c *echo.Context) error {
|
|
// Get user
|
|
user, err := getTemplateUserWithTheme(c, cfg)
|
|
if err != nil {
|
|
return renderErrorPage(c, "Error loading user", "user_load_error")
|
|
}
|
|
// Parse mediaItemID
|
|
mediaItemID := c.Param("mediaItemId")
|
|
parsedUUID, err := uuid.Parse(mediaItemID)
|
|
if err != nil {
|
|
return renderErrorPage(c, "Invalid media item ID", "invalid_id")
|
|
}
|
|
// Fetch media item (directly, no handler)
|
|
mediaItem, err := cfg.Queries.GetMediaItem(c.Request().Context(), pgtype.UUID{Bytes: parsedUUID, Valid: true})
|
|
if err != nil {
|
|
return renderErrorPage(c, "Media item not found", "not_found")
|
|
}
|
|
// Check library access (using pattern from opds.go)
|
|
userUUID, _ := uuid.Parse(user.ID)
|
|
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userUUID))
|
|
if err != nil {
|
|
return renderErrorPage(c, "Failed to check access", "access_error")
|
|
}
|
|
|
|
visible := false
|
|
for _, lib := range libraries {
|
|
if lib.ID == mediaItem.LibraryID {
|
|
visible = true
|
|
break
|
|
}
|
|
}
|
|
if !visible {
|
|
return renderErrorPage(c, "Access denied", "access_denied")
|
|
}
|
|
// Get reading progress
|
|
var progress database.ReadingProgress
|
|
progress, err = cfg.Queries.GetReadingProgress(c.Request().Context(), database.GetReadingProgressParams{
|
|
MediaItemID: pgtype.UUID{Bytes: parsedUUID, Valid: true},
|
|
UserID: uuidToPGType(userUUID),
|
|
})
|
|
if err != nil && err != pgx.ErrNoRows {
|
|
progress = database.ReadingProgress{}
|
|
}
|
|
// Get bookmarks
|
|
bookmarks, _ := cfg.Queries.GetMediaBookmarks(c.Request().Context(), database.GetMediaBookmarksParams{
|
|
MediaItemID: pgtype.UUID{Bytes: parsedUUID, Valid: true},
|
|
UserID: uuidToPGType(userUUID),
|
|
})
|
|
// Convert to template types
|
|
mediaUUID, _ := uuid.FromBytes(mediaItem.ID.Bytes[0:16])
|
|
libUUID, _ := uuid.FromBytes(mediaItem.LibraryID.Bytes[0:16])
|
|
metadata := templates.ReaderMetadata{
|
|
MediaItemID: mediaUUID.String(),
|
|
Title: mediaItem.Title,
|
|
Author: textToString(mediaItem.Author),
|
|
CoverImagePath: textToString(mediaItem.CoverImagePath),
|
|
LibraryType: mediaItem.FormatGroup,
|
|
MimeType: textToString(mediaItem.MimeType),
|
|
FilePath: mediaItem.FilePath,
|
|
TotalPages: int(mediaItem.PageCount.Int32),
|
|
ChapterCount: int(mediaItem.ChapterCount.Int32),
|
|
FormatGroup: mediaItem.FormatGroup,
|
|
MangaType: textToString(mediaItem.MangaType),
|
|
ReadingDirection: textToString(mediaItem.ReadingDirection),
|
|
LibraryID: libUUID.String(),
|
|
FileURL: fmt.Sprintf("/uploads/library-%s/%s", libUUID.String(), mediaItem.FilePath),
|
|
}
|
|
// Progress conversion (inline)
|
|
progressUUID, _ := uuid.FromBytes(progress.ID.Bytes[0:16])
|
|
progressMediaUUID, _ := uuid.FromBytes(progress.MediaItemID.Bytes[0:16])
|
|
progressUserUUID, _ := uuid.FromBytes(progress.UserID.Bytes[0:16])
|
|
templateProgress := templates.ReadingProgress{
|
|
ID: progressUUID.String(),
|
|
MediaItemID: progressMediaUUID.String(),
|
|
UserID: progressUserUUID.String(),
|
|
CurrentPage: int(progress.CurrentPage.Int32),
|
|
TotalPages: int(progress.TotalPages.Int32),
|
|
Percentage: progress.Percentage.Float64,
|
|
EpubCfi: textToString(progress.Epubcfi),
|
|
LastReadAt: progress.LastReadAt.Time,
|
|
}
|
|
// Bookmarks conversion (inline, with loop)
|
|
templateBookmarks := make([]templates.Bookmark, len(bookmarks))
|
|
for i, b := range bookmarks {
|
|
bookmarkUUID, _ := uuid.FromBytes(b.ID.Bytes[0:16])
|
|
bookmarkMediaUUID, _ := uuid.FromBytes(b.MediaItemID.Bytes[0:16])
|
|
bookmarkUserUUID, _ := uuid.FromBytes(b.UserID.Bytes[0:16])
|
|
|
|
var pageNumber *int
|
|
if b.PageNumber.Valid {
|
|
pageNumber = new(int(b.PageNumber.Int32))
|
|
}
|
|
|
|
var chapterNumber *int
|
|
if b.ChapterNumber.Valid {
|
|
chapterNumber = new(int(b.ChapterNumber.Int32))
|
|
}
|
|
|
|
templateBookmarks[i] = templates.Bookmark{
|
|
ID: bookmarkUUID.String(),
|
|
MediaItemID: bookmarkMediaUUID.String(),
|
|
UserID: bookmarkUserUUID.String(),
|
|
PageNumber: pageNumber,
|
|
ChapterNumber: chapterNumber,
|
|
CfiPosition: textToString(b.CfiPosition),
|
|
Title: b.Title,
|
|
Position: textToString(b.Position),
|
|
Notes: textToString(b.Notes),
|
|
CreatedAt: b.CreatedAt.Time,
|
|
}
|
|
}
|
|
// 8. Render template
|
|
var buf bytes.Buffer
|
|
err = templates.Reader(user, metadata, templateProgress, templateBookmarks).Render(c.Request().Context(), &buf)
|
|
if err != nil {
|
|
return renderErrorPage(c, "Error rendering reader", "render_error")
|
|
}
|
|
return c.HTML(http.StatusOK, buf.String())
|
|
})
|
|
|
|
// Content serving (lazy-loaded pages)
|
|
reader.GET("/:mediaItemId/pages/:pageNumber", cfg.ReaderHandler.GetPage)
|
|
|
|
// Chapter metadata
|
|
reader.GET("/:mediaItemId/chapters", cfg.ReaderHandler.GetChapters)
|
|
|
|
// Panel data (comics/manga)
|
|
reader.GET("/:mediaItemId/panels/:pageNumber", cfg.ReaderHandler.GetPanels)
|
|
reader.PUT("/:mediaItemId/panels/:pageNumber", cfg.ReaderHandler.UpdatePanels)
|
|
|
|
// PDF outline/TOC
|
|
reader.GET("/:mediaItemId/outline", cfg.ReaderHandler.GetPDFOutline)
|
|
|
|
// PDF thumbnails (for mini-map)
|
|
reader.GET("/:mediaItemId/thumbnails/:pageNumber", cfg.ReaderHandler.GetPDFThumbnail)
|
|
|
|
// Reading speed
|
|
reader.GET("/:mediaItemId/reading-speed", cfg.ReaderHandler.GetReadingSpeed)
|
|
reader.POST("/:mediaItemId/reading-speed", cfg.ReaderHandler.UpdateReadingSpeed)
|
|
|
|
// Dictionary lookup
|
|
reader.GET("/dictionary/:word", cfg.ReaderHandler.LookupWord)
|
|
|
|
// Reader settings
|
|
reader.GET("/settings", cfg.ReaderHandler.GetSettings)
|
|
reader.PUT("/settings", cfg.ReaderHandler.UpdateSettings)
|
|
}
|