Increase avatar download timeout and retries; add verification for uploaded avatars with fallback caching
This commit is contained in:
@@ -39,8 +39,8 @@ func Load() *Config {
|
|||||||
NextcloudBase: getEnv("NEXTCLOUD_BASEPATH", "/"),
|
NextcloudBase: getEnv("NEXTCLOUD_BASEPATH", "/"),
|
||||||
AllowedOrigins: getEnv("ALLOWED_ORIGINS", "https://b0esche.cloud,https://www.b0esche.cloud,https://*.b0esche.cloud,http://localhost:8080"),
|
AllowedOrigins: getEnv("ALLOWED_ORIGINS", "https://b0esche.cloud,https://www.b0esche.cloud,https://*.b0esche.cloud,http://localhost:8080"),
|
||||||
AvatarCacheDir: getEnv("AVATAR_CACHE_DIR", "/var/cache/b0esche/avatars"),
|
AvatarCacheDir: getEnv("AVATAR_CACHE_DIR", "/var/cache/b0esche/avatars"),
|
||||||
AvatarDownloadTimeoutSeconds: getEnvInt("AVATAR_DOWNLOAD_TIMEOUT_SECONDS", 10),
|
AvatarDownloadTimeoutSeconds: getEnvInt("AVATAR_DOWNLOAD_TIMEOUT_SECONDS", 20),
|
||||||
AvatarDownloadRetries: getEnvInt("AVATAR_DOWNLOAD_RETRIES", 2),
|
AvatarDownloadRetries: getEnvInt("AVATAR_DOWNLOAD_RETRIES", 3),
|
||||||
}
|
}
|
||||||
log.Printf("[CONFIG] Nextcloud URL: %q, User: %q, BasePath: %q, AvatarCacheDir: %q, AvatarDownloadTimeoutSeconds: %d, AvatarDownloadRetries: %d\n", cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudBase, cfg.AvatarCacheDir, cfg.AvatarDownloadTimeoutSeconds, cfg.AvatarDownloadRetries)
|
log.Printf("[CONFIG] Nextcloud URL: %q, User: %q, BasePath: %q, AvatarCacheDir: %q, AvatarDownloadTimeoutSeconds: %d, AvatarDownloadRetries: %d\n", cfg.NextcloudURL, cfg.NextcloudUser, cfg.NextcloudBase, cfg.AvatarCacheDir, cfg.AvatarDownloadTimeoutSeconds, cfg.AvatarDownloadRetries)
|
||||||
return cfg
|
return cfg
|
||||||
|
|||||||
@@ -4216,6 +4216,29 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify uploaded avatar is available on WebDAV (best-effort, retry)
|
||||||
|
verified := false
|
||||||
|
verifyRetries := 3
|
||||||
|
verifyTimeout := 5 // seconds
|
||||||
|
for i := 0; i < verifyRetries; i++ {
|
||||||
|
vctx, vcancel := context.WithTimeout(r.Context(), time.Duration(verifyTimeout)*time.Second)
|
||||||
|
resp, derr := client.Download(vctx, avatarPath, "")
|
||||||
|
vcancel()
|
||||||
|
if derr == nil && resp != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
verified = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fmt.Printf("[WARN] avatar verification attempt %d/%d failed for %s: %v\n", i+1, verifyRetries, avatarPath, derr)
|
||||||
|
time.Sleep(time.Duration(300*(1<<i)) * time.Millisecond) // 300ms, 600ms, 1.2s
|
||||||
|
}
|
||||||
|
// If verification failed, log detailed message (but proceed to respond with preview/cache)
|
||||||
|
if !verified {
|
||||||
|
fmt.Printf("[ERROR] avatar verification failed after %d attempts for %s\n", verifyRetries, avatarPath)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[INFO] avatar verification succeeded for %s\n", avatarPath)
|
||||||
|
}
|
||||||
|
|
||||||
// Build public URL including version based on updated_at and include token if available
|
// Build public URL including version based on updated_at and include token if available
|
||||||
var version int64 = time.Now().Unix()
|
var version int64 = time.Now().Unix()
|
||||||
// Try to use updated_at from DB to be more accurate
|
// Try to use updated_at from DB to be more accurate
|
||||||
@@ -4229,9 +4252,31 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
|
|||||||
cached := false
|
cached := false
|
||||||
if err := writeAvatarCache(cfg, userID.String(), versionStr, ext, fileBytes); err != nil {
|
if err := writeAvatarCache(cfg, userID.String(), versionStr, ext, fileBytes); err != nil {
|
||||||
fmt.Printf("[WARN] failed to write avatar cache for user=%s version=%s: %v\n", userID.String(), versionStr, err)
|
fmt.Printf("[WARN] failed to write avatar cache for user=%s version=%s: %v\n", userID.String(), versionStr, err)
|
||||||
|
// Attempt to write to an additional local data dir as a fallback
|
||||||
|
fallbackDir := "./data/avatars"
|
||||||
|
if err2 := os.MkdirAll(fallbackDir, 0755); err2 == nil {
|
||||||
|
fallbackPath := filepath.Join(fallbackDir, fmt.Sprintf("%s.%s%s", userID.String(), versionStr, ext))
|
||||||
|
if err3 := os.WriteFile(fallbackPath, fileBytes, 0644); err3 == nil {
|
||||||
|
fmt.Printf("[INFO] Wrote avatar cache to fallback path %s\n", fallbackPath)
|
||||||
|
cached = true
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[WARN] failed to write avatar cache to fallback path %s: %v\n", fallbackPath, err3)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[WARN] failed to create fallback avatar dir %s: %v\n", fallbackDir, err2)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
cached = true
|
cached = true
|
||||||
}
|
}
|
||||||
|
// Verify cache is readable
|
||||||
|
if cached {
|
||||||
|
if b, ct, cerr := readAvatarCache(cfg, userID.String(), versionStr); cerr != nil {
|
||||||
|
fmt.Printf("[WARN] cache write verification failed for user=%s v=%s: %v\n", userID.String(), versionStr, cerr)
|
||||||
|
cached = false
|
||||||
|
} else {
|
||||||
|
fmt.Printf("[INFO] cache write verified for user=%s v=%s (content-type=%s size=%d)\n", userID.String(), versionStr, ct, len(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Audit log
|
// Audit log
|
||||||
auditLogger.Log(r.Context(), audit.Entry{
|
auditLogger.Log(r.Context(), audit.Entry{
|
||||||
|
|||||||
Reference in New Issue
Block a user