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 }