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 }