Files
bookhoard/internal/router/helpers.go
T
john-okeefe 1f8c3c21ae fix(admin): cascade base_url to opds/api URLs, fix pending registration time format
- UpdateSystemConfiguration: when base_url changes, automatically
  update opds_base_url and api_base_url derived configs
- convertPending: format time.Time as RFC3339 string instead of
  relying on string type assertion which would panic
2026-06-02 19:45:39 -04:00

166 lines
4.0 KiB
Go

package router
import (
"context"
"log"
"net/url"
"time"
"bookhoard/internal/database"
"bookhoard/templates"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/labstack/echo/v5"
)
func getTemplateUserWithTheme(c *echo.Context, cfg *Config) (templates.User, error) {
userID := c.Get("user_id").(string)
userEmail := c.Get("user_email").(string)
userUsername := c.Get("user_username").(string)
userRole := c.Get("user_role").(string)
userUUID, err := uuid.Parse(userID)
if err != nil {
log.Printf("getTemplateUserWithTheme failed: invalid UUID '%s': %v", userID, err)
return templates.User{}, err
}
userDB, err := cfg.Queries.GetUser(c.Request().Context(), uuidToPGType(userUUID))
if err != nil {
log.Printf("getTemplateUserWithTheme failed: database query error for user ID %s: %v", userID, err)
return templates.User{}, err
}
userTheme := "tokyo-night"
if userDB.Theme.Valid {
userTheme = userDB.Theme.String
}
userTimezone := "UTC"
if userDB.Timezone.Valid {
userTimezone = userDB.Timezone.String
}
// Extract JWT token for WebSocket authentication
token := ""
if cookie, err := c.Cookie("token"); err == nil {
token = cookie.Value
}
return templates.User{
ID: userID,
Email: userEmail,
Username: userUsername,
Role: userRole,
Theme: userTheme,
Token: token,
Timezone: userTimezone,
}, nil
}
func convertPending(pending []map[string]interface{}) []templates.PendingRegistrationData {
result := make([]templates.PendingRegistrationData, len(pending))
for i, p := range pending {
result[i] = templates.PendingRegistrationData{
RegistrationID: p["registration_id"].(string),
DeviceName: p["device_name"].(string),
DeviceType: p["device_type"].(string),
ExpiresAt: p["expires_at"].(time.Time).Format(time.RFC3339),
}
}
return result
}
func pingDB(cfg *Config, ctx context.Context) error {
if cfg.DBPool != nil {
if pool, ok := cfg.DBPool.(*pgxpool.Pool); ok {
return pool.Ping(ctx)
}
}
return nil
}
func parseUUID(s string) (uuid.UUID, error) {
return uuid.Parse(s)
}
func uuidToPGType(u uuid.UUID) pgtype.UUID {
return pgtype.UUID{Bytes: u, Valid: true}
}
const selectedLibraryCookie = "selectedLibrary"
const allLibrariesSentinel = "__all__"
type LibraryResolution struct {
LibraryID string
IsAll bool
LibUUID pgtype.UUID
Libraries []templates.LibraryData
FirstID string
}
func getText(t pgtype.Text) string {
if t.Valid {
return t.String
}
return ""
}
func resolveLibrary(c *echo.Context, cfg *Config, userUUID string) LibraryResolution {
res := LibraryResolution{}
userU, _ := uuid.Parse(userUUID)
libraries, err := cfg.Queries.GetUserVisibleLibraries(c.Request().Context(), uuidToPGType(userU))
if err != nil {
log.Printf("GetUserVisibleLibraries failed: %v", err)
libraries = []database.GetUserVisibleLibrariesRow{}
}
res.Libraries = make([]templates.LibraryData, len(libraries))
for i, lib := range libraries {
libUUID, _ := uuid.FromBytes(lib.ID.Bytes[0:16])
res.Libraries[i] = templates.LibraryData{
ID: libUUID.String(),
Name: lib.Name,
Description: getText(lib.Description),
TypeName: lib.TypeName,
}
}
if len(libraries) > 0 {
libUUID, _ := uuid.FromBytes(libraries[0].ID.Bytes[0:16])
res.FirstID = libUUID.String()
}
libraryID := c.QueryParam("library_id")
if libraryID == "" {
if cookie, err := c.Cookie(selectedLibraryCookie); err == nil {
val, _ := url.QueryUnescape(cookie.Value)
if val == allLibrariesSentinel {
res.IsAll = true
res.LibraryID = ""
return res
}
if _, parseErr := uuid.Parse(val); parseErr == nil {
libraryID = val
}
}
}
if libraryID == "" {
res.LibraryID = res.FirstID
if res.LibraryID != "" {
parsed, _ := uuid.Parse(res.LibraryID)
res.LibUUID = pgtype.UUID{Bytes: parsed, Valid: true}
}
return res
}
res.LibraryID = libraryID
parsed, _ := uuid.Parse(libraryID)
res.LibUUID = pgtype.UUID{Bytes: parsed, Valid: true}
return res
}