diff --git a/internal/config/config.go b/internal/config/config.go index e3cb35d..a68adf9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,6 +1,7 @@ package config import ( + "context" "fmt" "os" "strconv" @@ -44,6 +45,30 @@ func (c *Config) DatabaseURL() string { c.DatabaseUser, c.DatabasePassword, c.DatabaseHost, c.DatabasePort, c.DatabaseName) } +// GetBaseURL returns the base URL from system configuration database with fallback to config/env var +func GetBaseURL(ctx context.Context, db interface{}) string { + // Try to get from database first + type SystemConfigQuerier interface { + GetSystemConfig(ctx context.Context, key string) (SystemConfigRow, error) + } + + if querier, ok := db.(SystemConfigQuerier); ok { + config, err := querier.GetSystemConfig(ctx, "base_url") + if err == nil && config.Value != "" { + return config.Value + } + } + + // Fallback: return empty string - caller should use their own fallback + return "" +} + +// SystemConfigRow represents a system configuration row +type SystemConfigRow struct { + Key string + Value string +} + func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value diff --git a/internal/handlers/opds.go b/internal/handlers/opds.go index 654643c..962d3b2 100644 --- a/internal/handlers/opds.go +++ b/internal/handlers/opds.go @@ -38,19 +38,15 @@ func NewOPDSHandler(db *database.Queries, libraryService *services.LibraryServic } } -// Helper function to get base URLs from system config +// Helper function to get base URL from system config func (h *OPDSHandler) getBaseURLs(c *echo.Context) (string, string, error) { baseURL, err := h.db.GetSystemConfig(c.Request().Context(), "base_url") if err != nil { return "", "", fmt.Errorf("failed to get base_url from config: %w", err) } - opdsBaseURL, err := h.db.GetSystemConfig(c.Request().Context(), "opds_base_url") - if err != nil { - return "", "", fmt.Errorf("failed to get opds_base_url from config: %w", err) - } - - return baseURL.Value, opdsBaseURL.Value, nil + opdsBaseURL := baseURL.Value + "/opds" + return baseURL.Value, opdsBaseURL, nil } // GetDeviceCatalog returns the OPDS catalog feed for a device diff --git a/internal/handlers/sidecar.go b/internal/handlers/sidecar.go index 2bc89e5..1563778 100644 --- a/internal/handlers/sidecar.go +++ b/internal/handlers/sidecar.go @@ -1,6 +1,7 @@ package handlers import ( + "bookhoard/internal/config" "bookhoard/internal/database" "encoding/json" "fmt" @@ -13,11 +14,12 @@ import ( ) type SidecarHandler struct { - db *database.Queries + db *database.Queries + cfg *config.Config } -func NewSidecarHandler(db *database.Queries) *SidecarHandler { - return &SidecarHandler{db: db} +func NewSidecarHandler(db *database.Queries, cfg *config.Config) *SidecarHandler { + return &SidecarHandler{db: db, cfg: cfg} } type SidecarConfig struct { @@ -79,13 +81,15 @@ func (h *SidecarHandler) GetSidecarConfig(c *echo.Context) error { userID := device.UserID.Bytes pgUserID := pgtype.UUID{Bytes: userID, Valid: true} - // Get system config - opdsBaseURL, _ := h.db.GetSystemConfig(ctx, "opds_base_url") - apiBaseURL, _ := h.db.GetSystemConfig(ctx, "api_base_url") + // Get base URL and compute paths + baseURL, _ := h.db.GetSystemConfig(ctx, "base_url") + if baseURL.Value == "" { + baseURL.Value = h.cfg.BaseURL + } // Generate URLs - opdsCatalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL.Value, deviceID.String()) - syncAPIURL := fmt.Sprintf("%s/sync/kobo", apiBaseURL.Value) + opdsCatalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", baseURL.Value, deviceID.String()) + syncAPIURL := fmt.Sprintf("%s/api/sync/kobo", baseURL.Value) // Get user's visible libraries with media items mediaItems, err := h.db.GetUserMediaItemsForSync(ctx, pgUserID) @@ -172,8 +176,8 @@ func (h *SidecarHandler) GetSidecarConfig(c *echo.Context) error { Bookhoard: SidecarBookhoardConfig{ OPDSCatalog: opdsCatalogURL, SyncAPI: syncAPIURL, - OPDSBaseURL: opdsBaseURL.Value, - APIBaseURL: apiBaseURL.Value, + OPDSBaseURL: baseURL.Value + "/opds", + APIBaseURL: baseURL.Value + "/api", DeviceID: deviceID.String(), DeviceToken: device.AuthToken, }, @@ -215,13 +219,15 @@ func (h *SidecarHandler) DownloadSidecarConfig(c *echo.Context) error { userID := device.UserID.Bytes pgUserID := pgtype.UUID{Bytes: userID, Valid: true} - // Get system config - opdsBaseURL, _ := h.db.GetSystemConfig(ctx, "opds_base_url") - apiBaseURL, _ := h.db.GetSystemConfig(ctx, "api_base_url") + // Get base URL and compute paths + baseURL, _ := h.db.GetSystemConfig(ctx, "base_url") + if baseURL.Value == "" { + baseURL.Value = h.cfg.BaseURL + } // Generate URLs - opdsCatalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", opdsBaseURL.Value, deviceID.String()) - syncAPIURL := fmt.Sprintf("%s/sync/kobo", apiBaseURL.Value) + opdsCatalogURL := fmt.Sprintf("%s/opds/devices/%s/catalog", baseURL.Value, deviceID.String()) + syncAPIURL := fmt.Sprintf("%s/api/sync/kobo", baseURL.Value) // Get user's visible libraries with media items mediaItems, err := h.db.GetUserMediaItemsForSync(ctx, pgUserID) @@ -303,8 +309,8 @@ func (h *SidecarHandler) DownloadSidecarConfig(c *echo.Context) error { Bookhoard: SidecarBookhoardConfig{ OPDSCatalog: opdsCatalogURL, SyncAPI: syncAPIURL, - OPDSBaseURL: opdsBaseURL.Value, - APIBaseURL: apiBaseURL.Value, + OPDSBaseURL: baseURL.Value + "/opds", + APIBaseURL: baseURL.Value + "/api", DeviceID: deviceID.String(), DeviceToken: device.AuthToken, }, @@ -394,6 +400,56 @@ func (h *SidecarHandler) UpdateSystemConfiguration(c *echo.Context) error { } } + // Check for HTMX request + if c.Request().Header.Get("HX-Request") == "true" { + // Fetch updated base_url for template + baseURL, err := h.db.GetSystemConfig(ctx, "base_url") + if err != nil || baseURL.Value == "" { + return c.HTML(http.StatusInternalServerError, `
Failed to fetch updated configuration
`) + } + + // Render success message with updated form + return c.HTML(http.StatusOK, fmt.Sprintf(` +
+

✅ Settings saved successfully!

+
+
+
+

Base URL

+ +
+ + +

The public URL of your Bookhoard instance (e.g., https://books.example.com)

+
+ +
+ +
+
+
+ +
+

URL Paths

+
+

OPDS: %s/opds

+

API: %s/api

+

Device Sync: %s/api/sync

+
+
+`, baseURL.Value, baseURL.Value, baseURL.Value)) + } + return c.JSON(http.StatusOK, map[string]string{ "status": "success", "message": "System configuration updated",