Repo-wide gofmt sweep: const-block alignment, doc-comment spacing, statement-after-brace fix in media.go, trailing blank lines. No semantic changes — whitespace-insensitive diff is empty for every file in this commit.
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()
|
|
}
|