- Add per-item notes editable in the detail and edit screens, synced between desktop and Android - Guard against duplicates by external ID and by type/year/title, mirroring the desktop findDuplicate logic in MediaDao - Wrap multi-step database operations in Room transactions - Disable Android auto backup and remove the destructive Room migration fallback so schema bumps fail loudly instead of wiping data - Bound crypto envelope KDF parameters when reading untrusted headers - Handle malformed server responses in SyncClient instead of crashing - Extend settings, database, and image cache on the desktop side
48 lines
1.1 KiB
Go
48 lines
1.1 KiB
Go
package main
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// rateLimiter is a minimal in-memory fixed-window limiter keyed by string
|
|
// (bearer-token subject for the TMDB proxy). It blunts runaway clients abusing
|
|
// the shared API key; it is not a full DoS defense.
|
|
type rateLimiter struct {
|
|
mu sync.Mutex
|
|
limit int
|
|
window time.Duration
|
|
counts map[string]rateEntry
|
|
}
|
|
|
|
type rateEntry struct {
|
|
windowStart time.Time
|
|
count int
|
|
}
|
|
|
|
func newRateLimiter(limit int, per time.Duration) *rateLimiter {
|
|
return &rateLimiter{limit: limit, window: per, counts: map[string]rateEntry{}}
|
|
}
|
|
|
|
func (r *rateLimiter) allow(key string) bool {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
e, ok := r.counts[key]
|
|
if !ok || now.Sub(e.windowStart) >= r.window {
|
|
// Occasionally garbage-collect stale keys so the map stays bounded.
|
|
if len(r.counts) > 10_000 {
|
|
for k, v := range r.counts {
|
|
if now.Sub(v.windowStart) >= r.window {
|
|
delete(r.counts, k)
|
|
}
|
|
}
|
|
}
|
|
r.counts[key] = rateEntry{windowStart: now, count: 1}
|
|
return true
|
|
}
|
|
e.count++
|
|
r.counts[key] = e
|
|
return e.count <= r.limit
|
|
}
|