T11: Dodat Cache-Control no-store za svež prikaz sa diska

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
djuka 2026-02-20 12:28:42 +00:00
parent aabdfa9e50
commit 633de945e4
4 changed files with 156 additions and 0 deletions

View File

@ -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

39
TASKS/review/T11.md Normal file
View File

@ -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

View File

@ -68,6 +68,14 @@ func New(cfg *config.Config) *Server {
Router: router, 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() s.setupRoutes()
return s return s
} }

View File

@ -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 { func containsStr(s, substr string) bool {
return len(s) >= len(substr) && findStr(s, substr) return len(s) >= len(substr) && findStr(s, substr)
} }