fix(qfs): project ui, config naming, sync timestamps - v1.5.4

This commit is contained in:
Mikhail Chusavitin
2026-03-16 08:32:15 +03:00
parent c599897142
commit 35c5600b36
16 changed files with 815 additions and 93 deletions

View File

@@ -39,6 +39,10 @@ logging:
t.Fatalf("load legacy config: %v", err)
}
setConfigDefaults(cfg)
cfg.Server.Host, _, err = normalizeLoopbackServerHost(cfg.Server.Host)
if err != nil {
t.Fatalf("normalize server host: %v", err)
}
if err := migrateConfigFileToRuntimeShape(path, cfg); err != nil {
t.Fatalf("migrate config: %v", err)
}
@@ -60,32 +64,43 @@ logging:
if !strings.Contains(text, "port: 9191") {
t.Fatalf("migrated config did not preserve server port:\n%s", text)
}
if !strings.Contains(text, "host: 127.0.0.1") {
t.Fatalf("migrated config did not normalize server host:\n%s", text)
}
if !strings.Contains(text, "level: debug") {
t.Fatalf("migrated config did not preserve logging level:\n%s", text)
}
}
func TestEnsureLoopbackServerHost(t *testing.T) {
func TestNormalizeLoopbackServerHost(t *testing.T) {
t.Parallel()
cases := []struct {
host string
wantErr bool
host string
want string
wantChanged bool
wantErr bool
}{
{host: "127.0.0.1", wantErr: false},
{host: "localhost", wantErr: false},
{host: "::1", wantErr: false},
{host: "0.0.0.0", wantErr: true},
{host: "192.168.1.10", wantErr: true},
{host: "127.0.0.1", want: "127.0.0.1", wantChanged: false, wantErr: false},
{host: "localhost", want: "127.0.0.1", wantChanged: true, wantErr: false},
{host: "::1", want: "127.0.0.1", wantChanged: true, wantErr: false},
{host: "0.0.0.0", want: "127.0.0.1", wantChanged: true, wantErr: false},
{host: "192.168.1.10", want: "127.0.0.1", wantChanged: true, wantErr: false},
}
for _, tc := range cases {
err := ensureLoopbackServerHost(tc.host)
got, changed, err := normalizeLoopbackServerHost(tc.host)
if tc.wantErr && err == nil {
t.Fatalf("expected error for host %q", tc.host)
}
if !tc.wantErr && err != nil {
t.Fatalf("unexpected error for host %q: %v", tc.host, err)
}
if got != tc.want {
t.Fatalf("unexpected normalized host for %q: got %q want %q", tc.host, got, tc.want)
}
if changed != tc.wantChanged {
t.Fatalf("unexpected changed flag for %q: got %t want %t", tc.host, changed, tc.wantChanged)
}
}
}

View File

@@ -148,10 +148,15 @@ func main() {
}
}
setConfigDefaults(cfg)
if err := ensureLoopbackServerHost(cfg.Server.Host); err != nil {
normalizedHost, changed, err := normalizeLoopbackServerHost(cfg.Server.Host)
if err != nil {
slog.Error("invalid server host", "host", cfg.Server.Host, "error", err)
os.Exit(1)
}
if changed {
slog.Warn("corrected server host to loopback", "from", cfg.Server.Host, "to", normalizedHost)
}
cfg.Server.Host = normalizedHost
if err := migrateConfigFileToRuntimeShape(resolvedConfigPath, cfg); err != nil {
slog.Error("failed to migrate config file format", "path", resolvedConfigPath, "error", err)
os.Exit(1)
@@ -334,21 +339,28 @@ func setConfigDefaults(cfg *config.Config) {
}
}
func ensureLoopbackServerHost(host string) error {
func normalizeLoopbackServerHost(host string) (string, bool, error) {
trimmed := strings.TrimSpace(host)
if trimmed == "" {
return fmt.Errorf("server.host must not be empty")
return "", false, fmt.Errorf("server.host must not be empty")
}
const loopbackHost = "127.0.0.1"
if trimmed == loopbackHost {
return loopbackHost, false, nil
}
if strings.EqualFold(trimmed, "localhost") {
return nil
return loopbackHost, true, nil
}
ip := net.ParseIP(strings.Trim(trimmed, "[]"))
if ip != nil && ip.IsLoopback() {
return nil
if ip != nil {
if ip.IsLoopback() || ip.IsUnspecified() {
return loopbackHost, trimmed != loopbackHost, nil
}
return loopbackHost, true, nil
}
return fmt.Errorf("QuoteForge local client must bind to localhost only")
return loopbackHost, true, nil
}
func vendorImportBodyLimit() int64 {
@@ -1490,6 +1502,8 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
project, err := projectService.Create(dbUsername, &req)
if err != nil {
switch {
case errors.Is(err, services.ErrReservedMainVariant):
respondError(c, http.StatusBadRequest, "invalid request", err)
case errors.Is(err, services.ErrProjectCodeExists):
respondError(c, http.StatusConflict, "conflict detected", err)
default:
@@ -1525,6 +1539,8 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
project, err := projectService.Update(c.Param("uuid"), dbUsername, &req)
if err != nil {
switch {
case errors.Is(err, services.ErrReservedMainVariant):
respondError(c, http.StatusBadRequest, "invalid request", err)
case errors.Is(err, services.ErrProjectCodeExists):
respondError(c, http.StatusConflict, "conflict detected", err)
case errors.Is(err, services.ErrProjectNotFound):