diff --git a/docs/developer/ui-controls.md b/docs/developer/ui-controls.md index 690d2df1..7fba381d 100644 --- a/docs/developer/ui-controls.md +++ b/docs/developer/ui-controls.md @@ -182,9 +182,13 @@ label: Filters where: All Books / Podcasts page → toolbar does: Opens the filter drawer for building and applying rules. -label: Register Device -where: Devices page -does: Submits a device registration request for the pending-approval flow. +label: Find device +where: Devices page → Pair a Device +does: Looks up the pairing code shown on the registering e-reader and renders the confirm card. + +label: Pair device +where: Devices page → Pair a Device → confirm card +does: Binds the device behind the pairing code to the signed-in account. label: Download All where: Podcast page → episodes area diff --git a/internal/router/frontend.go b/internal/router/frontend.go index 345799ff..5abb79fe 100644 --- a/internal/router/frontend.go +++ b/internal/router/frontend.go @@ -1152,7 +1152,6 @@ func registerFrontendRoutes(cfg *Config) { var errorMsg string var devices []handlers.DeviceInfo - var pendingList []templates.PendingRegistrationData devices, err = cfg.DeviceHandler.GetDevicesData(c) if err != nil { @@ -1161,22 +1160,40 @@ func registerFrontendRoutes(cfg *Config) { errorMsg = "Error loading devices" } - pendingMaps, err := cfg.DeviceHandler.GetPendingRegistrationsData(c) - if err != nil { - log.Printf("GetPendingRegistrationsData failed: %v", err) - pendingList = []templates.PendingRegistrationData{} - if errorMsg == "" { - errorMsg = "Error loading pending registrations" - } - } else { - pendingList = convertPending(pendingMaps) - } - // Get base URL from database config with fallback to config/env var baseURL := cfg.getBaseURL(c.Request().Context()) var buf bytes.Buffer - err = templates.Devices(user, devices, pendingList, errorMsg, baseURL).Render(c.Request().Context(), &buf) + err = templates.Devices(user, devices, errorMsg, baseURL).Render(c.Request().Context(), &buf) + if err != nil { + return err + } + return c.HTML(http.StatusOK, buf.String()) + }) + + // Pairing-code lookup partial: renders the confirm card (or the + // reason nothing matched) into #pairing-lookup-result. Errors render + // as content — htmx only swaps 2xx bodies, and the pairing section is + // a web-only surface (the JSON API route keeps real status codes). + frontendProtected.POST("/devices/pairing/lookup", func(c *echo.Context) error { + userID, ok := c.Get("user_id").(string) + if !ok { + return c.HTML(http.StatusUnauthorized, "") + } + + status := "found" + var deviceName, deviceType, pairingCode string + registration, err := handlers.ResolvePairingCode(userID, c.RealIP(), c.FormValue("pairing_code")) + if err != nil { + status = handlers.PairingErrorStatus(err) + } else { + deviceName = registration.DeviceName + deviceType = registration.DeviceType + pairingCode = registration.PairingCode + } + + var buf bytes.Buffer + err = templates.PairingLookupResult(deviceName, deviceType, pairingCode, status).Render(c.Request().Context(), &buf) if err != nil { return err } @@ -1834,7 +1851,6 @@ func registerFrontendRoutes(cfg *Config) { } var errorMsg string var devices []handlers.DeviceInfo - var pendingList []templates.PendingRegistrationData devices, err = cfg.DeviceHandler.GetDevicesData(c) if err != nil { @@ -1843,22 +1859,11 @@ func registerFrontendRoutes(cfg *Config) { errorMsg = "Error loading devices" } - pendingMaps, err := cfg.DeviceHandler.GetPendingRegistrationsData(c) - if err != nil { - log.Printf("GetPendingRegistrationsData failed: %v", err) - pendingList = []templates.PendingRegistrationData{} - if errorMsg == "" { - errorMsg = "Error loading pending registrations" - } - } else { - pendingList = convertPending(pendingMaps) - } - // Get base URL from database config with fallback to config/env var baseURL := cfg.getBaseURL(c.Request().Context()) var buf bytes.Buffer - err = templates.Devices(user, devices, pendingList, errorMsg, baseURL).Render(c.Request().Context(), &buf) + err = templates.Devices(user, devices, errorMsg, baseURL).Render(c.Request().Context(), &buf) if err != nil { return err } diff --git a/internal/router/helpers.go b/internal/router/helpers.go index d31ed5a1..c85af973 100644 --- a/internal/router/helpers.go +++ b/internal/router/helpers.go @@ -82,19 +82,6 @@ func getTemplateUserWithTheme(c *echo.Context, cfg *Config) (templates.User, err }, 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 { diff --git a/templates/devices.templ b/templates/devices.templ index 9be8e07b..75e9acfe 100644 --- a/templates/devices.templ +++ b/templates/devices.templ @@ -2,7 +2,7 @@ package templates import "bookhoard/internal/handlers" -templ Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []PendingRegistrationData, errorMessage string, baseURL string) { +templ Devices(user User, devices []handlers.DeviceInfo, errorMessage string, baseURL string) {
@@ -157,56 +157,43 @@ templ Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []P } - if len(pendingRegistrations) > 0 { -- { reg.DeviceType } - Expires in { reg.ExpiresAt } -
-+ On your e-reader, open the Bookhoard sync menu and tap Register device — then enter the + 6-digit code it shows below. Only someone holding the device can see the code, so nobody + else can claim (or interfere with) your registration. +
+ ++ { deviceType } device · Pair it with your account? Its reading progress will sync here. +
++ No waiting device matches that code — check the digits on your e-reader's screen. +
+ } else if status == "expired" { ++ That code expired. Tap Register device on the device again for a fresh one. +
+ } else if status == "claimed" { ++ That code was already used. If the device isn't listed above, register it again from the device. +
+ } else if status == "rate_limited" { ++ Too many pairing attempts — wait a minute and try again. +
+ } +} diff --git a/templates/devices_templ.go b/templates/devices_templ.go index ded198ae..6ee55f83 100644 --- a/templates/devices_templ.go +++ b/templates/devices_templ.go @@ -10,7 +10,7 @@ import templruntime "github.com/a-h/templ/runtime" import "bookhoard/internal/handlers" -func Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []PendingRegistrationData, errorMessage string, baseURL string) templ.Component { +func Devices(user User, devices []handlers.DeviceInfo, errorMessage string, baseURL string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -406,92 +406,15 @@ func Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []Pe return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "On your e-reader, open the Bookhoard sync menu and tap Register device — then enter the 6-digit code it shows below. Only someone holding the device can see the code, so nobody else can claim (or interfere with) your registration.
plugins/ directory")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "KOReader Plugin Setup- 1 Clone the Bookhoard plugin to your KOReader
plugins/ directory - 2 Open KOReader, tap the wrench icon at the top
- 3 Find and tap Bookhoard sync
- 4 Tap Server URL, enter your server address, then tap OK:
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var22 string
- templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(baseURL)
+ var templ_7745c5c3_Var17 string
+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(baseURL)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `devices.templ`, Line: 234, Col: 148}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `devices.templ`, Line: 221, Col: 148}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, " - 5 Return to this page and refresh — you'll see a pending registration. Click Approve to connect the device.
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "- 5 Tap Register device — your e-reader shows a 6-digit pairing code.
- 6 Type that code into the Pair a Device field on this page and confirm — the device finishes connecting by itself.
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -549,7 +472,7 @@ func Devices(user User, devices []handlers.DeviceInfo, pendingRegistrations []Pe
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "Once approved, reading progress sync and OPDS catalog access are set up automatically.Loading mappings...
Loading mappings...