Docker/Voltaserve/api/service/workspace_service.go
2024-04-17 20:22:30 +02:00

503 lines
14 KiB
Go

package service
import (
"sort"
"strings"
"time"
"voltaserve/cache"
"voltaserve/config"
"voltaserve/errorpkg"
"voltaserve/guard"
"voltaserve/helper"
"voltaserve/infra"
"voltaserve/model"
"voltaserve/repo"
"voltaserve/search"
"github.com/google/uuid"
)
type Workspace struct {
ID string `json:"id"`
Image *string `json:"image,omitempty"`
Name string `json:"name"`
RootID string `json:"rootId,omitempty"`
StorageCapacity int64 `json:"storageCapacity"`
Permission string `json:"permission"`
Organization Organization `json:"organization"`
CreateTime string `json:"createTime"`
UpdateTime *string `json:"updateTime,omitempty"`
}
type WorkspaceList struct {
Data []*Workspace `json:"data"`
TotalPages uint `json:"totalPages"`
TotalElements uint `json:"totalElements"`
Page uint `json:"page"`
Size uint `json:"size"`
}
type WorkspaceCreateOptions struct {
Name string `json:"name" validate:"required,max=255"`
Image *string `json:"image"`
OrganizationID string `json:"organizationId" validate:"required"`
StorageCapacity int64 `json:"storageCapacity"`
}
type WorkspaceListOptions struct {
Query string
Page uint
Size uint
SortBy string
SortOrder string
}
type WorkspaceUpdateNameOptions struct {
Name string `json:"name" validate:"required,max=255"`
}
type WorkspaceUpdateStorageCapacityOptions struct {
StorageCapacity int64 `json:"storageCapacity" validate:"required,min=1"`
}
type WorkspaceUpdateIsAutomaticOCREnabledOptions struct {
IsEnabled bool `json:"isEnabled" validate:"required"`
}
type WorkspaceService struct {
workspaceRepo repo.WorkspaceRepo
workspaceCache *cache.WorkspaceCache
workspaceGuard *guard.WorkspaceGuard
workspaceSearch *search.WorkspaceSearch
workspaceMapper *workspaceMapper
fileRepo repo.FileRepo
fileCache *cache.FileCache
fileGuard *guard.FileGuard
fileMapper *FileMapper
userRepo repo.UserRepo
s3 *infra.S3Manager
config config.Config
}
func NewWorkspaceService() *WorkspaceService {
return &WorkspaceService{
workspaceRepo: repo.NewWorkspaceRepo(),
workspaceCache: cache.NewWorkspaceCache(),
workspaceSearch: search.NewWorkspaceSearch(),
workspaceGuard: guard.NewWorkspaceGuard(),
workspaceMapper: newWorkspaceMapper(),
fileRepo: repo.NewFileRepo(),
fileCache: cache.NewFileCache(),
fileGuard: guard.NewFileGuard(),
fileMapper: NewFileMapper(),
userRepo: repo.NewUserRepo(),
s3: infra.NewS3Manager(),
config: config.GetConfig(),
}
}
func (svc *WorkspaceService) Create(opts WorkspaceCreateOptions, userID string) (*Workspace, error) {
id := helper.NewID()
bucket := strings.ReplaceAll(uuid.NewString(), "-", "")
if err := svc.s3.CreateBucket(bucket); err != nil {
return nil, err
}
if opts.StorageCapacity == 0 {
opts.StorageCapacity = svc.config.Defaults.WorkspaceStorageCapacityBytes
}
workspace, err := svc.workspaceRepo.Insert(repo.WorkspaceInsertOptions{
ID: id,
Name: opts.Name,
StorageCapacity: opts.StorageCapacity,
OrganizationID: opts.OrganizationID,
Image: opts.Image,
Bucket: bucket,
})
if err != nil {
return nil, err
}
if err := svc.workspaceRepo.GrantUserPermission(workspace.GetID(), userID, model.PermissionOwner); err != nil {
return nil, err
}
workspace, err = svc.workspaceRepo.Find(workspace.GetID())
if err != nil {
return nil, err
}
root, err := svc.fileRepo.Insert(repo.FileInsertOptions{
Name: "root",
WorkspaceID: workspace.GetID(),
Type: model.FileTypeFolder,
})
if err != nil {
return nil, err
}
if err := svc.fileRepo.GrantUserPermission(root.GetID(), userID, model.PermissionOwner); err != nil {
return nil, err
}
if err = svc.workspaceRepo.UpdateRootID(workspace.GetID(), root.GetID()); err != nil {
return nil, err
}
if workspace, err = svc.workspaceRepo.Find(workspace.GetID()); err != nil {
return nil, err
}
if err = svc.workspaceSearch.Index([]model.Workspace{workspace}); err != nil {
return nil, err
}
if root, err = svc.fileRepo.Find(root.GetID()); err != nil {
return nil, err
}
if err := svc.fileCache.Set(root); err != nil {
return nil, err
}
if err = svc.workspaceCache.Set(workspace); err != nil {
return nil, err
}
res, err := svc.workspaceMapper.mapOne(workspace, userID)
if err != nil {
return nil, err
}
return res, nil
}
func (svc *WorkspaceService) Find(id string, userID string) (*Workspace, error) {
user, err := svc.userRepo.Find(userID)
if err != nil {
return nil, err
}
workspace, err := svc.workspaceCache.Get(id)
if err != nil {
return nil, err
}
if err = svc.workspaceGuard.Authorize(user, workspace, model.PermissionViewer); err != nil {
return nil, err
}
res, err := svc.workspaceMapper.mapOne(workspace, userID)
if err != nil {
return nil, err
}
return res, nil
}
func (svc *WorkspaceService) List(opts WorkspaceListOptions, userID string) (*WorkspaceList, error) {
user, err := svc.userRepo.Find(userID)
if err != nil {
return nil, err
}
var authorized []model.Workspace
if opts.Query == "" {
ids, err := svc.workspaceRepo.GetIDs()
if err != nil {
return nil, err
}
authorized, err = svc.doAuthorizationByIDs(ids, user)
if err != nil {
return nil, err
}
} else {
workspaces, err := svc.workspaceSearch.Query(opts.Query)
if err != nil {
return nil, err
}
authorized, err = svc.doAuthorization(workspaces, user)
if err != nil {
return nil, err
}
}
if opts.SortBy == "" {
opts.SortBy = SortByDateCreated
}
if opts.SortOrder == "" {
opts.SortOrder = SortOrderAsc
}
sorted := svc.doSorting(authorized, opts.SortBy, opts.SortOrder)
paged, totalElements, totalPages := svc.doPagination(sorted, opts.Page, opts.Size)
mapped, err := svc.workspaceMapper.mapMany(paged, userID)
if err != nil {
return nil, err
}
return &WorkspaceList{
Data: mapped,
TotalPages: totalPages,
TotalElements: totalElements,
Page: opts.Page,
Size: uint(len(mapped)),
}, nil
}
func (svc *WorkspaceService) UpdateName(id string, name string, userID string) (*Workspace, error) {
user, err := svc.userRepo.Find(userID)
if err != nil {
return nil, err
}
workspace, err := svc.workspaceCache.Get(id)
if err != nil {
return nil, err
}
if err = svc.workspaceGuard.Authorize(user, workspace, model.PermissionEditor); err != nil {
return nil, err
}
if workspace, err = svc.workspaceRepo.UpdateName(id, name); err != nil {
return nil, err
}
if err = svc.workspaceSearch.Update([]model.Workspace{workspace}); err != nil {
return nil, err
}
if err = svc.workspaceCache.Set(workspace); err != nil {
return nil, err
}
res, err := svc.workspaceMapper.mapOne(workspace, userID)
if err != nil {
return nil, err
}
return res, nil
}
func (svc *WorkspaceService) UpdateStorageCapacity(id string, storageCapacity int64, userID string) (*Workspace, error) {
user, err := svc.userRepo.Find(userID)
if err != nil {
return nil, err
}
workspace, err := svc.workspaceCache.Get(id)
if err != nil {
return nil, err
}
if err = svc.workspaceGuard.Authorize(user, workspace, model.PermissionEditor); err != nil {
return nil, err
}
size, err := svc.fileRepo.GetSize(workspace.GetRootID())
if err != nil {
return nil, err
}
if storageCapacity < size {
return nil, errorpkg.NewInsufficientStorageCapacityError()
}
if workspace, err = svc.workspaceRepo.UpdateStorageCapacity(id, storageCapacity); err != nil {
return nil, err
}
if err = svc.workspaceSearch.Update([]model.Workspace{workspace}); err != nil {
return nil, err
}
if err = svc.workspaceCache.Set(workspace); err != nil {
return nil, err
}
res, err := svc.workspaceMapper.mapOne(workspace, userID)
if err != nil {
return nil, err
}
return res, nil
}
func (svc *WorkspaceService) Delete(id string, userID string) error {
user, err := svc.userRepo.Find(userID)
if err != nil {
return err
}
workspace, err := svc.workspaceCache.Get(id)
if err != nil {
return err
}
if err = svc.workspaceGuard.Authorize(user, workspace, model.PermissionOwner); err != nil {
return err
}
if workspace, err = svc.workspaceRepo.Find(id); err != nil {
return err
}
if err = svc.workspaceRepo.Delete(id); err != nil {
return err
}
if err = svc.workspaceSearch.Delete([]string{workspace.GetID()}); err != nil {
return err
}
if err = svc.workspaceCache.Delete(id); err != nil {
return err
}
if err = svc.s3.RemoveBucket(workspace.GetBucket()); err != nil {
return err
}
return nil
}
func (svc *WorkspaceService) HasEnoughSpaceForByteSize(id string, byteSize int64) (bool, error) {
workspace, err := svc.workspaceRepo.Find(id)
if err != nil {
return false, err
}
root, err := svc.fileRepo.Find(workspace.GetRootID())
if err != nil {
return false, err
}
usage, err := svc.fileRepo.GetSize(root.GetID())
if err != nil {
return false, err
}
expectedUsage := usage + byteSize
if expectedUsage > workspace.GetStorageCapacity() {
return false, err
}
return true, nil
}
func (svc *WorkspaceService) findAll(userID string) ([]*Workspace, error) {
user, err := svc.userRepo.Find(userID)
if err != nil {
return nil, err
}
ids, err := svc.workspaceRepo.GetIDs()
if err != nil {
return nil, err
}
authorized, err := svc.doAuthorizationByIDs(ids, user)
if err != nil {
return nil, err
}
mapped, err := svc.workspaceMapper.mapMany(authorized, userID)
if err != nil {
return nil, err
}
return mapped, nil
}
func (svc *WorkspaceService) doAuthorization(data []model.Workspace, user model.User) ([]model.Workspace, error) {
var res []model.Workspace
for _, w := range data {
if svc.workspaceGuard.IsAuthorized(user, w, model.PermissionViewer) {
res = append(res, w)
}
}
return res, nil
}
func (svc *WorkspaceService) doAuthorizationByIDs(ids []string, user model.User) ([]model.Workspace, error) {
var res []model.Workspace
for _, id := range ids {
var w model.Workspace
w, err := svc.workspaceCache.Get(id)
if err != nil {
return nil, err
}
if svc.workspaceGuard.IsAuthorized(user, w, model.PermissionViewer) {
res = append(res, w)
}
}
return res, nil
}
func (svc *WorkspaceService) doSorting(data []model.Workspace, sortBy string, sortOrder string) []model.Workspace {
if sortBy == SortByName {
sort.Slice(data, func(i, j int) bool {
if sortOrder == SortOrderDesc {
return data[i].GetName() > data[j].GetName()
} else {
return data[i].GetName() < data[j].GetName()
}
})
return data
} else if sortBy == SortByDateCreated {
sort.Slice(data, func(i, j int) bool {
a, _ := time.Parse(time.RFC3339, data[i].GetCreateTime())
b, _ := time.Parse(time.RFC3339, data[j].GetCreateTime())
if sortOrder == SortOrderDesc {
return a.UnixMilli() > b.UnixMilli()
} else {
return a.UnixMilli() < b.UnixMilli()
}
})
return data
} else if sortBy == SortByDateModified {
sort.Slice(data, func(i, j int) bool {
if data[i].GetUpdateTime() != nil && data[j].GetUpdateTime() != nil {
a, _ := time.Parse(time.RFC3339, *data[i].GetUpdateTime())
b, _ := time.Parse(time.RFC3339, *data[j].GetUpdateTime())
if sortOrder == SortOrderDesc {
return a.UnixMilli() > b.UnixMilli()
} else {
return a.UnixMilli() < b.UnixMilli()
}
} else {
return false
}
})
return data
}
return data
}
func (svc *WorkspaceService) doPagination(data []model.Workspace, page, size uint) ([]model.Workspace, uint, uint) {
totalElements := uint(len(data))
totalPages := (totalElements + size - 1) / size
if page > totalPages {
return nil, totalElements, totalPages
}
startIndex := (page - 1) * size
endIndex := startIndex + size
if endIndex > totalElements {
endIndex = totalElements
}
pageData := data[startIndex:endIndex]
return pageData, totalElements, totalPages
}
type workspaceMapper struct {
orgCache *cache.OrganizationCache
orgMapper *organizationMapper
groupCache *cache.GroupCache
}
func newWorkspaceMapper() *workspaceMapper {
return &workspaceMapper{
orgCache: cache.NewOrganizationCache(),
orgMapper: newOrganizationMapper(),
groupCache: cache.NewGroupCache(),
}
}
func (mp *workspaceMapper) mapOne(m model.Workspace, userID string) (*Workspace, error) {
org, err := mp.orgCache.Get(m.GetOrganizationID())
if err != nil {
return nil, err
}
v, err := mp.orgMapper.mapOne(org, userID)
if err != nil {
return nil, err
}
res := &Workspace{
ID: m.GetID(),
Name: m.GetName(),
RootID: m.GetRootID(),
StorageCapacity: m.GetStorageCapacity(),
Organization: *v,
CreateTime: m.GetCreateTime(),
UpdateTime: m.GetUpdateTime(),
}
res.Permission = ""
for _, p := range m.GetUserPermissions() {
if p.GetUserID() == userID && model.GetPermissionWeight(p.GetValue()) > model.GetPermissionWeight(res.Permission) {
res.Permission = p.GetValue()
}
}
for _, p := range m.GetGroupPermissions() {
g, err := mp.groupCache.Get(p.GetGroupID())
if err != nil {
return nil, err
}
for _, u := range g.GetUsers() {
if u == userID && model.GetPermissionWeight(p.GetValue()) > model.GetPermissionWeight(res.Permission) {
res.Permission = p.GetValue()
}
}
}
return res, nil
}
func (mp *workspaceMapper) mapMany(workspaces []model.Workspace, userID string) ([]*Workspace, error) {
res := make([]*Workspace, 0)
for _, f := range workspaces {
v, err := mp.mapOne(f, userID)
if err != nil {
return nil, err
}
res = append(res, v)
}
return res, nil
}