From 633de945e46c6c86b86dbf6c260e717c242554e5 Mon Sep 17 00:00:00 2001 From: djuka Date: Fri, 20 Feb 2026 12:28:42 +0000 Subject: [PATCH] =?UTF-8?q?T11:=20Dodat=20Cache-Control=20no-store=20za=20?= =?UTF-8?q?sve=C5=BE=20prikaz=20sa=20diska?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- TASKS/reports/T11-report.md | 39 ++++++++++++++++ TASKS/review/T11.md | 39 ++++++++++++++++ code/internal/server/server.go | 8 ++++ code/internal/server/server_test.go | 70 +++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 TASKS/reports/T11-report.md create mode 100644 TASKS/review/T11.md diff --git a/TASKS/reports/T11-report.md b/TASKS/reports/T11-report.md new file mode 100644 index 0000000..052d730 --- /dev/null +++ b/TASKS/reports/T11-report.md @@ -0,0 +1,39 @@ +# T11 Izveštaj: Fix — server uvek čita svež stanje sa diska + +**Agent:** coder +**Model:** Opus +**Datum:** 2026-02-20 + +--- + +## Šta je urađeno + +### Analiza + +Server kod (`ScanTasks()`) već čita sa diska na svaki request — nema internog keša. +Problem je bio u **browser keširanju**: nije bio postavljen `Cache-Control` header, +pa browser može servirati stale HTML/JSON iz svog keša. + +### Fix + +Dodat middleware u `server.go` koji postavlja `Cache-Control: no-store, no-cache, must-revalidate` +na sve dinamičke rute (osim `/static/*` koji su immutable). + +### Izmenjeni fajlovi + +| Fajl | Izmena | +|------|--------| +| `internal/server/server.go` | Cache-Control middleware za dinamičke rute | +| `internal/server/server_test.go` | 2 nova testa | + +### Novi testovi + +``` +TestNoCacheHeaders PASS — proverava Cache-Control na / i /api/tasks +TestDashboardReflectsDiskChanges PASS — premesti fajl na disku, sledeći request vidi promenu +``` + +### Ukupno projekat: 92 testa, svi prolaze + +- `go vet ./...` — čist +- `go build ./...` — prolazi diff --git a/TASKS/review/T11.md b/TASKS/review/T11.md new file mode 100644 index 0000000..6dfeb29 --- /dev/null +++ b/TASKS/review/T11.md @@ -0,0 +1,39 @@ +# T11: Fix — server uvek čita svež stanje sa diska + +**Kreirao:** planer +**Datum:** 2026-02-20 +**Agent:** coder +**Model:** Sonnet +**Zavisi od:** T10 ✅ + +--- + +## Opis + +BUG: Dashboard prikazuje stare podatke jer server kešira taskove. +Server MORA da čita fajlove sa diska na SVAKI request. +ScanTasks() se poziva svaki put kad neko otvori dashboard ili API. + +## Pravilo + +Nema keša za taskove. Disk je izvor istine. +Svaki GET / i GET /api/tasks poziva ScanTasks() iznova. + +## Test + +1. Pokreni server +2. Ručno premesti fajl iz backlog/ u ready/ (terminal) +3. Osveži dashboard (F5) — task mora biti u Ready koloni +4. Bez restart-a servera + +## Očekivani izlaz + +Dashboard uvek prikazuje tačno stanje sa diska. + +--- + +## Pitanja + +--- + +## Odgovori diff --git a/code/internal/server/server.go b/code/internal/server/server.go index 2cd51f6..f3f36ce 100644 --- a/code/internal/server/server.go +++ b/code/internal/server/server.go @@ -68,6 +68,14 @@ func New(cfg *config.Config) *Server { Router: router, } + // No caching for dynamic routes — disk is the source of truth. + router.Use(func(c *gin.Context) { + if !strings.HasPrefix(c.Request.URL.Path, "/static") { + c.Header("Cache-Control", "no-store, no-cache, must-revalidate") + } + c.Next() + }) + s.setupRoutes() return s } diff --git a/code/internal/server/server_test.go b/code/internal/server/server_test.go index 5ab60e2..4d2b94f 100644 --- a/code/internal/server/server_test.go +++ b/code/internal/server/server_test.go @@ -478,6 +478,76 @@ func TestIsMoveAllowed(t *testing.T) { } } +func TestNoCacheHeaders(t *testing.T) { + srv := setupTestServer(t) + + // Dynamic route should have no-cache headers + req := httptest.NewRequest(http.MethodGet, "/", nil) + w := httptest.NewRecorder() + srv.Router.ServeHTTP(w, req) + + cc := w.Header().Get("Cache-Control") + if !containsStr(cc, "no-store") { + t.Errorf("expected Cache-Control no-store on dashboard, got %q", cc) + } + + // API route should also have no-cache headers + req2 := httptest.NewRequest(http.MethodGet, "/api/tasks", nil) + w2 := httptest.NewRecorder() + srv.Router.ServeHTTP(w2, req2) + + cc2 := w2.Header().Get("Cache-Control") + if !containsStr(cc2, "no-store") { + t.Errorf("expected Cache-Control no-store on API, got %q", cc2) + } +} + +func TestDashboardReflectsDiskChanges(t *testing.T) { + srv := setupTestServer(t) + + // Initial state: T08 in backlog + req := httptest.NewRequest(http.MethodGet, "/api/tasks", nil) + w := httptest.NewRecorder() + srv.Router.ServeHTTP(w, req) + + var tasks1 []taskResponse + json.Unmarshal(w.Body.Bytes(), &tasks1) + + found := false + for _, task := range tasks1 { + if task.ID == "T08" && task.Status == "backlog" { + found = true + } + } + if !found { + t.Fatal("expected T08 in backlog initially") + } + + // Move file on disk (simulating external change) + os.Rename( + filepath.Join(srv.Config.TasksDir, "backlog", "T08.md"), + filepath.Join(srv.Config.TasksDir, "ready", "T08.md"), + ) + + // Second request should reflect the change without server restart + req2 := httptest.NewRequest(http.MethodGet, "/api/tasks", nil) + w2 := httptest.NewRecorder() + srv.Router.ServeHTTP(w2, req2) + + var tasks2 []taskResponse + json.Unmarshal(w2.Body.Bytes(), &tasks2) + + found = false + for _, task := range tasks2 { + if task.ID == "T08" && task.Status == "ready" { + found = true + } + } + if !found { + t.Fatal("expected T08 in ready after disk move — server did not read fresh state") + } +} + func containsStr(s, substr string) bool { return len(s) >= len(substr) && findStr(s, substr) }