- Introduced new endpoints for contact discovery, including scanning, listing, and managing discovered contacts. - Implemented retry logic for handling missing DAV credentials during contact operations. - Added public share functionality for drive API, allowing users to manage public shares, including upload, delete, and rename operations. - Updated Nextcloud configuration to support public share links and improved error handling for public share permissions. - Enhanced logging and validation across contact and drive APIs for better error tracking and user feedback. - Added tests for new contact matching and ranking functionalities to ensure accuracy and reliability.
133 lines
4.0 KiB
Go
133 lines
4.0 KiB
Go
package nextcloud
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type ocsShareRecord struct {
|
|
ID flexShareID `json:"id"`
|
|
Path string `json:"path"`
|
|
ShareType int `json:"share_type"`
|
|
Permissions int `json:"permissions"`
|
|
URL string `json:"url"`
|
|
Expiration string `json:"expiration"`
|
|
ShareWith *string `json:"share_with"`
|
|
ShareWithDisplayName string `json:"share_with_displayname"`
|
|
Label string `json:"label"`
|
|
Token string `json:"token"`
|
|
UIDOwner string `json:"uid_owner"`
|
|
DisplayNameOwner string `json:"displayname_owner"`
|
|
UIDFileOwner string `json:"uid_file_owner"`
|
|
DisplayNameFileOwner string `json:"displayname_file_owner"`
|
|
Stime int64 `json:"stime"`
|
|
Note string `json:"note"`
|
|
ItemType string `json:"item_type"`
|
|
Password *string `json:"password"`
|
|
}
|
|
|
|
// flexShareID accepts Nextcloud share ids as JSON string or number.
|
|
type flexShareID string
|
|
|
|
func (f *flexShareID) UnmarshalJSON(b []byte) error {
|
|
var s string
|
|
if err := json.Unmarshal(b, &s); err == nil {
|
|
*f = flexShareID(strings.TrimSpace(s))
|
|
return nil
|
|
}
|
|
var n json.Number
|
|
if err := json.Unmarshal(b, &n); err == nil {
|
|
*f = flexShareID(n.String())
|
|
return nil
|
|
}
|
|
return fmt.Errorf("invalid share id")
|
|
}
|
|
|
|
func (f flexShareID) String() string {
|
|
return string(f)
|
|
}
|
|
|
|
func decodeOCSShareRecords(raw json.RawMessage) ([]ocsShareRecord, error) {
|
|
raw = json.RawMessage(strings.TrimSpace(string(raw)))
|
|
if len(raw) == 0 || string(raw) == "null" || string(raw) == "[]" {
|
|
return nil, nil
|
|
}
|
|
if raw[0] == '[' {
|
|
var list []ocsShareRecord
|
|
if err := json.Unmarshal(raw, &list); err != nil {
|
|
return nil, err
|
|
}
|
|
return list, nil
|
|
}
|
|
var one ocsShareRecord
|
|
if err := json.Unmarshal(raw, &one); err != nil {
|
|
return nil, err
|
|
}
|
|
return []ocsShareRecord{one}, nil
|
|
}
|
|
|
|
func decodeOCSShareResponse(body io.Reader, fallbackPath string) (*ShareInfo, error) {
|
|
var ocsResp struct {
|
|
OCS struct {
|
|
Data json.RawMessage `json:"data"`
|
|
} `json:"ocs"`
|
|
}
|
|
if err := json.NewDecoder(body).Decode(&ocsResp); err != nil {
|
|
return nil, err
|
|
}
|
|
items, err := decodeOCSShareRecords(ocsResp.OCS.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(items) == 0 {
|
|
return nil, fmt.Errorf("empty share response")
|
|
}
|
|
info := mapOCSShareRecord(items[0], fallbackPath)
|
|
return &info, nil
|
|
}
|
|
|
|
func mapOCSShareRecord(item ocsShareRecord, fallbackPath string) ShareInfo {
|
|
path := strings.TrimSpace(item.Path)
|
|
if path == "" {
|
|
path = fallbackPath
|
|
}
|
|
shareWith := ""
|
|
if item.ShareWith != nil {
|
|
shareWith = strings.TrimSpace(*item.ShareWith)
|
|
}
|
|
expiration := strings.TrimSpace(item.Expiration)
|
|
info := ShareInfo{
|
|
ID: item.ID.String(),
|
|
Path: path,
|
|
ShareType: item.ShareType,
|
|
Permissions: item.Permissions,
|
|
URL: item.URL,
|
|
ExpiresAt: expiration,
|
|
ShareWith: shareWith,
|
|
ShareWithDisplayName: strings.TrimSpace(item.ShareWithDisplayName),
|
|
Label: strings.TrimSpace(item.Label),
|
|
Token: strings.TrimSpace(item.Token),
|
|
OwnerID: strings.TrimSpace(item.UIDOwner),
|
|
OwnerDisplayName: strings.TrimSpace(item.DisplayNameOwner),
|
|
FileOwnerID: strings.TrimSpace(item.UIDFileOwner),
|
|
FileOwnerDisplayName: strings.TrimSpace(item.DisplayNameFileOwner),
|
|
Note: strings.TrimSpace(item.Note),
|
|
ItemType: strings.TrimSpace(item.ItemType),
|
|
HasPassword: item.Password != nil && strings.TrimSpace(*item.Password) != "",
|
|
}
|
|
if ts := item.Stime; ts > 0 {
|
|
info.CreatedAt = time.Unix(ts, 0).UTC().Format(time.RFC3339)
|
|
}
|
|
if item.ShareType == 3 {
|
|
if strings.EqualFold(info.Label, "internal") {
|
|
info.AccessMode = "internal"
|
|
} else {
|
|
info.AccessMode = "public"
|
|
}
|
|
}
|
|
return info
|
|
}
|