Refactor WebDAV client to expose BaseURL and update avatar upload logic for improved URL handling

This commit is contained in:
Leon Bösche
2026-01-29 10:30:24 +01:00
parent 11daed18d7
commit 688cec90a8
4 changed files with 29 additions and 28 deletions

Binary file not shown.

View File

@@ -4038,7 +4038,7 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
// Upload to Nextcloud // Upload to Nextcloud
client := storage.NewWebDAVClient(cfg) client := storage.NewWebDAVClient(cfg)
avatarPath := fmt.Sprintf("remote.php/dav/files/b0esche/avatars/%s", filename) avatarPath := fmt.Sprintf("avatars/%s", filename)
err = client.Upload(r.Context(), avatarPath, bytes.NewReader(fileBytes), header.Size) err = client.Upload(r.Context(), avatarPath, bytes.NewReader(fileBytes), header.Size)
if err != nil { if err != nil {
errors.LogError(r, err, "Failed to upload avatar") errors.LogError(r, err, "Failed to upload avatar")
@@ -4049,11 +4049,12 @@ func uploadUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *databas
// Get public URL - for now, construct it manually since Nextcloud doesn't provide direct public URLs // Get public URL - for now, construct it manually since Nextcloud doesn't provide direct public URLs
// In a real setup, you'd configure Nextcloud to serve public URLs or use a CDN // In a real setup, you'd configure Nextcloud to serve public URLs or use a CDN
publicURL := "/user/avatar" publicURL := "/user/avatar"
webdavURL := fmt.Sprintf("%s/%s", client.BaseURL, avatarPath)
// Update user profile with avatar URL // Update user profile with avatar URL
_, err = db.ExecContext(r.Context(), _, err = db.ExecContext(r.Context(),
`UPDATE users SET avatar_url = $1, updated_at = NOW() WHERE id = $2`, `UPDATE users SET avatar_url = $1, updated_at = NOW() WHERE id = $2`,
publicURL, userID) webdavURL, userID)
if err != nil { if err != nil {
errors.LogError(r, err, "Failed to update user avatar") errors.LogError(r, err, "Failed to update user avatar")
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
@@ -4112,16 +4113,13 @@ func getUserAvatarHandler(w http.ResponseWriter, r *http.Request, db *database.D
return return
} }
// Parse the avatar URL to get the remote path
// Assuming avatarURL is like https://storage.b0esche.cloud/remote.php/dav/files/b0esche/avatars/filename
baseURL := cfg.NextcloudURL
if !strings.HasSuffix(baseURL, "/") {
baseURL += "/"
}
remotePath := strings.TrimPrefix(*avatarURL, baseURL)
// Download from WebDAV // Download from WebDAV
client := storage.NewWebDAVClient(cfg) client := storage.NewWebDAVClient(cfg)
if client == nil {
errors.WriteError(w, errors.CodeInternal, "WebDAV client not configured", http.StatusInternalServerError)
return
}
remotePath := strings.TrimPrefix(*avatarURL, client.BaseURL+"/")
resp, err := client.Download(r.Context(), remotePath, "") resp, err := client.Download(r.Context(), remotePath, "")
if err != nil { if err != nil {
errors.LogError(r, err, "Failed to download avatar") errors.LogError(r, err, "Failed to download avatar")

View File

@@ -69,7 +69,7 @@ func NewUserWebDAVClient(nextcloudBaseURL, username, password string) *WebDAVCli
fullURL := fmt.Sprintf("%s/remote.php/dav/files/%s", baseURL, username) fullURL := fmt.Sprintf("%s/remote.php/dav/files/%s", baseURL, username)
return &WebDAVClient{ return &WebDAVClient{
baseURL: fullURL, BaseURL: fullURL,
user: username, user: username,
pass: password, pass: password,
basePrefix: "/", basePrefix: "/",

View File

@@ -15,7 +15,7 @@ import (
) )
type WebDAVClient struct { type WebDAVClient struct {
baseURL string BaseURL string
user string user string
pass string pass string
basePrefix string basePrefix string
@@ -29,17 +29,20 @@ func NewWebDAVClient(cfg *config.Config) *WebDAVClient {
return nil return nil
} }
u := strings.TrimRight(cfg.NextcloudURL, "/") u := strings.TrimRight(cfg.NextcloudURL, "/")
if !strings.Contains(u, "/remote.php") {
u += "/remote.php/dav/files/" + cfg.NextcloudUser
}
base := cfg.NextcloudBase base := cfg.NextcloudBase
if base == "" { if base == "" {
base = "/" base = "/"
} }
log.Printf("[WEBDAV] Initializing WebDAV client - URL: %s, User: %s, BasePath: %s\n", u, cfg.NextcloudUser, base) log.Printf("[WEBDAV] Initializing WebDAV client - URL: %s, User: %s, BasePath: %s\n", u, cfg.NextcloudUser, base)
return &WebDAVClient{ return &WebDAVClient{
baseURL: u, BaseURL: u,
user: cfg.NextcloudUser, user: cfg.NextcloudUser,
pass: cfg.NextcloudPass, pass: cfg.NextcloudPass,
basePrefix: strings.TrimRight(base, "/"), basePrefix: strings.TrimRight(base, "/"),
httpClient: &http.Client{Timeout: 10 * time.Minute}, httpClient: &http.Client{Timeout: 30 * time.Second},
} }
} }
@@ -57,14 +60,14 @@ func (c *WebDAVClient) ensureParent(ctx context.Context, remotePath string) erro
cur = path.Join(cur, p) cur = path.Join(cur, p)
var mkurl string var mkurl string
if cur == "" || cur == "/" { if cur == "" || cur == "/" {
mkurl = fmt.Sprintf("%s/%s", c.baseURL, url.PathEscape(p)) mkurl = fmt.Sprintf("%s/%s", c.BaseURL, url.PathEscape(p))
} else { } else {
// Ensure there's a "/" between baseURL and cur // Ensure there's a "/" between baseURL and cur
sep := "" sep := ""
if !strings.HasSuffix(c.baseURL, "/") && !strings.HasPrefix(cur, "/") { if !strings.HasSuffix(c.BaseURL, "/") && !strings.HasPrefix(cur, "/") {
sep = "/" sep = "/"
} }
mkurl = fmt.Sprintf("%s%s%s", c.baseURL, sep, strings.TrimPrefix(cur, "/")) mkurl = fmt.Sprintf("%s%s%s", c.BaseURL, sep, strings.TrimPrefix(cur, "/"))
} }
req, _ := http.NewRequestWithContext(ctx, "MKCOL", mkurl, nil) req, _ := http.NewRequestWithContext(ctx, "MKCOL", mkurl, nil)
if c.user != "" { if c.user != "" {
@@ -103,13 +106,13 @@ func (c *WebDAVClient) Upload(ctx context.Context, remotePath string, r io.Reade
var full string var full string
if u == "" { if u == "" {
full = fmt.Sprintf("%s/%s", c.baseURL, url.PathEscape(rel)) full = fmt.Sprintf("%s/%s", c.BaseURL, url.PathEscape(rel))
} else { } else {
full = fmt.Sprintf("%s%s/%s", c.baseURL, u, url.PathEscape(rel)) full = fmt.Sprintf("%s%s/%s", c.BaseURL, u, url.PathEscape(rel))
} }
full = strings.ReplaceAll(full, "%2F", "/") full = strings.ReplaceAll(full, "%2F", "/")
fmt.Printf("[WEBDAV-UPLOAD] BaseURL: %s, BasePrefix: %s, RemotePath: %s, Full URL: %s\n", c.baseURL, c.basePrefix, remotePath, full) fmt.Printf("[WEBDAV-UPLOAD] BaseURL: %s, BasePrefix: %s, RemotePath: %s, Full URL: %s\n", c.BaseURL, c.basePrefix, remotePath, full)
req, err := http.NewRequestWithContext(ctx, "PUT", full, r) req, err := http.NewRequestWithContext(ctx, "PUT", full, r)
if err != nil { if err != nil {
@@ -150,9 +153,9 @@ func (c *WebDAVClient) Download(ctx context.Context, remotePath string, rangeHea
var full string var full string
if u == "" { if u == "" {
full = fmt.Sprintf("%s/%s", c.baseURL, url.PathEscape(rel)) full = fmt.Sprintf("%s/%s", c.BaseURL, url.PathEscape(rel))
} else { } else {
full = fmt.Sprintf("%s%s/%s", c.baseURL, u, url.PathEscape(rel)) full = fmt.Sprintf("%s%s/%s", c.BaseURL, u, url.PathEscape(rel))
} }
full = strings.ReplaceAll(full, "%2F", "/") full = strings.ReplaceAll(full, "%2F", "/")
@@ -196,9 +199,9 @@ func (c *WebDAVClient) Delete(ctx context.Context, remotePath string) error {
var full string var full string
if u == "" { if u == "" {
full = fmt.Sprintf("%s/%s", c.baseURL, url.PathEscape(rel)) full = fmt.Sprintf("%s/%s", c.BaseURL, url.PathEscape(rel))
} else { } else {
full = fmt.Sprintf("%s%s/%s", c.baseURL, u, url.PathEscape(rel)) full = fmt.Sprintf("%s%s/%s", c.BaseURL, u, url.PathEscape(rel))
} }
full = strings.ReplaceAll(full, "%2F", "/") full = strings.ReplaceAll(full, "%2F", "/")
@@ -247,18 +250,18 @@ func (c *WebDAVClient) Move(ctx context.Context, sourcePath, targetPath string)
// Build source URL // Build source URL
var sourceURL string var sourceURL string
if u == "" { if u == "" {
sourceURL = fmt.Sprintf("%s/%s", c.baseURL, url.PathEscape(sourceRel)) sourceURL = fmt.Sprintf("%s/%s", c.BaseURL, url.PathEscape(sourceRel))
} else { } else {
sourceURL = fmt.Sprintf("%s%s/%s", c.baseURL, u, url.PathEscape(sourceRel)) sourceURL = fmt.Sprintf("%s%s/%s", c.BaseURL, u, url.PathEscape(sourceRel))
} }
sourceURL = strings.ReplaceAll(sourceURL, "%2F", "/") sourceURL = strings.ReplaceAll(sourceURL, "%2F", "/")
// Build target URL // Build target URL
var targetURL string var targetURL string
if u == "" { if u == "" {
targetURL = fmt.Sprintf("%s/%s", c.baseURL, url.PathEscape(targetRel)) targetURL = fmt.Sprintf("%s/%s", c.BaseURL, url.PathEscape(targetRel))
} else { } else {
targetURL = fmt.Sprintf("%s%s/%s", c.baseURL, u, url.PathEscape(targetRel)) targetURL = fmt.Sprintf("%s%s/%s", c.BaseURL, u, url.PathEscape(targetRel))
} }
targetURL = strings.ReplaceAll(targetURL, "%2F", "/") targetURL = strings.ReplaceAll(targetURL, "%2F", "/")