feat(demo): reset the demo instance every 20 minutes with a countdown
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m34s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m34s
DEMO_RESET_MINUTES (default 20, 0 = off) resets the database to the sample data. Demo accounts get fixed IDs so sessions survive; requests hold a read lock so the reset never runs under a half-finished request (streams excluded). DEMO_RESET goes to every connection, /auth/status announces the next reset. The app shows "✨ Demo · Reset in MM:SS" in place of the version, then clears cached answers, remounts the views, reloads in the background and shows a short notice; if no event arrives (long polling) it asks the server after the countdown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
afd3b0347c
commit
6b9eb62caf
12 files changed
+348
-9
No files matched your search
@@ -0,0 +1,77 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// In demo mode the database is reset to the sample data every DEMO_RESET_MINUTES. Requests hold a read
|
||||
// lock while they run and the reset takes the write lock, so no request sees half-dropped tables.
|
||||
// Long-lived streams (WebSocket, SSE, long polling) are left out, otherwise the reset would wait for
|
||||
// them forever; they reconnect or retry on their own. The demo accounts keep their IDs, so sessions
|
||||
// stay valid; clients get DEMO_RESET and reload their data.
|
||||
|
||||
type demoState struct {
|
||||
gate sync.RWMutex // read: a running request, write: the reset
|
||||
|
||||
mu sync.Mutex
|
||||
nextReset time.Time
|
||||
}
|
||||
|
||||
func isLongLivedRequest(r *http.Request) bool {
|
||||
p := r.URL.Path
|
||||
return strings.HasPrefix(p, "/api/v1/ws") || p == "/api/v1/events" ||
|
||||
(strings.HasPrefix(p, "/api/v1/docs/") && strings.HasSuffix(p, "/changes"))
|
||||
}
|
||||
|
||||
func (s *Server) demoResetGate(next http.Handler) http.Handler {
|
||||
if !s.cfg.DemoMode {
|
||||
return next
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if isLongLivedRequest(r) || !strings.HasPrefix(r.URL.Path, "/api/") {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
s.demo.gate.RLock()
|
||||
defer s.demo.gate.RUnlock()
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// demoNextReset is zero when no periodic reset runs.
|
||||
func (s *Server) demoNextReset() time.Time {
|
||||
s.demo.mu.Lock()
|
||||
defer s.demo.mu.Unlock()
|
||||
return s.demo.nextReset
|
||||
}
|
||||
|
||||
func (s *Server) setDemoNextReset(t time.Time) {
|
||||
s.demo.mu.Lock()
|
||||
s.demo.nextReset = t
|
||||
s.demo.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Server) resetDemo() {
|
||||
s.demo.gate.Lock()
|
||||
err := s.store.ResetAndSeedDemo()
|
||||
s.demo.gate.Unlock()
|
||||
if err != nil {
|
||||
slog.Error("Demo-Reset fehlgeschlagen", "error", err)
|
||||
return
|
||||
}
|
||||
s.wsHub.BroadcastSystem("DEMO_RESET")
|
||||
}
|
||||
|
||||
func (s *Server) startDemoResetTicker() {
|
||||
interval := time.Duration(s.cfg.DemoResetMinutes) * time.Minute
|
||||
for {
|
||||
next := time.Now().Add(interval)
|
||||
s.setDemoNextReset(next)
|
||||
time.Sleep(time.Until(next))
|
||||
s.resetDemo()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user