ultisuite-backend/internal/nextcloud/groupfolders.go
R3D347HR4Y 1d063237b9
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
feat(transcription): integrate Faster Whisper for Jitsi transcriptions
- 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.
2026-06-12 19:10:18 +02:00

253 lines
7.6 KiB
Go

package nextcloud
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
)
const groupFolderAllPermissions = 31
type GroupFolder struct {
ID int `json:"id"`
MountPoint string `json:"mount_point"`
Groups map[string]int `json:"groups"`
Quota int64 `json:"quota"`
Size int64 `json:"size"`
ACL bool `json:"acl"`
Manage []string `json:"manage"`
}
// GroupFolderWebDAVPath builds the WebDAV URL for a group folder root or subpath.
func GroupFolderWebDAVPath(folderID int, logicalPath string) string {
base := fmt.Sprintf("/remote.php/dav/groupfolders/%d", folderID)
logical := strings.Trim(logicalPath, "/")
if logical == "" {
return base
}
parts := strings.Split(logical, "/")
for i, p := range parts {
parts[i] = url.PathEscape(p)
}
return base + "/" + strings.Join(parts, "/")
}
func (c *Client) ListGroupFolders(ctx context.Context, userID string) ([]GroupFolder, error) {
resp, err := c.DoAsUser(ctx, "GET", "/index.php/apps/groupfolders/folders?format=json", nil, userID, ocsJSONHeaders())
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, &HTTPStatusError{Operation: "list groupfolders", StatusCode: resp.StatusCode}
}
return decodeGroupFoldersResponse(resp.Body)
}
func (c *Client) ListGroupFoldersAdmin(ctx context.Context) ([]GroupFolder, error) {
resp, err := c.doRequest(ctx, "GET", "/index.php/apps/groupfolders/folders?format=json", nil, ocsJSONHeaders())
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, &HTTPStatusError{Operation: "list groupfolders admin", StatusCode: resp.StatusCode}
}
return decodeGroupFoldersResponse(resp.Body)
}
func decodeGroupFoldersResponse(body io.Reader) ([]GroupFolder, 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) == "[]" || string(raw) == "null" {
return nil, nil
}
var asMap map[string]GroupFolder
if err := json.Unmarshal(raw, &asMap); err == nil && len(asMap) > 0 {
out := make([]GroupFolder, 0, len(asMap))
for _, item := range asMap {
out = append(out, item)
}
return out, nil
}
var asList []GroupFolder
if err := json.Unmarshal(raw, &asList); err != nil {
return nil, err
}
return asList, nil
}
func (c *Client) CreateGroupFolder(ctx context.Context, mountPoint string) (int, error) {
form := url.Values{}
form.Set("mountpoint", mountPoint)
resp, err := c.doRequest(ctx, "POST", "/index.php/apps/groupfolders/folders?format=json", 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 {
return 0, &HTTPStatusError{Operation: "create groupfolder", StatusCode: resp.StatusCode}
}
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("groupfolder create returned empty id")
}
return payload.OCS.Data.ID, nil
}
func (c *Client) DeleteGroupFolder(ctx context.Context, folderID int) error {
path := fmt.Sprintf("/index.php/apps/groupfolders/folders/%d?format=json", folderID)
resp, err := c.doRequest(ctx, "DELETE", path, nil, ocsJSONHeaders())
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return &HTTPStatusError{Operation: "delete groupfolder", StatusCode: resp.StatusCode}
}
return nil
}
func (c *Client) AssignGroupToFolder(ctx context.Context, folderID int, groupID string, permissions int) error {
if permissions <= 0 {
permissions = groupFolderAllPermissions
}
form := url.Values{}
form.Set("group", groupID)
path := fmt.Sprintf("/index.php/apps/groupfolders/folders/%d/groups?format=json", folderID)
resp, err := c.doRequest(ctx, "POST", path, strings.NewReader(form.Encode()), map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
})
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return &HTTPStatusError{Operation: "assign group to folder", StatusCode: resp.StatusCode}
}
formPerm := url.Values{}
formPerm.Set("permissions", strconv.Itoa(permissions))
pathPerm := fmt.Sprintf("/index.php/apps/groupfolders/folders/%d/groups/%s?format=json", folderID, url.PathEscape(groupID))
resp2, err := c.doRequest(ctx, "POST", pathPerm, strings.NewReader(formPerm.Encode()), map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
})
if err != nil {
return err
}
defer resp2.Body.Close()
if resp2.StatusCode != http.StatusOK {
return &HTTPStatusError{Operation: "set groupfolder permissions", StatusCode: resp2.StatusCode}
}
return nil
}
func (c *Client) SetGroupFolderQuota(ctx context.Context, folderID int, quotaBytes int64) error {
form := url.Values{}
form.Set("quota", strconv.FormatInt(quotaBytes, 10))
path := fmt.Sprintf("/index.php/apps/groupfolders/folders/%d/quota?format=json", folderID)
resp, err := c.doRequest(ctx, "POST", path, strings.NewReader(form.Encode()), map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
})
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return &HTTPStatusError{Operation: "set groupfolder quota", StatusCode: resp.StatusCode}
}
return nil
}
func (c *Client) RenameGroupFolder(ctx context.Context, folderID int, mountPoint string) error {
form := url.Values{}
form.Set("mountpoint", mountPoint)
path := fmt.Sprintf("/index.php/apps/groupfolders/folders/%d/mountpoint?format=json", folderID)
resp, err := c.doRequest(ctx, "POST", path, strings.NewReader(form.Encode()), map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
})
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return &HTTPStatusError{Operation: "rename groupfolder", StatusCode: resp.StatusCode}
}
return nil
}
func (c *Client) EnsureGroup(ctx context.Context, groupID string) error {
groupID = strings.TrimSpace(groupID)
if groupID == "" {
return fmt.Errorf("group id is empty")
}
exists, err := c.groupExists(ctx, groupID)
if err != nil {
return err
}
if exists {
return nil
}
form := url.Values{}
form.Set("groupid", groupID)
resp, err := c.doRequest(ctx, "POST", "/ocs/v1.php/cloud/groups", strings.NewReader(form.Encode()), map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
})
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return &HTTPStatusError{Operation: "create group", StatusCode: resp.StatusCode}
}
return nil
}
func (c *Client) groupExists(ctx context.Context, groupID string) (bool, error) {
path := "/ocs/v1.php/cloud/groups/" + url.PathEscape(groupID)
resp, err := c.doRequest(ctx, "GET", path, nil, ocsJSONHeaders())
if err != nil {
return false, err
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusNotFound:
return false, nil
default:
return false, &HTTPStatusError{Operation: "group exists", StatusCode: resp.StatusCode}
}
}
func OrgGroupID(orgSlug string) string {
slug := strings.TrimSpace(strings.ToLower(orgSlug))
slug = strings.ReplaceAll(slug, " ", "-")
return "org-" + slug
}