The audiobook library type shipped backend-complete (LibraryTypeAudiobooks constant, allowed extensions, MIME map, schema seed row, GET /api/libraries/types) but neither create-library form offered it: the admin Create Library modal and the setup wizard's add-library step both hardcoded their <option> lists to the original three types (ebooks/comics/manga), so an audiobook library could not be created from the UI. Both dropdowns now render from the same DB-backed library_types table the create API resolves types against — a future type appears without template edits (the exact drift the rendering-ownership rule warns about): - templates/utils.go: OrderedLibraryTypes pins the historical dropdown order (ebooks, comics, manga, audiobooks) and appends unknown future types by name; LibraryTypeDisplayName renders labels with a capitalize-the-name fallback for types added without a label - templates/admin_library.templ: new LibraryTypeOptions fragment used by the modal's select; AdminLibrary takes the types list - templates/setup.templ: Setup takes the types list, same fragment - internal/router/helpers.go: libraryTypeOptionsFor fetches via LibraryService.GetLibraryTypes, falling back to the built-in type names on DB failure so the forms stay usable (CreateLibrary still resolves the name against the DB, so the fallback is never authoritative) - internal/router/frontend.go, internal/router/setup.go: pass the list into the two pages Tests: - templates/utils_test.go: pure unit tests for ordering + labels - cmd/server/tests/admin_library_page_test.go: SSR regression — /admin/library must offer all four library types, audiobooks exactly once (options come from the DB loop, not a hardcode)
92 lines
2.4 KiB
Go
92 lines
2.4 KiB
Go
package router
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"bookhoard/internal/setupstatus"
|
|
"bookhoard/templates"
|
|
|
|
"github.com/labstack/echo/v5"
|
|
)
|
|
|
|
func isSetupComplete(cfg *Config) bool {
|
|
getter := func(ctx context.Context) (string, error) {
|
|
row, err := cfg.Queries.GetSystemConfig(ctx, "base_url")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return row.Value, nil
|
|
}
|
|
return setupstatus.IsSetupComplete(context.Background(), cfg.Queries, getter)
|
|
}
|
|
|
|
// setupAllowedAPIRoutes lists API endpoints that remain accessible before
|
|
// initial setup is complete so the server can be configured via API.
|
|
var setupAllowedAPIRoutes = []string{
|
|
"/api/auth/register",
|
|
"/api/auth/login",
|
|
"/api/system/config",
|
|
}
|
|
|
|
// isAllowedDuringSetup reports whether a request path should bypass the setup
|
|
// gate. This includes the setup page itself, static assets, health checks, and
|
|
// the minimal set of API routes needed to perform initial configuration.
|
|
func isAllowedDuringSetup(path string) bool {
|
|
if path == "/setup" || path == "/setup/" {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(path, "/static/") || path == "/health" || path == "/favicon.ico" {
|
|
return true
|
|
}
|
|
for _, route := range setupAllowedAPIRoutes {
|
|
if path == route || strings.HasPrefix(path, route+"/") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func setupRedirectMiddleware(cfg *Config) echo.MiddlewareFunc {
|
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c *echo.Context) error {
|
|
path := c.Request().URL.Path
|
|
|
|
if isAllowedDuringSetup(path) {
|
|
return next(c)
|
|
}
|
|
|
|
if !isSetupComplete(cfg) {
|
|
if strings.HasPrefix(path, "/api/") {
|
|
return c.JSON(http.StatusServiceUnavailable, map[string]string{
|
|
"error": "Server setup is not complete. Configure an admin account and base_url via the setup wizard or API.",
|
|
})
|
|
}
|
|
return c.Redirect(http.StatusFound, "/setup")
|
|
}
|
|
|
|
return next(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func registerSetupRoutes(cfg *Config) {
|
|
e := cfg.Echo
|
|
|
|
e.GET("/setup", func(c *echo.Context) error {
|
|
if isSetupComplete(cfg) {
|
|
return c.Redirect(http.StatusFound, "/")
|
|
}
|
|
var buf bytes.Buffer
|
|
libraryTypes := libraryTypeOptionsFor(c.Request().Context(), cfg)
|
|
if err := templates.Setup(libraryTypes).Render(c.Request().Context(), &buf); err != nil {
|
|
log.Printf("Failed to render setup template: %v", err)
|
|
return c.HTML(http.StatusInternalServerError, "Failed to render setup page")
|
|
}
|
|
return c.HTML(http.StatusOK, buf.String())
|
|
})
|
|
}
|