Refactor: Eliminate duplicate types - Use handler types directly

- Deleted templates.CollectionDetailData - using templates.CollectionData everywhere
- Deleted templates.BookData - using handlers.BookInfo everywhere
- Deleted templates.DeviceData - using handlers.DeviceInfo everywhere
- Deleted templates.ProgressItemData - using handlers.ProgressWithMedia everywhere
- Deleted templates.convertDevices() helper - Use handlers types directly in templates
- Enhanced handlers.ProgressWithMedia with device metadata fields
- Added handlers.getDeviceIcon() helper
- Updated all templates to import handlers package
- Cleaned up unused imports

This aligns codebase with templ's design philosophy (use Go types directly, no parallel type system)
This commit is contained in:
2026-02-12 19:48:07 -05:00
parent b5745e1554
commit 2a64ca423f
15 changed files with 639 additions and 168 deletions
+7 -7
View File
@@ -63,6 +63,13 @@ type UpdateDeviceMappingRequest struct {
SyncDirection string `json:"sync_direction" validate:"required,oneof=bidirectional book_to_device device_to_book none"`
}
type BookInfo struct {
MediaItemID string `json:"media_item_id"`
Title string `json:"title"`
Author string `json:"author"`
CoverImagePath string `json:"cover_image_path"`
}
func (h *CollectionHandler) CreateCollection(c echo.Context) error {
user := c.Get("user").(database.Users)
userUUID := uuid.UUID(user.ID.Bytes)
@@ -171,13 +178,6 @@ func (h *CollectionHandler) GetCollection(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
type BookInfo struct {
MediaItemID string `json:"media_item_id"`
Title string `json:"title"`
Author string `json:"author"`
CoverImagePath string `json:"cover_image_path"`
}
bookList := make([]BookInfo, 0, len(books))
for _, book := range books {
bookList = append(bookList, BookInfo{
+50 -22
View File
@@ -14,6 +14,20 @@ import (
"github.com/labstack/echo/v4"
)
// getDeviceIcon returns an emoji icon for device type
func getDeviceIcon(deviceType string) string {
switch deviceType {
case "kobo":
return "📚"
case "koreader":
return "📖"
case "kindle":
return "📱"
default:
return "📚"
}
}
// GetUniversalProgress retrieves progress with all location references
func (h *Handler) GetUniversalProgress(c echo.Context) error {
user := MustGetAuthenticatedUser(c)
@@ -229,16 +243,22 @@ func (h *Handler) GetProgressHistory(c echo.Context) error {
}
type ProgressWithMedia struct {
MediaItemID uuid.UUID
Title string
Author string
CoverImagePath string
Percentage float64
CurrentPage int32
TotalPages int32
LastReadAt time.Time
Epubcfi string
LastSyncDevice string
MediaItemID uuid.UUID `json:"media_item_id"`
Title string `json:"title"`
Author string `json:"author"`
CoverImagePath string `json:"cover_image_path"`
Percentage float64 `json:"percentage"`
CurrentPage int32 `json:"current_page"`
TotalPages int32 `json:"total_pages"`
LastReadAt time.Time `json:"last_read_at"`
Epubcfi string `json:"epubcfi"`
LastSyncDevice string `json:"last_sync_device"`
DeviceName string `json:"device_name,omitempty"`
DeviceType string `json:"device_type,omitempty"`
DeviceIcon string `json:"device_icon,omitempty"`
ProgressPercentage float64 `json:"-"`
EpubCFI string `json:"-"`
LastUpdated string `json:"-"`
}
// GetAllProgress retrieves all progress for a user with sync source info
@@ -283,17 +303,26 @@ func (h *Handler) GetAllProgress(c echo.Context) error {
deviceName = progress.LastSyncDevice.String
}
lastUpdated := ""
if progress.LastReadAt.Valid {
lastUpdated = progress.LastReadAt.Time.Format("2006-01-02 15:04")
}
progressList = append(progressList, ProgressWithMedia{
MediaItemID: progress.MediaItemID.Bytes,
Title: mediaItem.Title,
Author: author,
CoverImagePath: coverPath,
Percentage: progress.Percentage.Float64,
CurrentPage: progress.CurrentPage.Int32,
TotalPages: progress.TotalPages.Int32,
LastReadAt: progress.LastReadAt.Time,
Epubcfi: epubcfi,
LastSyncDevice: deviceName,
MediaItemID: progress.MediaItemID.Bytes,
Title: mediaItem.Title,
Author: author,
CoverImagePath: coverPath,
Percentage: progress.Percentage.Float64,
CurrentPage: progress.CurrentPage.Int32,
TotalPages: progress.TotalPages.Int32,
LastReadAt: progress.LastReadAt.Time,
Epubcfi: epubcfi,
LastSyncDevice: deviceName,
ProgressPercentage: progress.Percentage.Float64,
EpubCFI: epubcfi,
LastUpdated: lastUpdated,
DeviceIcon: getDeviceIcon(deviceName),
})
}
@@ -349,7 +378,7 @@ func (h *Handler) GetAllProgressData(c echo.Context) ([]ProgressWithMedia, error
MediaItemID: progress.MediaItemID.Bytes,
Title: mediaItem.Title,
Author: author,
CoverImagePath: coverPath,
CoverImagePath: coverPath,
Percentage: progress.Percentage.Float64,
CurrentPage: progress.CurrentPage.Int32,
TotalPages: progress.TotalPages.Int32,
@@ -361,4 +390,3 @@ func (h *Handler) GetAllProgressData(c echo.Context) ([]ProgressWithMedia, error
return progressList, nil
}
+4 -5
View File
@@ -158,18 +158,17 @@ func registerFrontendRoutes(cfg *Config) {
if err != nil {
return c.HTML(http.StatusInternalServerError, "Error loading user")
}
deviceData, err := cfg.DeviceHandler.GetDevicesData(c)
devices, err := cfg.DeviceHandler.GetDevicesData(c)
if err != nil {
return c.HTML(http.StatusInternalServerError, "Error loading devices")
}
pendingData, err := cfg.DeviceHandler.GetPendingRegistrationsData(c)
pendingMaps, err := cfg.DeviceHandler.GetPendingRegistrationsData(c)
if err != nil {
return c.HTML(http.StatusInternalServerError, "Error loading pending")
}
devicesList := convertDevices(deviceData)
pendingList := convertPending(pendingData)
pendingList := convertPending(pendingMaps)
var buf bytes.Buffer
err = templates.Devices(user, devicesList, pendingList).Render(c.Request().Context(), &buf)
err = templates.Devices(user, devices, pendingList).Render(c.Request().Context(), &buf)
if err != nil {
return err
}
-25
View File
@@ -2,9 +2,7 @@ package router
import (
"context"
"time"
"bookhoard/internal/handlers"
"bookhoard/templates"
"github.com/google/uuid"
@@ -43,29 +41,6 @@ func getTemplateUserWithTheme(c echo.Context, cfg *Config) (templates.User, erro
}, nil
}
func convertDevices(deviceInfos []handlers.DeviceInfo) []templates.DeviceData {
result := make([]templates.DeviceData, len(deviceInfos))
for i, d := range deviceInfos {
lastSync := ""
if d.LastSync != nil {
lastSync = d.LastSync.Format(time.RFC3339)
}
lastSeen := ""
if d.LastSeen != nil {
lastSeen = d.LastSeen.Format(time.RFC3339)
}
result[i] = templates.DeviceData{
ID: d.ID.String(),
DeviceName: d.DeviceName,
DeviceType: d.DeviceType,
SyncEnabled: d.SyncEnabled,
LastSync: lastSync,
LastSeen: lastSeen,
}
}
return result
}
func convertPending(pending []map[string]interface{}) []templates.PendingRegistrationData {
result := make([]templates.PendingRegistrationData, len(pending))
for i, p := range pending {