ajout app
This commit is contained in:
12
Voltaserve/api/service/constants.go
Normal file
12
Voltaserve/api/service/constants.go
Normal file
@ -0,0 +1,12 @@
|
||||
package service
|
||||
|
||||
const SortByEmail = "email"
|
||||
const SortByFullName = "full_name"
|
||||
const SortByName = "name"
|
||||
const SortByKind = "kind"
|
||||
const SortBySize = "size"
|
||||
const SortByDateCreated = "date_created"
|
||||
const SortByDateModified = "date_modified"
|
||||
|
||||
const SortOrderAsc = "asc"
|
||||
const SortOrderDesc = "desc"
|
1922
Voltaserve/api/service/file_service.go
Normal file
1922
Voltaserve/api/service/file_service.go
Normal file
File diff suppressed because it is too large
Load Diff
520
Voltaserve/api/service/group_service.go
Normal file
520
Voltaserve/api/service/group_service.go
Normal file
@ -0,0 +1,520 @@
|
||||
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
|
||||
}
|
428
Voltaserve/api/service/invitation_service.go
Normal file
428
Voltaserve/api/service/invitation_service.go
Normal file
@ -0,0 +1,428 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"voltaserve/cache"
|
||||
"voltaserve/config"
|
||||
"voltaserve/errorpkg"
|
||||
"voltaserve/guard"
|
||||
"voltaserve/infra"
|
||||
"voltaserve/model"
|
||||
"voltaserve/repo"
|
||||
)
|
||||
|
||||
type Invitation struct {
|
||||
ID string `json:"id"`
|
||||
Owner *User `json:"owner,omitempty"`
|
||||
Email string `json:"email"`
|
||||
Organization *Organization `json:"organization,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreateTime string `json:"createTime"`
|
||||
UpdateTime *string `json:"updateTime"`
|
||||
}
|
||||
|
||||
type InvitationList struct {
|
||||
Data []*Invitation `json:"data"`
|
||||
TotalPages uint `json:"totalPages"`
|
||||
TotalElements uint `json:"totalElements"`
|
||||
Page uint `json:"page"`
|
||||
Size uint `json:"size"`
|
||||
}
|
||||
|
||||
type InvitationCreateOptions struct {
|
||||
OrganizationID string `json:"organizationId" validate:"required"`
|
||||
Emails []string `json:"emails" validate:"required,dive,email"`
|
||||
}
|
||||
|
||||
type InvitationListOptions struct {
|
||||
Page uint
|
||||
Size uint
|
||||
SortBy string
|
||||
SortOrder string
|
||||
}
|
||||
|
||||
type InvitationService struct {
|
||||
orgRepo repo.OrganizationRepo
|
||||
orgMapper *organizationMapper
|
||||
invitationRepo repo.InvitationRepo
|
||||
invitationMapper *invitationMapper
|
||||
orgCache *cache.OrganizationCache
|
||||
orgGuard *guard.OrganizationGuard
|
||||
userRepo repo.UserRepo
|
||||
mailTmpl *infra.MailTemplate
|
||||
config config.Config
|
||||
}
|
||||
|
||||
func NewInvitationService() *InvitationService {
|
||||
return &InvitationService{
|
||||
orgRepo: repo.NewOrganizationRepo(),
|
||||
orgCache: cache.NewOrganizationCache(),
|
||||
orgGuard: guard.NewOrganizationGuard(),
|
||||
invitationRepo: repo.NewInvitationRepo(),
|
||||
invitationMapper: newInvitationMapper(),
|
||||
userRepo: repo.NewUserRepo(),
|
||||
mailTmpl: infra.NewMailTemplate(),
|
||||
orgMapper: newOrganizationMapper(),
|
||||
config: config.GetConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *InvitationService) Create(opts InvitationCreateOptions, userID string) error {
|
||||
for i := range opts.Emails {
|
||||
opts.Emails[i] = strings.ToLower(opts.Emails[i])
|
||||
}
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
org, err := svc.orgCache.Get(opts.OrganizationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := svc.orgGuard.Authorize(user, org, model.PermissionOwner); err != nil {
|
||||
return err
|
||||
}
|
||||
orgMembers, err := svc.orgRepo.GetMembers(opts.OrganizationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outgoingInvitations, err := svc.invitationRepo.GetOutgoing(opts.OrganizationID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var emails []string
|
||||
|
||||
/* Collect emails of non existing members and outgoing invitations */
|
||||
for _, e := range opts.Emails {
|
||||
existing := false
|
||||
for _, u := range orgMembers {
|
||||
if e == u.GetEmail() {
|
||||
existing = true
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, i := range outgoingInvitations {
|
||||
if e == i.GetEmail() && i.GetStatus() == model.InvitationStatusPending {
|
||||
existing = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !existing {
|
||||
emails = append(emails, e)
|
||||
}
|
||||
}
|
||||
|
||||
/* Persist invitations */
|
||||
invitations, err := svc.invitationRepo.Insert(repo.InvitationInsertOptions{
|
||||
UserID: userID,
|
||||
OrganizationID: opts.OrganizationID,
|
||||
Emails: emails,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/* Send emails */
|
||||
for _, inv := range invitations {
|
||||
variables := map[string]string{
|
||||
"USER_FULL_NAME": user.GetFullName(),
|
||||
"ORGANIZATION_NAME": org.GetName(),
|
||||
"UI_URL": svc.config.PublicUIURL,
|
||||
}
|
||||
_, err := svc.userRepo.FindByEmail(inv.GetEmail())
|
||||
var templateName string
|
||||
if err == nil {
|
||||
templateName = "join-organization"
|
||||
} else {
|
||||
templateName = "signup-and-join-organization"
|
||||
}
|
||||
if err := svc.mailTmpl.Send(templateName, inv.GetEmail(), variables); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *InvitationService) GetIncoming(opts InvitationListOptions, userID string) (*InvitationList, error) {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
invitations, err := svc.invitationRepo.GetIncoming(user.GetEmail())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.SortBy == "" {
|
||||
opts.SortBy = SortByDateCreated
|
||||
}
|
||||
if opts.SortOrder == "" {
|
||||
opts.SortOrder = SortOrderAsc
|
||||
}
|
||||
sorted := svc.doSorting(invitations, opts.SortBy, opts.SortOrder)
|
||||
paged, totalElements, totalPages := svc.doPagination(sorted, opts.Page, opts.Size)
|
||||
mapped, err := svc.invitationMapper.mapMany(paged, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &InvitationList{
|
||||
Data: mapped,
|
||||
TotalPages: totalPages,
|
||||
TotalElements: totalElements,
|
||||
Page: opts.Page,
|
||||
Size: uint(len(mapped)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *InvitationService) GetOutgoing(orgID string, opts InvitationListOptions, userID string) (*InvitationList, error) {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
invitations, err := svc.invitationRepo.GetOutgoing(orgID, user.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.SortBy == "" {
|
||||
opts.SortBy = SortByDateCreated
|
||||
}
|
||||
if opts.SortOrder == "" {
|
||||
opts.SortOrder = SortOrderAsc
|
||||
}
|
||||
sorted := svc.doSorting(invitations, opts.SortBy, opts.SortOrder)
|
||||
paged, totalElements, totalPages := svc.doPagination(sorted, opts.Page, opts.Size)
|
||||
mapped, err := svc.invitationMapper.mapMany(paged, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &InvitationList{
|
||||
Data: mapped,
|
||||
TotalPages: totalPages,
|
||||
TotalElements: totalElements,
|
||||
Page: opts.Page,
|
||||
Size: uint(len(mapped)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *InvitationService) Accept(id string, userID string) error {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
invitation, err := svc.invitationRepo.Find(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if invitation.GetStatus() != model.InvitationStatusPending {
|
||||
return errorpkg.NewCannotAcceptNonPendingInvitationError(invitation)
|
||||
}
|
||||
if user.GetEmail() != invitation.GetEmail() {
|
||||
return errorpkg.NewUserNotAllowedToAcceptInvitationError(user, invitation)
|
||||
}
|
||||
org, err := svc.orgCache.Get(invitation.GetOrganizationID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, u := range org.GetUsers() {
|
||||
if u == userID {
|
||||
return errorpkg.NewUserAlreadyMemberOfOrganizationError(user, org)
|
||||
}
|
||||
}
|
||||
invitation.SetStatus(model.InvitationStatusAccepted)
|
||||
if err := svc.invitationRepo.Save(invitation); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := svc.orgRepo.AddUser(invitation.GetOrganizationID(), userID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := svc.orgRepo.GrantUserPermission(invitation.GetOrganizationID(), userID, model.PermissionViewer); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := svc.orgCache.Refresh(invitation.GetOrganizationID()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *InvitationService) Decline(id string, userID string) error {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
invitation, err := svc.invitationRepo.Find(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if invitation.GetStatus() != model.InvitationStatusPending {
|
||||
return errorpkg.NewCannotDeclineNonPendingInvitationError(invitation)
|
||||
}
|
||||
if user.GetEmail() != invitation.GetEmail() {
|
||||
return errorpkg.NewUserNotAllowedToDeclineInvitationError(user, invitation)
|
||||
}
|
||||
invitation.SetStatus(model.InvitationStatusDeclined)
|
||||
if err := svc.invitationRepo.Save(invitation); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *InvitationService) Resend(id string, userID string) error {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
invitation, err := svc.invitationRepo.Find(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if invitation.GetStatus() != model.InvitationStatusPending {
|
||||
return errorpkg.NewCannotResendNonPendingInvitationError(invitation)
|
||||
}
|
||||
org, err := svc.orgCache.Get(invitation.GetOrganizationID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
variables := map[string]string{
|
||||
"USER_FULL_NAME": user.GetFullName(),
|
||||
"ORGANIZATION_NAME": org.GetName(),
|
||||
"UI_URL": svc.config.PublicUIURL,
|
||||
}
|
||||
_, err = svc.userRepo.FindByEmail(invitation.GetEmail())
|
||||
var templateName string
|
||||
if err == nil {
|
||||
templateName = "join-organization"
|
||||
} else {
|
||||
templateName = "signup-and-join-organization"
|
||||
}
|
||||
if err := svc.mailTmpl.Send(templateName, invitation.GetEmail(), variables); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *InvitationService) Delete(id string, userID string) error {
|
||||
invitation, err := svc.invitationRepo.Find(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if userID != invitation.GetOwnerID() {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errorpkg.NewUserNotAllowedToDeleteInvitationError(user, invitation)
|
||||
}
|
||||
if err := svc.invitationRepo.Delete(invitation.GetID()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *InvitationService) doSorting(data []model.Invitation, sortBy string, sortOrder string) []model.Invitation {
|
||||
if sortBy == SortByEmail {
|
||||
sort.Slice(data, func(i, j int) bool {
|
||||
if sortOrder == SortOrderDesc {
|
||||
return data[i].GetEmail() > data[j].GetEmail()
|
||||
} else {
|
||||
return data[i].GetEmail() < data[j].GetEmail()
|
||||
}
|
||||
})
|
||||
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 *InvitationService) doPagination(data []model.Invitation, page, size uint) ([]model.Invitation, 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 invitationMapper struct {
|
||||
orgCache *cache.OrganizationCache
|
||||
userRepo repo.UserRepo
|
||||
userMapper *userMapper
|
||||
orgMapper *organizationMapper
|
||||
}
|
||||
|
||||
func newInvitationMapper() *invitationMapper {
|
||||
return &invitationMapper{
|
||||
orgCache: cache.NewOrganizationCache(),
|
||||
userRepo: repo.NewUserRepo(),
|
||||
userMapper: newUserMapper(),
|
||||
orgMapper: newOrganizationMapper(),
|
||||
}
|
||||
}
|
||||
|
||||
func (mp *invitationMapper) mapOne(m model.Invitation, userID string) (*Invitation, error) {
|
||||
owner, err := mp.userRepo.Find(m.GetOwnerID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
return &Invitation{
|
||||
ID: m.GetID(),
|
||||
Owner: mp.userMapper.mapOne(owner),
|
||||
Email: m.GetEmail(),
|
||||
Organization: v,
|
||||
Status: m.GetStatus(),
|
||||
CreateTime: m.GetCreateTime(),
|
||||
UpdateTime: m.GetUpdateTime(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (mp *invitationMapper) mapMany(invitations []model.Invitation, userID string) ([]*Invitation, error) {
|
||||
res := make([]*Invitation, 0)
|
||||
for _, m := range invitations {
|
||||
v, err := mp.mapOne(m, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, v)
|
||||
}
|
||||
return res, nil
|
||||
}
|
45
Voltaserve/api/service/notification_service.go
Normal file
45
Voltaserve/api/service/notification_service.go
Normal file
@ -0,0 +1,45 @@
|
||||
package service
|
||||
|
||||
import "voltaserve/repo"
|
||||
|
||||
type Notification struct {
|
||||
Type string `json:"type"`
|
||||
Body interface{} `json:"body"`
|
||||
}
|
||||
|
||||
type NotificationService struct {
|
||||
userRepo repo.UserRepo
|
||||
invitationRepo repo.InvitationRepo
|
||||
invitationMapper *invitationMapper
|
||||
}
|
||||
|
||||
func NewNotificationService() *NotificationService {
|
||||
return &NotificationService{
|
||||
userRepo: repo.NewUserRepo(),
|
||||
invitationRepo: repo.NewInvitationRepo(),
|
||||
invitationMapper: newInvitationMapper(),
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *NotificationService) GetAll(userID string) ([]*Notification, error) {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
invitations, err := svc.invitationRepo.GetIncoming(user.GetEmail())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
notifications := make([]*Notification, 0)
|
||||
for _, inv := range invitations {
|
||||
v, err := svc.invitationMapper.mapOne(inv, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
notifications = append(notifications, &Notification{
|
||||
Type: "new_invitation",
|
||||
Body: &v,
|
||||
})
|
||||
}
|
||||
return notifications, nil
|
||||
}
|
418
Voltaserve/api/service/organization_service.go
Normal file
418
Voltaserve/api/service/organization_service.go
Normal file
@ -0,0 +1,418 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
"voltaserve/cache"
|
||||
"voltaserve/config"
|
||||
"voltaserve/errorpkg"
|
||||
"voltaserve/guard"
|
||||
"voltaserve/helper"
|
||||
"voltaserve/model"
|
||||
"voltaserve/repo"
|
||||
"voltaserve/search"
|
||||
|
||||
"github.com/gofiber/fiber/v2/log"
|
||||
)
|
||||
|
||||
type Organization struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Image *string `json:"image,omitempty"`
|
||||
Permission string `json:"permission"`
|
||||
CreateTime string `json:"createTime"`
|
||||
UpdateTime *string `json:"updateTime,omitempty"`
|
||||
}
|
||||
|
||||
type OrganizationList struct {
|
||||
Data []*Organization `json:"data"`
|
||||
TotalPages uint `json:"totalPages"`
|
||||
TotalElements uint `json:"totalElements"`
|
||||
Page uint `json:"page"`
|
||||
Size uint `json:"size"`
|
||||
}
|
||||
|
||||
type OrganizationCreateOptions struct {
|
||||
Name string `json:"name" validate:"required,max=255"`
|
||||
Image *string `json:"image"`
|
||||
}
|
||||
|
||||
type OrganizationListOptions struct {
|
||||
Query string
|
||||
Page uint
|
||||
Size uint
|
||||
SortBy string
|
||||
SortOrder string
|
||||
}
|
||||
|
||||
type OrganizationUpdateNameOptions struct {
|
||||
Name string `json:"name" validate:"required,max=255"`
|
||||
}
|
||||
|
||||
type OrganizationUpdateImageOptions struct {
|
||||
Image string `json:"image" validate:"required,base64"`
|
||||
}
|
||||
|
||||
type OrganizationRemoveMemberOptions struct {
|
||||
UserID string `json:"userId" validate:"required"`
|
||||
}
|
||||
|
||||
type OrganizationService struct {
|
||||
orgRepo repo.OrganizationRepo
|
||||
orgCache *cache.OrganizationCache
|
||||
orgGuard *guard.OrganizationGuard
|
||||
orgMapper *organizationMapper
|
||||
orgSearch *search.OrganizationSearch
|
||||
userRepo repo.UserRepo
|
||||
userSearch *search.UserSearch
|
||||
userMapper *userMapper
|
||||
groupRepo repo.GroupRepo
|
||||
groupService *GroupService
|
||||
groupMapper *groupMapper
|
||||
config config.Config
|
||||
}
|
||||
|
||||
func NewOrganizationService() *OrganizationService {
|
||||
return &OrganizationService{
|
||||
orgRepo: repo.NewOrganizationRepo(),
|
||||
orgCache: cache.NewOrganizationCache(),
|
||||
orgGuard: guard.NewOrganizationGuard(),
|
||||
orgSearch: search.NewOrganizationSearch(),
|
||||
orgMapper: newOrganizationMapper(),
|
||||
userRepo: repo.NewUserRepo(),
|
||||
userSearch: search.NewUserSearch(),
|
||||
groupRepo: repo.NewGroupRepo(),
|
||||
groupService: NewGroupService(),
|
||||
groupMapper: newGroupMapper(),
|
||||
userMapper: newUserMapper(),
|
||||
config: config.GetConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *OrganizationService) Create(opts OrganizationCreateOptions, userID string) (*Organization, error) {
|
||||
org, err := svc.orgRepo.Insert(repo.OrganizationInsertOptions{
|
||||
ID: helper.NewID(),
|
||||
Name: opts.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := svc.orgRepo.GrantUserPermission(org.GetID(), userID, model.PermissionOwner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
org, err = svc.orgRepo.Find(org.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := svc.orgSearch.Index([]model.Organization{org}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := svc.orgCache.Set(org); err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
res, err := svc.orgMapper.mapOne(org, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (svc *OrganizationService) Find(id string, userID string) (*Organization, error) {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
org, err := svc.orgCache.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := svc.orgGuard.Authorize(user, org, model.PermissionViewer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := svc.orgMapper.mapOne(org, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (svc *OrganizationService) List(opts OrganizationListOptions, userID string) (*OrganizationList, error) {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var authorized []model.Organization
|
||||
if opts.Query == "" {
|
||||
ids, err := svc.orgRepo.GetIDs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorized, err = svc.doAuthorizationByIDs(ids, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
orgs, err := svc.orgSearch.Query(opts.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authorized, err = svc.doAuthorization(orgs, 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.orgMapper.mapMany(paged, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &OrganizationList{
|
||||
Data: mapped,
|
||||
TotalPages: totalPages,
|
||||
TotalElements: totalElements,
|
||||
Page: opts.Page,
|
||||
Size: uint(len(mapped)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *OrganizationService) UpdateName(id string, name string, userID string) (*Organization, error) {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
org, err := svc.orgCache.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := svc.orgGuard.Authorize(user, org, model.PermissionEditor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
org.SetName(name)
|
||||
if err := svc.orgRepo.Save(org); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := svc.orgSearch.Update([]model.Organization{org}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = svc.orgCache.Set(org)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := svc.orgMapper.mapOne(org, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (svc *OrganizationService) Delete(id string, userID string) error {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
org, err := svc.orgCache.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := svc.orgGuard.Authorize(user, org, model.PermissionOwner); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := svc.orgRepo.Delete(id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := svc.orgCache.Delete(org.GetID()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := svc.orgSearch.Delete([]string{org.GetID()}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *OrganizationService) RemoveMember(id string, memberID string, userID string) error {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
member, err := svc.userRepo.Find(memberID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
org, err := svc.orgCache.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/* Make sure member is not the last remaining owner of the organization */
|
||||
ownerCount, err := svc.orgRepo.GetOwnerCount(org.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if svc.orgGuard.IsAuthorized(member, org, model.PermissionOwner) && ownerCount == 1 {
|
||||
return errorpkg.NewCannotRemoveLastRemainingOwnerOfOrganizationError(org.GetID())
|
||||
}
|
||||
|
||||
if userID != member.GetID() {
|
||||
if err := svc.orgGuard.Authorize(user, org, model.PermissionOwner); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove member from all groups belonging to this organization */
|
||||
groupsIDs, err := svc.groupRepo.GetIDsForOrganization(org.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, groupID := range groupsIDs {
|
||||
if err := svc.groupService.RemoveMemberUnauthorized(groupID, member.GetID()); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := svc.orgRepo.RevokeUserPermission(id, member.GetID()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := svc.orgRepo.RemoveMember(id, member.GetID()); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := svc.orgCache.Refresh(org.GetID()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *OrganizationService) doAuthorization(data []model.Organization, user model.User) ([]model.Organization, error) {
|
||||
var res []model.Organization
|
||||
for _, o := range data {
|
||||
if svc.orgGuard.IsAuthorized(user, o, model.PermissionViewer) {
|
||||
res = append(res, o)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (svc *OrganizationService) doAuthorizationByIDs(ids []string, user model.User) ([]model.Organization, error) {
|
||||
var res []model.Organization
|
||||
for _, id := range ids {
|
||||
var o model.Organization
|
||||
o, err := svc.orgCache.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if svc.orgGuard.IsAuthorized(user, o, model.PermissionViewer) {
|
||||
res = append(res, o)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (svc *OrganizationService) doSorting(data []model.Organization, sortBy string, sortOrder string) []model.Organization {
|
||||
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 *OrganizationService) doPagination(data []model.Organization, page, size uint) ([]model.Organization, 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 organizationMapper struct {
|
||||
groupCache *cache.GroupCache
|
||||
}
|
||||
|
||||
func newOrganizationMapper() *organizationMapper {
|
||||
return &organizationMapper{
|
||||
groupCache: cache.NewGroupCache(),
|
||||
}
|
||||
}
|
||||
|
||||
func (mp *organizationMapper) mapOne(m model.Organization, userID string) (*Organization, error) {
|
||||
res := &Organization{
|
||||
ID: m.GetID(),
|
||||
Name: m.GetName(),
|
||||
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 *organizationMapper) mapMany(orgs []model.Organization, userID string) ([]*Organization, error) {
|
||||
res := make([]*Organization, 0)
|
||||
for _, f := range orgs {
|
||||
v, err := mp.mapOne(f, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, v)
|
||||
}
|
||||
return res, nil
|
||||
}
|
142
Voltaserve/api/service/storage_service.go
Normal file
142
Voltaserve/api/service/storage_service.go
Normal file
@ -0,0 +1,142 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"voltaserve/cache"
|
||||
"voltaserve/guard"
|
||||
"voltaserve/model"
|
||||
"voltaserve/repo"
|
||||
)
|
||||
|
||||
type StorageUsage struct {
|
||||
Bytes int64 `json:"bytes"`
|
||||
MaxBytes int64 `json:"maxBytes"`
|
||||
Percentage int `json:"percentage"`
|
||||
}
|
||||
|
||||
type StorageService struct {
|
||||
workspaceRepo repo.WorkspaceRepo
|
||||
workspaceCache *cache.WorkspaceCache
|
||||
workspaceGuard *guard.WorkspaceGuard
|
||||
fileRepo repo.FileRepo
|
||||
fileCache *cache.FileCache
|
||||
fileGuard *guard.FileGuard
|
||||
storageMapper *storageMapper
|
||||
userRepo repo.UserRepo
|
||||
}
|
||||
|
||||
func NewStorageService() *StorageService {
|
||||
return &StorageService{
|
||||
workspaceRepo: repo.NewWorkspaceRepo(),
|
||||
workspaceCache: cache.NewWorkspaceCache(),
|
||||
workspaceGuard: guard.NewWorkspaceGuard(),
|
||||
fileRepo: repo.NewFileRepo(),
|
||||
fileCache: cache.NewFileCache(),
|
||||
fileGuard: guard.NewFileGuard(),
|
||||
storageMapper: newStorageMapper(),
|
||||
userRepo: repo.NewUserRepo(),
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *StorageService) GetAccountUsage(userID string) (*StorageUsage, error) {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids, err := svc.workspaceRepo.GetIDs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workspaces := []model.Workspace{}
|
||||
for _, id := range ids {
|
||||
var workspace model.Workspace
|
||||
workspace, err = svc.workspaceCache.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if svc.workspaceGuard.IsAuthorized(user, workspace, model.PermissionOwner) {
|
||||
workspaces = append(workspaces, workspace)
|
||||
}
|
||||
}
|
||||
var maxBytes int64 = 0
|
||||
var b int64 = 0
|
||||
for _, w := range workspaces {
|
||||
root, err := svc.fileCache.Get(w.GetRootID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size, err := svc.fileRepo.GetSize(root.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = b + size
|
||||
maxBytes = maxBytes + w.GetStorageCapacity()
|
||||
}
|
||||
return svc.storageMapper.mapStorageUsage(b, maxBytes), nil
|
||||
}
|
||||
|
||||
func (svc *StorageService) GetWorkspaceUsage(workspaceID string, userID string) (*StorageUsage, error) {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workspace, err := svc.workspaceCache.Get(workspaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = svc.workspaceGuard.Authorize(user, workspace, model.PermissionViewer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root, err := svc.fileCache.Get(workspace.GetRootID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = svc.fileGuard.Authorize(user, root, model.PermissionViewer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size, err := svc.fileRepo.GetSize(root.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return svc.storageMapper.mapStorageUsage(size, workspace.GetStorageCapacity()), nil
|
||||
}
|
||||
|
||||
func (svc *StorageService) GetFileUsage(fileID string, userID string) (*StorageUsage, error) {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file, err := svc.fileCache.Get(fileID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = svc.fileGuard.Authorize(user, file, model.PermissionViewer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size, err := svc.fileRepo.GetSize(file.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workspace, err := svc.workspaceCache.Get(file.GetWorkspaceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return svc.storageMapper.mapStorageUsage(size, workspace.GetStorageCapacity()), nil
|
||||
}
|
||||
|
||||
type storageMapper struct {
|
||||
}
|
||||
|
||||
func newStorageMapper() *storageMapper {
|
||||
return &storageMapper{}
|
||||
}
|
||||
|
||||
func (mp *storageMapper) mapStorageUsage(byteCount int64, maxBytes int64) *StorageUsage {
|
||||
res := StorageUsage{
|
||||
Bytes: byteCount,
|
||||
MaxBytes: maxBytes,
|
||||
}
|
||||
if maxBytes != 0 {
|
||||
res.Percentage = int(byteCount * 100 / maxBytes)
|
||||
}
|
||||
return &res
|
||||
}
|
247
Voltaserve/api/service/user_service.go
Normal file
247
Voltaserve/api/service/user_service.go
Normal file
@ -0,0 +1,247 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"voltaserve/cache"
|
||||
"voltaserve/config"
|
||||
"voltaserve/guard"
|
||||
"voltaserve/model"
|
||||
"voltaserve/repo"
|
||||
"voltaserve/search"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
FullName string `json:"fullName"`
|
||||
Picture *string `json:"picture,omitempty"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
CreateTime string `json:"createTime"`
|
||||
UpdateTime *string `json:"updateTime"`
|
||||
}
|
||||
|
||||
type UserList struct {
|
||||
Data []*User `json:"data"`
|
||||
TotalPages uint `json:"totalPages"`
|
||||
TotalElements uint `json:"totalElements"`
|
||||
Page uint `json:"page"`
|
||||
Size uint `json:"size"`
|
||||
}
|
||||
|
||||
type UserListOptions struct {
|
||||
Query string
|
||||
OrganizationID string
|
||||
GroupID string
|
||||
NonGroupMembersOnly bool
|
||||
SortBy string
|
||||
SortOrder string
|
||||
Page uint
|
||||
Size uint
|
||||
}
|
||||
|
||||
type UserService struct {
|
||||
userRepo repo.UserRepo
|
||||
userMapper *userMapper
|
||||
userSearch *search.UserSearch
|
||||
orgRepo repo.OrganizationRepo
|
||||
orgCache *cache.OrganizationCache
|
||||
orgGuard *guard.OrganizationGuard
|
||||
groupRepo repo.GroupRepo
|
||||
groupGuard *guard.GroupGuard
|
||||
groupCache *cache.GroupCache
|
||||
config config.Config
|
||||
}
|
||||
|
||||
func NewUserService() *UserService {
|
||||
return &UserService{
|
||||
userRepo: repo.NewUserRepo(),
|
||||
userMapper: newUserMapper(),
|
||||
userSearch: search.NewUserSearch(),
|
||||
orgRepo: repo.NewOrganizationRepo(),
|
||||
orgCache: cache.NewOrganizationCache(),
|
||||
orgGuard: guard.NewOrganizationGuard(),
|
||||
groupRepo: repo.NewGroupRepo(),
|
||||
groupGuard: guard.NewGroupGuard(),
|
||||
groupCache: cache.NewGroupCache(),
|
||||
config: config.GetConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *UserService) List(opts UserListOptions, userID string) (*UserList, error) {
|
||||
user, err := svc.userRepo.Find(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.OrganizationID == "" && opts.GroupID == "" {
|
||||
return &UserList{
|
||||
Data: []*User{},
|
||||
TotalPages: 1,
|
||||
TotalElements: 0,
|
||||
Page: 1,
|
||||
Size: 0,
|
||||
}, nil
|
||||
}
|
||||
var org model.Organization
|
||||
if opts.OrganizationID != "" {
|
||||
org, err = svc.orgCache.Get(opts.OrganizationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := svc.orgGuard.Authorize(user, org, model.PermissionViewer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var group model.Group
|
||||
if opts.GroupID != "" {
|
||||
group, err = svc.groupCache.Get(opts.GroupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := svc.groupGuard.Authorize(user, group, model.PermissionViewer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
res := []model.User{}
|
||||
if opts.Query == "" {
|
||||
if opts.OrganizationID != "" && opts.GroupID != "" && opts.NonGroupMembersOnly {
|
||||
orgMembers, err := svc.orgRepo.GetMembers(group.GetOrganizationID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
groupMembers, err := svc.groupRepo.GetMembers(opts.GroupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, om := range orgMembers {
|
||||
found := false
|
||||
for _, tm := range groupMembers {
|
||||
if om.GetID() == tm.GetID() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
res = append(res, om)
|
||||
}
|
||||
}
|
||||
} else if opts.OrganizationID != "" {
|
||||
res, err = svc.orgRepo.GetMembers(opts.OrganizationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if opts.GroupID != "" {
|
||||
res, err = svc.groupRepo.GetMembers(opts.GroupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
users, err := svc.userSearch.Query(opts.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var members []model.User
|
||||
if opts.OrganizationID != "" {
|
||||
members, err = svc.orgRepo.GetMembers(opts.OrganizationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if opts.GroupID != "" {
|
||||
members, err = svc.groupRepo.GetMembers(opts.GroupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, m := range members {
|
||||
for _, u := range users {
|
||||
if u.GetID() == m.GetID() {
|
||||
res = append(res, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if opts.SortBy == "" {
|
||||
opts.SortBy = SortByDateCreated
|
||||
}
|
||||
if opts.SortOrder == "" {
|
||||
opts.SortOrder = SortOrderAsc
|
||||
}
|
||||
sorted := svc.doSorting(res, opts.SortBy, opts.SortOrder)
|
||||
paged, totalElements, totalPages := svc.doPagination(sorted, opts.Page, opts.Size)
|
||||
mapped, err := svc.userMapper.mapMany(paged)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &UserList{
|
||||
Data: mapped,
|
||||
TotalPages: totalPages,
|
||||
TotalElements: totalElements,
|
||||
Page: opts.Page,
|
||||
Size: uint(len(mapped)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (svc *UserService) doSorting(data []model.User, sortBy string, sortOrder string) []model.User {
|
||||
if sortBy == SortByEmail {
|
||||
sort.Slice(data, func(i, j int) bool {
|
||||
if sortOrder == SortOrderDesc {
|
||||
return data[i].GetEmail() > data[j].GetEmail()
|
||||
} else {
|
||||
return data[i].GetEmail() < data[j].GetEmail()
|
||||
}
|
||||
})
|
||||
return data
|
||||
} else if sortBy == SortByFullName {
|
||||
sort.Slice(data, func(i, j int) bool {
|
||||
if sortOrder == SortOrderDesc {
|
||||
return data[i].GetFullName() > data[j].GetFullName()
|
||||
} else {
|
||||
return data[i].GetFullName() < data[j].GetFullName()
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (svc *UserService) doPagination(data []model.User, page, size uint) ([]model.User, 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 userMapper struct {
|
||||
}
|
||||
|
||||
func newUserMapper() *userMapper {
|
||||
return &userMapper{}
|
||||
}
|
||||
|
||||
func (mp *userMapper) mapOne(user model.User) *User {
|
||||
return &User{
|
||||
ID: user.GetID(),
|
||||
FullName: user.GetFullName(),
|
||||
Picture: user.GetPicture(),
|
||||
Email: user.GetEmail(),
|
||||
Username: user.GetUsername(),
|
||||
CreateTime: user.GetCreateTime(),
|
||||
UpdateTime: user.GetUpdateTime(),
|
||||
}
|
||||
}
|
||||
|
||||
func (mp *userMapper) mapMany(users []model.User) ([]*User, error) {
|
||||
res := []*User{}
|
||||
for _, u := range users {
|
||||
res = append(res, mp.mapOne(u))
|
||||
}
|
||||
return res, nil
|
||||
}
|
502
Voltaserve/api/service/workspace_service.go
Normal file
502
Voltaserve/api/service/workspace_service.go
Normal file
@ -0,0 +1,502 @@
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user