personal workspace backend flush
This commit is contained in:
113
go_cloud/internal/storage/webdav.go
Normal file
113
go_cloud/internal/storage/webdav.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"go.b0esche.cloud/backend/internal/config"
|
||||
)
|
||||
|
||||
type WebDAVClient struct {
|
||||
baseURL string
|
||||
user string
|
||||
pass string
|
||||
basePrefix string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewWebDAVClient returns nil if no Nextcloud URL configured
|
||||
func NewWebDAVClient(cfg *config.Config) *WebDAVClient {
|
||||
if cfg == nil || strings.TrimSpace(cfg.NextcloudURL) == "" {
|
||||
return nil
|
||||
}
|
||||
u := strings.TrimRight(cfg.NextcloudURL, "/")
|
||||
base := cfg.NextcloudBase
|
||||
if base == "" {
|
||||
base = "/"
|
||||
}
|
||||
return &WebDAVClient{
|
||||
baseURL: u,
|
||||
user: cfg.NextcloudUser,
|
||||
pass: cfg.NextcloudPass,
|
||||
basePrefix: strings.TrimRight(base, "/"),
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
// ensureParent creates intermediate collections using MKCOL. Ignoring errors when already exists.
|
||||
func (c *WebDAVClient) ensureParent(ctx context.Context, remotePath string) error {
|
||||
// build incremental paths
|
||||
dir := path.Dir(remotePath)
|
||||
if dir == "." || dir == "/" || dir == "" {
|
||||
return nil
|
||||
}
|
||||
// split and build prefixes
|
||||
parts := strings.Split(strings.Trim(dir, "/"), "/")
|
||||
cur := c.basePrefix
|
||||
for _, p := range parts {
|
||||
cur = path.Join(cur, p)
|
||||
mkurl := fmt.Sprintf("%s%s", c.baseURL, cur)
|
||||
req, _ := http.NewRequestWithContext(ctx, "MKCOL", mkurl, nil)
|
||||
if c.user != "" {
|
||||
req.SetBasicAuth(c.user, c.pass)
|
||||
}
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
// 201 created, 405 exists — ignore
|
||||
if resp.StatusCode == 201 || resp.StatusCode == 405 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upload streams the content to the remotePath using HTTP PUT (WebDAV). remotePath should be absolute under basePrefix.
|
||||
func (c *WebDAVClient) Upload(ctx context.Context, remotePath string, r io.Reader, size int64) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("no webdav client configured")
|
||||
}
|
||||
// Ensure parent collections
|
||||
if err := c.ensureParent(ctx, remotePath); err != nil {
|
||||
return err
|
||||
}
|
||||
// Construct URL
|
||||
// remotePath might be like /orgs/<id>/file.txt; ensure it joins to basePrefix
|
||||
rel := strings.TrimLeft(remotePath, "/")
|
||||
u := c.basePrefix
|
||||
if u == "/" || u == "" {
|
||||
u = "/"
|
||||
}
|
||||
full := fmt.Sprintf("%s%s/%s", c.baseURL, u, url.PathEscape(rel))
|
||||
full = strings.ReplaceAll(full, "%2F", "/")
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "PUT", full, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if size > 0 {
|
||||
req.ContentLength = size
|
||||
}
|
||||
if c.user != "" {
|
||||
req.SetBasicAuth(c.user, c.pass)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/octet-stream")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return nil
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("webdav upload failed: %d %s", resp.StatusCode, string(body))
|
||||
}
|
||||
Reference in New Issue
Block a user