- Added support for Faster Whisper transcription via Jigasi and Skynet. - Updated .env.example to include new environment variables for transcription settings. - Enhanced Jitsi Docker Compose configuration to include Skynet and Jigasi services. - Introduced new API endpoints for managing organizational folders in the drive service. - Updated Nextcloud initialization script to enable external file mounting. - Improved error handling and response structures in the drive API. - Added new properties for organization settings related to transcription and agenda management.
279 lines
8.4 KiB
Go
279 lines
8.4 KiB
Go
package nextcloud
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type ExternalMount struct {
|
|
ID int `json:"id"`
|
|
MountPoint string `json:"mount_point"`
|
|
Backend string `json:"backend"`
|
|
Status int `json:"status"`
|
|
}
|
|
|
|
type WebDAVMountConfig struct {
|
|
Host string `json:"host"`
|
|
Root string `json:"root"`
|
|
User string `json:"user"`
|
|
Password string `json:"password"`
|
|
Secure bool `json:"secure"`
|
|
}
|
|
|
|
// CreateUserWebDAVMount registers a WebDAV external storage mount for a user.
|
|
func (c *Client) CreateUserWebDAVMount(ctx context.Context, userID, mountPoint string, cfg WebDAVMountConfig) (int, error) {
|
|
return c.createExternalMount(ctx, mountPoint, "dav", "password::password", userID, map[string]string{
|
|
"host": cfg.Host,
|
|
"root": cfg.Root,
|
|
"user": cfg.User,
|
|
"password": cfg.Password,
|
|
"secure": boolString(cfg.Secure),
|
|
})
|
|
}
|
|
|
|
// CreateGlobalWebDAVMount registers an org-wide WebDAV mount (all users).
|
|
func (c *Client) CreateGlobalWebDAVMount(ctx context.Context, mountPoint string, cfg WebDAVMountConfig) (int, error) {
|
|
return c.createExternalMount(ctx, mountPoint, "dav", "password::password", "", map[string]string{
|
|
"host": cfg.Host,
|
|
"root": cfg.Root,
|
|
"user": cfg.User,
|
|
"password": cfg.Password,
|
|
"secure": boolString(cfg.Secure),
|
|
})
|
|
}
|
|
|
|
func boolString(v bool) string {
|
|
if v {
|
|
return "true"
|
|
}
|
|
return "false"
|
|
}
|
|
|
|
func (c *Client) createExternalMount(ctx context.Context, mountPoint, backend, authBackend, userID string, config map[string]string) (int, error) {
|
|
form := url.Values{}
|
|
form.Set("mountPoint", mountPoint)
|
|
form.Set("backend", backend)
|
|
form.Set("authBackend", authBackend)
|
|
if userID != "" {
|
|
form.Set("user", userID)
|
|
}
|
|
for k, v := range config {
|
|
form.Set("config["+k+"]", v)
|
|
}
|
|
apiPath := "/index.php/apps/files_external/api/v1/mounts?format=json"
|
|
resp, err := c.doRequest(ctx, "POST", apiPath, strings.NewReader(form.Encode()), map[string]string{
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
|
return 0, fmt.Errorf("create external mount: %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
|
}
|
|
var payload struct {
|
|
OCS struct {
|
|
Data struct {
|
|
ID int `json:"id"`
|
|
} `json:"data"`
|
|
} `json:"ocs"`
|
|
}
|
|
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
|
return 0, err
|
|
}
|
|
if payload.OCS.Data.ID <= 0 {
|
|
return 0, fmt.Errorf("external mount create returned empty id")
|
|
}
|
|
return payload.OCS.Data.ID, nil
|
|
}
|
|
|
|
func (c *Client) DeleteExternalMount(ctx context.Context, mountID int) error {
|
|
apiPath := fmt.Sprintf("/index.php/apps/files_external/api/v1/mounts/%d?format=json", mountID)
|
|
resp, err := c.doRequest(ctx, "DELETE", apiPath, nil, ocsJSONHeaders())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return &HTTPStatusError{Operation: "delete external mount", StatusCode: resp.StatusCode}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) ListUserExternalMounts(ctx context.Context, userID string) ([]ExternalMount, error) {
|
|
apiPath := "/index.php/apps/files_external/api/v1/mounts?format=json"
|
|
resp, err := c.DoAsUser(ctx, "GET", apiPath, nil, userID, ocsJSONHeaders())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, &HTTPStatusError{Operation: "list external mounts", StatusCode: resp.StatusCode}
|
|
}
|
|
return decodeExternalMounts(resp.Body)
|
|
}
|
|
|
|
func (c *Client) ListGlobalExternalMounts(ctx context.Context) ([]ExternalMount, error) {
|
|
apiPath := "/index.php/apps/files_external/globalstorages?format=json"
|
|
resp, err := c.doRequest(ctx, "GET", apiPath, nil, ocsJSONHeaders())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, &HTTPStatusError{Operation: "list global external mounts", StatusCode: resp.StatusCode}
|
|
}
|
|
return decodeExternalMounts(resp.Body)
|
|
}
|
|
|
|
func decodeExternalMounts(body io.Reader) ([]ExternalMount, error) {
|
|
var payload struct {
|
|
OCS struct {
|
|
Data json.RawMessage `json:"data"`
|
|
} `json:"ocs"`
|
|
}
|
|
if err := json.NewDecoder(body).Decode(&payload); err != nil {
|
|
return nil, err
|
|
}
|
|
raw := payload.OCS.Data
|
|
if len(raw) == 0 || string(raw) == "null" {
|
|
return nil, nil
|
|
}
|
|
var mounts []ExternalMount
|
|
if err := json.Unmarshal(raw, &mounts); err == nil {
|
|
return mounts, nil
|
|
}
|
|
var asMap map[string]ExternalMount
|
|
if err := json.Unmarshal(raw, &asMap); err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]ExternalMount, 0, len(asMap))
|
|
for _, m := range asMap {
|
|
out = append(out, m)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// CreateOAuthExternalMount creates a mount using OAuth2 backend (Google, Dropbox, etc.).
|
|
func (c *Client) CreateOAuthExternalMount(ctx context.Context, userID, mountPoint, backend, authBackend string, oauthConfig map[string]string) (int, error) {
|
|
config := map[string]string{
|
|
"configured": "false",
|
|
"token": "",
|
|
}
|
|
for k, v := range oauthConfig {
|
|
config[k] = v
|
|
}
|
|
return c.createExternalMount(ctx, mountPoint, backend, authBackend, userID, config)
|
|
}
|
|
|
|
type OAuth2StepResult struct {
|
|
URL string
|
|
Token string
|
|
}
|
|
|
|
func (c *Client) StartExternalStorageOAuth2(ctx context.Context, userID, clientID, clientSecret, redirectURI string) (string, error) {
|
|
result, err := c.postExternalStorageOAuth2(ctx, userID, map[string]string{
|
|
"step": "1",
|
|
"client_id": clientID,
|
|
"client_secret": clientSecret,
|
|
"redirect": redirectURI,
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if result.URL == "" {
|
|
return "", fmt.Errorf("oauth2 step 1: empty authorization url")
|
|
}
|
|
return result.URL, nil
|
|
}
|
|
|
|
func (c *Client) CompleteExternalStorageOAuth2(ctx context.Context, userID, clientID, clientSecret, redirectURI, code string) (string, error) {
|
|
result, err := c.postExternalStorageOAuth2(ctx, userID, map[string]string{
|
|
"step": "2",
|
|
"client_id": clientID,
|
|
"client_secret": clientSecret,
|
|
"redirect": redirectURI,
|
|
"code": code,
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if result.Token == "" {
|
|
return "", fmt.Errorf("oauth2 step 2: empty token")
|
|
}
|
|
return result.Token, nil
|
|
}
|
|
|
|
func (c *Client) UpdateUserExternalMountOAuth(ctx context.Context, userID string, mountID int, clientID, clientSecret, token string) error {
|
|
form := url.Values{}
|
|
form.Set("client_id", clientID)
|
|
form.Set("client_secret", clientSecret)
|
|
form.Set("token", token)
|
|
form.Set("configured", "true")
|
|
apiPath := fmt.Sprintf("/index.php/apps/files_external/userstorages/%d?format=json", mountID)
|
|
resp, err := c.DoAsUser(ctx, "PUT", apiPath, strings.NewReader(form.Encode()), userID, map[string]string{
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
|
return fmt.Errorf("update external mount oauth: %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) postExternalStorageOAuth2(ctx context.Context, userID string, fields map[string]string) (OAuth2StepResult, error) {
|
|
form := url.Values{}
|
|
for k, v := range fields {
|
|
form.Set(k, v)
|
|
}
|
|
apiPath := "/index.php/apps/files_external/ajax/oauth2.php"
|
|
resp, err := c.DoAsUser(ctx, "POST", apiPath, strings.NewReader(form.Encode()), userID, map[string]string{
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
})
|
|
if err != nil {
|
|
return OAuth2StepResult{}, err
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 8192))
|
|
if err != nil {
|
|
return OAuth2StepResult{}, err
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return OAuth2StepResult{}, fmt.Errorf("oauth2 request: %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
|
}
|
|
var payload struct {
|
|
Status string `json:"status"`
|
|
Data struct {
|
|
URL string `json:"url"`
|
|
Token string `json:"token"`
|
|
Message string `json:"message"`
|
|
} `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(body, &payload); err != nil {
|
|
return OAuth2StepResult{}, fmt.Errorf("oauth2 response decode: %w", err)
|
|
}
|
|
if payload.Status != "success" {
|
|
msg := strings.TrimSpace(payload.Data.Message)
|
|
if msg == "" {
|
|
msg = strings.TrimSpace(string(body))
|
|
}
|
|
return OAuth2StepResult{}, fmt.Errorf("oauth2 failed: %s", msg)
|
|
}
|
|
return OAuth2StepResult{URL: payload.Data.URL, Token: payload.Data.Token}, nil
|
|
}
|
|
|
|
func ParseMountID(raw string) (int, error) {
|
|
return strconv.Atoi(strings.TrimSpace(raw))
|
|
}
|