ultisuite-backend/internal/api/drive/org_folders_service.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

257 lines
7.2 KiB
Go

package drive
import (
"context"
"errors"
"fmt"
"strings"
"github.com/ultisuite/ulti-backend/internal/drivestore"
"github.com/ultisuite/ulti-backend/internal/nextcloud"
)
type OrgFolderView struct {
ID string `json:"id"`
OrgSlug string `json:"org_slug"`
MountPoint string `json:"mount_point"`
NCFolderID int `json:"nc_folder_id"`
QuotaBytes *int64 `json:"quota_bytes,omitempty"`
AutoProvisioned bool `json:"auto_provisioned"`
Permissions int `json:"permissions,omitempty"`
Size int64 `json:"size,omitempty"`
}
func (s *Service) ListOrgFoldersForUser(ctx context.Context, ncUserID string) ([]OrgFolderView, error) {
store := s.ensureStore()
if store == nil {
return nil, fmt.Errorf("store not configured")
}
rows, err := store.ListOrgFolders(ctx)
if err != nil {
return nil, err
}
ncFolders, err := s.nc.ListGroupFolders(ctx, ncUserID)
if err != nil {
return nil, mapDriveError(err)
}
accessible := make(map[int]nextcloud.GroupFolder, len(ncFolders))
for _, f := range ncFolders {
accessible[f.ID] = f
}
out := make([]OrgFolderView, 0, len(rows))
for _, row := range rows {
ncFolder, ok := accessible[row.NCFolderID]
if !ok {
continue
}
perms := 0
for _, p := range ncFolder.Groups {
if p > perms {
perms = p
}
}
out = append(out, OrgFolderView{
ID: row.ID,
OrgSlug: row.OrgSlug,
MountPoint: row.MountPoint,
NCFolderID: row.NCFolderID,
QuotaBytes: row.QuotaBytes,
AutoProvisioned: row.AutoProvisioned,
Permissions: perms,
Size: ncFolder.Size,
})
}
return out, nil
}
func (s *Service) ListOrgFoldersAdmin(ctx context.Context) ([]OrgFolderView, error) {
store := s.ensureStore()
if store == nil {
return nil, fmt.Errorf("store not configured")
}
rows, err := store.ListOrgFolders(ctx)
if err != nil {
return nil, err
}
out := make([]OrgFolderView, 0, len(rows))
for _, row := range rows {
out = append(out, OrgFolderView{
ID: row.ID,
OrgSlug: row.OrgSlug,
MountPoint: row.MountPoint,
NCFolderID: row.NCFolderID,
QuotaBytes: row.QuotaBytes,
AutoProvisioned: row.AutoProvisioned,
})
}
return out, nil
}
type CreateOrgFolderParams struct {
OrgSlug string
MountPoint string
QuotaBytes *int64
AutoProvisioned bool
CreatedBy string
}
func (s *Service) CreateOrgFolder(ctx context.Context, p CreateOrgFolderParams) (OrgFolderView, error) {
store := s.ensureStore()
if store == nil {
return OrgFolderView{}, fmt.Errorf("store not configured")
}
orgSlug := strings.TrimSpace(strings.ToLower(p.OrgSlug))
mountPoint := strings.TrimSpace(p.MountPoint)
if orgSlug == "" || mountPoint == "" {
return OrgFolderView{}, ErrInvalid
}
if _, err := store.GetOrgFolderBySlug(ctx, orgSlug); err == nil {
return OrgFolderView{}, ErrConflict
} else if !errors.Is(err, drivestore.ErrOrgFolderNotFound) {
return OrgFolderView{}, err
}
groupID := nextcloud.OrgGroupID(orgSlug)
if err := s.nc.EnsureGroup(ctx, groupID); err != nil {
return OrgFolderView{}, mapDriveError(err)
}
ncFolderID, err := s.nc.CreateGroupFolder(ctx, mountPoint)
if err != nil {
return OrgFolderView{}, mapDriveError(err)
}
if err := s.nc.AssignGroupToFolder(ctx, ncFolderID, groupID, 31); err != nil {
_ = s.nc.DeleteGroupFolder(ctx, ncFolderID)
return OrgFolderView{}, mapDriveError(err)
}
if p.QuotaBytes != nil {
if err := s.nc.SetGroupFolderQuota(ctx, ncFolderID, *p.QuotaBytes); err != nil {
return OrgFolderView{}, mapDriveError(err)
}
}
row, err := store.CreateOrgFolder(ctx, drivestore.CreateOrgFolderParams{
OrgSlug: orgSlug,
NCFolderID: ncFolderID,
MountPoint: mountPoint,
QuotaBytes: p.QuotaBytes,
AutoProvisioned: p.AutoProvisioned,
CreatedBy: p.CreatedBy,
})
if err != nil {
_ = s.nc.DeleteGroupFolder(ctx, ncFolderID)
return OrgFolderView{}, err
}
return OrgFolderView{
ID: row.ID,
OrgSlug: row.OrgSlug,
MountPoint: row.MountPoint,
NCFolderID: row.NCFolderID,
QuotaBytes: row.QuotaBytes,
}, nil
}
func (s *Service) ProvisionOrgFolder(ctx context.Context, orgSlug, createdBy string) (OrgFolderView, error) {
orgSlug = strings.TrimSpace(strings.ToLower(orgSlug))
if orgSlug == "" {
return OrgFolderView{}, ErrInvalid
}
store := s.ensureStore()
if store == nil {
return OrgFolderView{}, fmt.Errorf("store not configured")
}
if existing, err := store.GetOrgFolderBySlug(ctx, orgSlug); err == nil {
return OrgFolderView{
ID: existing.ID,
OrgSlug: existing.OrgSlug,
MountPoint: existing.MountPoint,
NCFolderID: existing.NCFolderID,
QuotaBytes: existing.QuotaBytes,
AutoProvisioned: existing.AutoProvisioned,
}, nil
} else if !errors.Is(err, drivestore.ErrOrgFolderNotFound) {
return OrgFolderView{}, err
}
mountPoint := orgSlug
if mountPoint == "" {
mountPoint = "org"
}
return s.createOrgFolderInternal(ctx, orgSlug, mountPoint, nil, true, createdBy)
}
func (s *Service) createOrgFolderInternal(ctx context.Context, orgSlug, mountPoint string, quota *int64, auto bool, createdBy string) (OrgFolderView, error) {
view, err := s.CreateOrgFolder(ctx, CreateOrgFolderParams{
OrgSlug: orgSlug,
MountPoint: mountPoint,
QuotaBytes: quota,
AutoProvisioned: auto,
CreatedBy: createdBy,
})
if err != nil {
return OrgFolderView{}, err
}
view.AutoProvisioned = auto
return view, nil
}
func (s *Service) UpdateOrgFolder(ctx context.Context, id, mountPoint string, quotaBytes *int64) (OrgFolderView, error) {
store := s.ensureStore()
if store == nil {
return OrgFolderView{}, fmt.Errorf("store not configured")
}
row, err := store.GetOrgFolder(ctx, id)
if err != nil {
return OrgFolderView{}, err
}
if mountPoint != "" && mountPoint != row.MountPoint {
if err := s.nc.RenameGroupFolder(ctx, row.NCFolderID, mountPoint); err != nil {
return OrgFolderView{}, mapDriveError(err)
}
}
if quotaBytes != nil {
if err := s.nc.SetGroupFolderQuota(ctx, row.NCFolderID, *quotaBytes); err != nil {
return OrgFolderView{}, mapDriveError(err)
}
}
updated, err := store.UpdateOrgFolder(ctx, id, mountPoint, quotaBytes)
if err != nil {
return OrgFolderView{}, err
}
return OrgFolderView{
ID: updated.ID,
OrgSlug: updated.OrgSlug,
MountPoint: updated.MountPoint,
NCFolderID: updated.NCFolderID,
QuotaBytes: updated.QuotaBytes,
}, nil
}
func (s *Service) DeleteOrgFolder(ctx context.Context, id string) error {
store := s.ensureStore()
if store == nil {
return fmt.Errorf("store not configured")
}
row, err := store.GetOrgFolder(ctx, id)
if err != nil {
return err
}
if err := s.nc.DeleteGroupFolder(ctx, row.NCFolderID); err != nil {
return mapDriveError(err)
}
return store.DeleteOrgFolder(ctx, id)
}
func (s *Service) SyncOrgFolders(ctx context.Context, orgSlugs []string, createdBy string) ([]OrgFolderView, error) {
out := make([]OrgFolderView, 0, len(orgSlugs))
for _, slug := range orgSlugs {
slug = strings.TrimSpace(strings.ToLower(slug))
if slug == "" {
continue
}
view, err := s.ProvisionOrgFolder(ctx, slug, createdBy)
if err != nil {
return nil, err
}
out = append(out, view)
}
return out, nil
}