Three bugs fixed: 1. Schema seeded base_url with fake placeholder 'bookhoard.example.com'. Removed seed; startup now seeds from BASE_URL env var only if DB row is empty (admin changes persist across restarts). One-time UPDATE clears the placeholder in existing installs. 2. config.GetBaseURL() had a broken type assertion (local SystemConfigRow vs database.SystemConfig) that always failed, returning . Admin panel showed env var fallback instead of actual DB value. Fixed with a function-type getter that properly wraps the DB query. 3. OPDS handler read base_url only from DB with no fallback. When DB had the placeholder, all feed links pointed to an unreachable domain, breaking KOReader search/download. Added deriveBaseURL() helper that falls back to the request Host/scheme when DB value is empty. Setup gate improvements: - isSetupComplete now requires both admin user AND non-empty base_url - Setup middleware no longer exempts all /api/ routes; only allows /api/auth/register, /api/auth/login, /api/system/config before setup is complete. All other API routes get 503. - Cache invalidated when base_url is saved via admin settings Dev workflow: - New bruno/NewDevDBSetup/SetBaseUrl.yml for dev DB setup - NewDB.sh runs SetBaseUrl between RegisterUser and CreateEbookLibrary
80 lines
2.5 KiB
Go
80 lines
2.5 KiB
Go
// Package setupstatus reports whether the application's initial setup has been
|
|
// completed. Setup is considered complete when at least one admin user exists
|
|
// AND a non-empty base_url has been configured, regardless of how those were
|
|
// created (setup wizard, API, or a future CLI). This keeps the setup gate a
|
|
// derived property of real data rather than a manually-flipped flag that can
|
|
// drift out of sync.
|
|
package setupstatus
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// AdminCounter is satisfied by *database.Queries. It is defined as an interface
|
|
// here so this package does not import the database package, keeping the
|
|
// dependency graph flat and avoiding import cycles.
|
|
type AdminCounter interface {
|
|
CountAdmins(ctx context.Context) (int64, error)
|
|
}
|
|
|
|
// BaseURLGetter returns the configured base_url value from the database, or an
|
|
// error if it cannot be read. Defined as a function type (not an interface) so
|
|
// it can be satisfied by a closure wrapping *database.Queries.GetSystemConfig
|
|
// without importing the database package.
|
|
type BaseURLGetter func(ctx context.Context) (string, error)
|
|
|
|
var (
|
|
cacheMu sync.RWMutex
|
|
cacheComplete bool = true
|
|
cacheExpiry time.Time
|
|
cacheTTL = 10 * time.Second
|
|
)
|
|
|
|
// IsSetupComplete reports whether setup is complete. Setup is complete when at
|
|
// least one admin user exists AND base_url is configured. A short in-memory
|
|
// cache avoids hammering the database on every request. On a database error the
|
|
// function fails open (returns true) so a transient outage does not lock users
|
|
// out of the app.
|
|
func IsSetupComplete(ctx context.Context, q AdminCounter, baseURLGetter BaseURLGetter) bool {
|
|
cacheMu.RLock()
|
|
if time.Now().Before(cacheExpiry) {
|
|
complete := cacheComplete
|
|
cacheMu.RUnlock()
|
|
return complete
|
|
}
|
|
cacheMu.RUnlock()
|
|
|
|
complete := true
|
|
|
|
count, err := q.CountAdmins(ctx)
|
|
if err == nil {
|
|
complete = count > 0
|
|
}
|
|
|
|
if complete && baseURLGetter != nil {
|
|
baseURL, err := baseURLGetter(ctx)
|
|
if err == nil {
|
|
complete = baseURL != ""
|
|
}
|
|
}
|
|
|
|
cacheMu.Lock()
|
|
cacheComplete = complete
|
|
cacheExpiry = time.Now().Add(cacheTTL)
|
|
cacheMu.Unlock()
|
|
return complete
|
|
}
|
|
|
|
// Invalidate clears the cached setup status so the next call to IsSetupComplete
|
|
// re-reads from the database. Call this after any write that could change the
|
|
// admin user count (user creation, role promotion/demotion, user deletion) or
|
|
// the base_url configuration.
|
|
func Invalidate() {
|
|
cacheMu.Lock()
|
|
cacheComplete = true
|
|
cacheExpiry = time.Time{}
|
|
cacheMu.Unlock()
|
|
}
|