Fix avatar URL token handling and improve user avatar download timeout management
This commit is contained in:
@@ -131,7 +131,9 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
|||||||
_avatarUrl = response['avatarUrl'] as String?;
|
_avatarUrl = response['avatarUrl'] as String?;
|
||||||
final authState = context.read<AuthBloc>().state;
|
final authState = context.read<AuthBloc>().state;
|
||||||
if (authState is AuthAuthenticated && _avatarUrl != null) {
|
if (authState is AuthAuthenticated && _avatarUrl != null) {
|
||||||
_avatarUrl = "$_avatarUrl&token=${authState.token}";
|
if (!_avatarUrl!.contains('token=')) {
|
||||||
|
_avatarUrl = "$_avatarUrl&token=${authState.token}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (_currentUser != null && _avatarUrl != null) {
|
if (_currentUser != null && _avatarUrl != null) {
|
||||||
final updatedUser = _currentUser!.copyWith(
|
final updatedUser = _currentUser!.copyWith(
|
||||||
@@ -412,8 +414,10 @@ class _AccountSettingsDialogState extends State<AccountSettingsDialog> {
|
|||||||
if (authState is AuthAuthenticated) {
|
if (authState is AuthAuthenticated) {
|
||||||
_currentUser = authState.user;
|
_currentUser = authState.user;
|
||||||
_avatarUrl = _currentUser?.avatarUrl;
|
_avatarUrl = _currentUser?.avatarUrl;
|
||||||
if (_avatarUrl != null) {
|
if (_avatarUrl != null && authState.token.isNotEmpty) {
|
||||||
_avatarUrl = "$_avatarUrl&token=${authState.token}";
|
if (!_avatarUrl!.contains('token=')) {
|
||||||
|
_avatarUrl = "$_avatarUrl&token=${authState.token}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
stderrors "errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
@@ -4185,18 +4186,33 @@ func getUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *database.D
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
remotePath := strings.TrimPrefix(*avatarURL, client.BaseURL+"/")
|
remotePath := strings.TrimPrefix(*avatarURL, client.BaseURL+"/")
|
||||||
resp, err := client.Download(r.Context(), remotePath, "")
|
|
||||||
|
// Use a short timeout to avoid hanging behind proxies; return 404 if WebDAV is slow or times out
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := client.Download(ctx, remotePath, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// If download timed out or gateway returned 504, treat as "no avatar" so frontend shows default icon
|
||||||
|
if strings.Contains(err.Error(), "504") || stderrors.Is(err, context.DeadlineExceeded) {
|
||||||
|
fmt.Printf("[ERROR] Avatar download timeout for remotePath=%s: %v\n", remotePath, err)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
errors.LogError(r, err, "Failed to download avatar")
|
errors.LogError(r, err, "Failed to download avatar")
|
||||||
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Copy headers
|
// Copy headers but ensure sensible caching
|
||||||
for k, v := range resp.Header {
|
for k, v := range resp.Header {
|
||||||
w.Header()[k] = v
|
w.Header()[k] = v
|
||||||
}
|
}
|
||||||
|
// Opt into short caching; client-side cache is also versioned via v=
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=300")
|
||||||
|
|
||||||
w.WriteHeader(resp.StatusCode)
|
w.WriteHeader(resp.StatusCode)
|
||||||
io.Copy(w, resp.Body)
|
io.Copy(w, resp.Body)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user