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:
parent
aabdfa9e50
commit
633de945e4
39
TASKS/reports/T11-report.md
Normal file
39
TASKS/reports/T11-report.md
Normal 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
39
TASKS/review/T11.md
Normal 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
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user