feat: add item notes and duplicate detection across platforms
- 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
This commit is contained in:
parent
85f5c6dd4e
commit
2b35139364
30 changed files with 837 additions and 124 deletions
|
|
@ -28,6 +28,7 @@ type server struct {
|
|||
oauth oauth2.Config
|
||||
client *http.Client
|
||||
store *store // nil when DATABASE_URL is unset (sync disabled)
|
||||
tmdbLim *rateLimiter
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
@ -54,6 +55,8 @@ func main() {
|
|||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
},
|
||||
client: &http.Client{Timeout: 15 * time.Second},
|
||||
// Generous per-user cap on proxied TMDB calls (shared API key).
|
||||
tmdbLim: newRateLimiter(120, time.Minute),
|
||||
}
|
||||
|
||||
if cfg.DatabaseURL != "" {
|
||||
|
|
@ -184,7 +187,9 @@ func (s *server) handleCallback(w http.ResponseWriter, r *http.Request) {
|
|||
oauthToken, err := s.oauth.Exchange(ctx, r.URL.Query().Get("code"),
|
||||
oauth2.VerifierOption(verifierCookie.Value))
|
||||
if err != nil {
|
||||
http.Error(w, "Token-Austausch fehlgeschlagen: "+err.Error(), http.StatusBadGateway)
|
||||
// Log the details server-side; don't leak upstream internals to clients.
|
||||
log.Printf("oauth exchange failed: %v", err)
|
||||
http.Error(w, "Token-Austausch fehlgeschlagen", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +200,8 @@ func (s *server) handleCallback(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
idToken, err := s.verifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
http.Error(w, "id_token-Prüfung fehlgeschlagen: "+err.Error(), http.StatusUnauthorized)
|
||||
log.Printf("id_token verification failed: %v", err)
|
||||
http.Error(w, "id_token-Prüfung fehlgeschlagen", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -293,10 +299,15 @@ func (s *server) handleProxy(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
bearer := strings.TrimSpace(strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer"))
|
||||
if _, err := verifyToken(s.cfg.SessionKey, bearer); err != nil {
|
||||
claims, err := verifyToken(s.cfg.SessionKey, bearer)
|
||||
if err != nil {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if !s.tmdbLim.allow(claims.Subject) {
|
||||
http.Error(w, "rate limit exceeded, try again later", http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
|
||||
// Build upstream URL: /3/<path> -> <TMDBBase>/<path>, keep the query, force api_key.
|
||||
upstreamPath := strings.TrimPrefix(r.URL.Path, "/3")
|
||||
|
|
@ -319,7 +330,8 @@ func (s *server) handleProxy(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
http.Error(w, "upstream error: "+err.Error(), http.StatusBadGateway)
|
||||
log.Printf("tmdb upstream error for %s: %v", r.URL.Path, err)
|
||||
http.Error(w, "upstream error", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue