package nextcloud import ( "context" "fmt" "io" "net/http" "time" ) type Client struct { baseURL string httpClient *http.Client adminUser string adminPass string credStore *DAVCredentialStore } func NewClient(baseURL, adminUser, adminPass string) *Client { return &Client{ baseURL: baseURL, httpClient: &http.Client{ Timeout: 30 * time.Second, }, adminUser: adminUser, adminPass: adminPass, } } func (c *Client) WithDAVCredentials(store *DAVCredentialStore) *Client { if c == nil { return nil } c.credStore = store return c } func (c *Client) doRequest(ctx context.Context, method, path string, body io.Reader, headers map[string]string) (*http.Response, error) { url := c.baseURL + path req, err := http.NewRequestWithContext(ctx, method, url, body) if err != nil { return nil, err } req.SetBasicAuth(c.adminUser, c.adminPass) req.Header.Set("OCS-APIRequest", "true") for k, v := range headers { req.Header.Set(k, v) } return c.httpClient.Do(req) } func (c *Client) DoAsUser(ctx context.Context, method, path string, body io.Reader, userID string, headers map[string]string) (*http.Response, error) { token, err := c.userDAVToken(ctx, userID) if err != nil { return nil, err } url := c.baseURL + path req, err := http.NewRequestWithContext(ctx, method, url, body) if err != nil { return nil, err } req.SetBasicAuth(userID, token) for k, v := range headers { req.Header.Set(k, v) } resp, err := c.httpClient.Do(req) if err != nil { return nil, err } if resp.StatusCode == http.StatusUnauthorized && c.credStore != nil { _ = c.credStore.DeleteToken(ctx, userID) resp.Body.Close() return nil, ErrDAVCredentialsMissing } return resp, nil } func (c *Client) userDAVToken(ctx context.Context, userID string) (string, error) { if c.credStore == nil { return "", fmt.Errorf("nextcloud dav credentials store not configured") } token, err := c.credStore.GetToken(ctx, userID) if err != nil { return "", err } return token, nil } func (c *Client) WebDAVPath(userID, path string) string { return fmt.Sprintf("/remote.php/dav/files/%s/%s", userID, path) }