package service import ( "sort" "time" "voltaserve/cache" "voltaserve/config" "voltaserve/guard" "voltaserve/helper" "voltaserve/model" "voltaserve/repo" "voltaserve/search" ) type Group struct { ID string `json:"id"` Name string `json:"name"` Image *string `json:"image,omitempty"` Organization Organization `json:"organization"` Permission string `json:"permission"` CreateTime string `json:"createTime,omitempty"` UpdateTime *string `json:"updateTime"` } type GroupList struct { Data []*Group `json:"data"` TotalPages uint `json:"totalPages"` TotalElements uint `json:"totalElements"` Page uint `json:"page"` Size uint `json:"size"` } type GroupCreateOptions struct { Name string `json:"name" validate:"required,max=255"` Image *string `json:"image"` OrganizationID string `json:"organizationId" validate:"required"` } type GroupListOptions struct { Query string OrganizationID string Page uint Size uint SortBy string SortOrder string } type GroupUpdateNameOptions struct { Name string `json:"name" validate:"required,max=255"` } type GroupUpdateImageOptions struct { Image string `json:"image" validate:"required,base64"` } type GroupAddMemberOptions struct { UserID string `json:"userId" validate:"required"` } type GroupRemoveMemberOptions struct { UserID string `json:"userId" validate:"required"` } type GroupService struct { groupRepo repo.GroupRepo groupGuard *guard.GroupGuard groupSearch *search.GroupSearch groupMapper *groupMapper groupCache *cache.GroupCache userRepo repo.UserRepo userSearch *search.UserSearch userMapper *userMapper workspaceRepo repo.WorkspaceRepo workspaceCache *cache.WorkspaceCache fileRepo repo.FileRepo fileCache *cache.FileCache fileGuard *guard.FileGuard orgRepo repo.OrganizationRepo orgCache *cache.OrganizationCache orgGuard *guard.OrganizationGuard config config.Config } func NewGroupService() *GroupService { return &GroupService{ groupRepo: repo.NewGroupRepo(), groupGuard: guard.NewGroupGuard(), groupCache: cache.NewGroupCache(), groupSearch: search.NewGroupSearch(), groupMapper: newGroupMapper(), userRepo: repo.NewUserRepo(), userSearch: search.NewUserSearch(), userMapper: newUserMapper(), workspaceRepo: repo.NewWorkspaceRepo(), workspaceCache: cache.NewWorkspaceCache(), fileRepo: repo.NewFileRepo(), fileCache: cache.NewFileCache(), orgRepo: repo.NewOrganizationRepo(), orgGuard: guard.NewOrganizationGuard(), orgCache: cache.NewOrganizationCache(), fileGuard: guard.NewFileGuard(), config: config.GetConfig(), } } func (svc *GroupService) Create(opts GroupCreateOptions, userID string) (*Group, error) { user, err := svc.userRepo.Find(userID) if err != nil { return nil, err } org, err := svc.orgCache.Get(opts.OrganizationID) if err != nil { return nil, err } if err := svc.orgGuard.Authorize(user, org, model.PermissionEditor); err != nil { return nil, err } group, err := svc.groupRepo.Insert(repo.GroupInsertOptions{ ID: helper.NewID(), Name: opts.Name, OrganizationID: opts.OrganizationID, OwnerID: userID, }) if err != nil { return nil, err } if err := svc.groupRepo.GrantUserPermission(group.GetID(), userID, model.PermissionOwner); err != nil { return nil, err } group, err = svc.groupRepo.Find(group.GetID()) if err != nil { return nil, err } if err := svc.groupSearch.Index([]model.Group{group}); err != nil { return nil, err } if err := svc.groupCache.Set(group); err != nil { return nil, err } res, err := svc.groupMapper.mapOne(group, userID) if err != nil { return nil, err } return res, nil } func (svc *GroupService) Find(id string, userID string) (*Group, error) { user, err := svc.userRepo.Find(userID) if err != nil { return nil, err } group, err := svc.groupCache.Get(id) if err != nil { return nil, err } if err := svc.groupGuard.Authorize(user, group, model.PermissionViewer); err != nil { return nil, err } res, err := svc.groupMapper.mapOne(group, userID) if err != nil { return nil, err } return res, nil } func (svc *GroupService) List(opts GroupListOptions, userID string) (*GroupList, error) { user, err := svc.userRepo.Find(userID) if err != nil { return nil, err } var authorized []model.Group if opts.Query == "" { if opts.OrganizationID == "" { ids, err := svc.groupRepo.GetIDs() if err != nil { return nil, err } authorized, err = svc.doAuthorizationByIDs(ids, user) if err != nil { return nil, err } } else { groups, err := svc.orgRepo.GetGroups(opts.OrganizationID) if err != nil { return nil, err } authorized, err = svc.doAuthorization(groups, user) if err != nil { return nil, err } } } else { groups, err := svc.groupSearch.Query(opts.Query) if err != nil { return nil, err } var filtered []model.Group if opts.OrganizationID == "" { filtered = groups } else { for _, g := range groups { if g.GetOrganizationID() == opts.OrganizationID { filtered = append(filtered, g) } } } authorized, err = svc.doAuthorization(filtered, 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.groupMapper.mapMany(paged, userID) if err != nil { return nil, err } return &GroupList{ Data: mapped, TotalPages: totalPages, TotalElements: totalElements, Page: opts.Page, Size: uint(len(mapped)), }, nil } func (svc *GroupService) UpdateName(id string, name string, userID string) (*Group, error) { user, err := svc.userRepo.Find(userID) if err != nil { return nil, err } group, err := svc.groupCache.Get(id) if err != nil { return nil, err } if err := svc.groupGuard.Authorize(user, group, model.PermissionEditor); err != nil { return nil, err } group.SetName(name) if err := svc.groupRepo.Save(group); err != nil { return nil, err } if err := svc.groupSearch.Update([]model.Group{group}); err != nil { return nil, err } err = svc.groupCache.Set(group) if err != nil { return nil, err } res, err := svc.groupMapper.mapOne(group, userID) if err != nil { return nil, err } return res, nil } func (svc *GroupService) Delete(id string, userID string) error { user, err := svc.userRepo.Find(userID) if err != nil { return err } group, err := svc.groupCache.Get(id) if err != nil { return nil } if err := svc.groupGuard.Authorize(user, group, model.PermissionOwner); err != nil { return err } if err := svc.groupRepo.Delete(id); err != nil { return err } if err := svc.groupSearch.Delete([]string{group.GetID()}); err != nil { return err } if err := svc.refreshCacheForOrganization(group.GetOrganizationID()); err != nil { return err } return nil } func (svc *GroupService) AddMember(id string, memberID string, userID string) error { user, err := svc.userRepo.Find(userID) if err != nil { return err } group, err := svc.groupCache.Get(id) if err != nil { return nil } if err := svc.groupGuard.Authorize(user, group, model.PermissionOwner); err != nil { return err } if _, err := svc.userRepo.Find(memberID); err != nil { return err } if err := svc.groupRepo.AddUser(id, memberID); err != nil { return err } if err := svc.groupRepo.GrantUserPermission(group.GetID(), memberID, model.PermissionViewer); err != nil { return err } if _, err := svc.groupCache.Refresh(group.GetID()); err != nil { return err } if err := svc.refreshCacheForOrganization(group.GetOrganizationID()); err != nil { return err } return nil } func (svc *GroupService) RemoveMember(id string, memberID string, userID string) error { user, err := svc.userRepo.Find(userID) if err != nil { return err } group, err := svc.groupCache.Get(id) if err != nil { return nil } if err := svc.groupGuard.Authorize(user, group, model.PermissionOwner); err != nil { return err } if err := svc.RemoveMemberUnauthorized(id, memberID); err != nil { return err } return nil } func (svc *GroupService) RemoveMemberUnauthorized(id string, memberID string) error { group, err := svc.groupCache.Get(id) if err != nil { return nil } if _, err := svc.userRepo.Find(memberID); err != nil { return err } if err := svc.groupRepo.RemoveMember(id, memberID); err != nil { return err } if err := svc.groupRepo.RevokeUserPermission(id, memberID); err != nil { return err } if _, err := svc.groupCache.Refresh(group.GetID()); err != nil { return err } if err := svc.refreshCacheForOrganization(group.GetOrganizationID()); err != nil { return err } return nil } func (svc *GroupService) refreshCacheForOrganization(orgID string) error { workspaceIDs, err := svc.workspaceRepo.GetIDsByOrganization(orgID) if err != nil { return err } for _, workspaceID := range workspaceIDs { if _, err := svc.workspaceCache.Refresh(workspaceID); err != nil { return err } filesIDs, err := svc.fileRepo.GetIDsByWorkspace(workspaceID) if err != nil { return err } for _, id := range filesIDs { if _, err := svc.fileCache.Refresh(id); err != nil { return err } } } return nil } func (svc *GroupService) doAuthorization(data []model.Group, user model.User) ([]model.Group, error) { var res []model.Group for _, g := range data { if svc.groupGuard.IsAuthorized(user, g, model.PermissionViewer) { res = append(res, g) } } return res, nil } func (svc *GroupService) doAuthorizationByIDs(ids []string, user model.User) ([]model.Group, error) { var res []model.Group for _, id := range ids { var o model.Group o, err := svc.groupCache.Get(id) if err != nil { return nil, err } if svc.groupGuard.IsAuthorized(user, o, model.PermissionViewer) { res = append(res, o) } } return res, nil } func (svc *GroupService) doSorting(data []model.Group, sortBy string, sortOrder string) []model.Group { 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 *GroupService) doPagination(data []model.Group, page, size uint) ([]model.Group, 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 groupMapper struct { orgCache *cache.OrganizationCache orgMapper *organizationMapper groupCache *cache.GroupCache } func newGroupMapper() *groupMapper { return &groupMapper{ orgCache: cache.NewOrganizationCache(), orgMapper: newOrganizationMapper(), groupCache: cache.NewGroupCache(), } } func (mp *groupMapper) mapOne(m model.Group, userID string) (*Group, 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 := &Group{ ID: m.GetID(), Name: m.GetName(), 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 *groupMapper) mapMany(groups []model.Group, userID string) ([]*Group, error) { res := []*Group{} for _, g := range groups { v, err := mp.mapOne(g, userID) if err != nil { return nil, err } res = append(res, v) } return res, nil }