d0f304c262
- use `roles_authorization.go` APIs as the ultimate source of truth - unify&simplify community modifications in terms of events creation
543 lines
15 KiB
Go
543 lines
15 KiB
Go
package communities
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
)
|
|
|
|
func (o *Community) ChatsByCategoryID(categoryID string) []string {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
var chatIDs []string
|
|
if o.config == nil || o.config.CommunityDescription == nil {
|
|
return chatIDs
|
|
}
|
|
|
|
for chatID, chat := range o.config.CommunityDescription.Chats {
|
|
if chat.CategoryId == categoryID {
|
|
chatIDs = append(chatIDs, chatID)
|
|
}
|
|
}
|
|
return chatIDs
|
|
}
|
|
|
|
func (o *Community) CommunityChatsIDs() []string {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
var chatIDs []string
|
|
if o.config == nil || o.config.CommunityDescription == nil {
|
|
return chatIDs
|
|
}
|
|
|
|
for chatID := range o.config.CommunityDescription.Chats {
|
|
chatIDs = append(chatIDs, chatID)
|
|
}
|
|
return chatIDs
|
|
}
|
|
|
|
func (o *Community) CreateCategory(categoryID string, categoryName string, chatIDs []string) (*CommunityChanges, error) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CATEGORY_CREATE)) {
|
|
return nil, ErrNotAuthorized
|
|
}
|
|
|
|
changes, err := o.createCategory(categoryID, categoryName, chatIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
changes.CategoriesAdded[categoryID] = o.config.CommunityDescription.Categories[categoryID]
|
|
for i, cid := range chatIDs {
|
|
changes.ChatsModified[cid] = &CommunityChatChanges{
|
|
MembersAdded: make(map[string]*protobuf.CommunityMember),
|
|
MembersRemoved: make(map[string]*protobuf.CommunityMember),
|
|
CategoryModified: categoryID,
|
|
PositionModified: i,
|
|
}
|
|
}
|
|
|
|
if o.IsControlNode() {
|
|
o.increaseClock()
|
|
} else {
|
|
err := o.addNewCommunityEvent(o.ToCreateCategoryCommunityEvent(categoryID, categoryName, chatIDs))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return changes, nil
|
|
}
|
|
|
|
func (o *Community) EditCategory(categoryID string, categoryName string, chatIDs []string) (*CommunityChanges, error) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CATEGORY_EDIT)) {
|
|
return nil, ErrNotAuthorized
|
|
}
|
|
|
|
changes, err := o.editCategory(categoryID, categoryName, chatIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
changes.CategoriesModified[categoryID] = o.config.CommunityDescription.Categories[categoryID]
|
|
for i, cid := range chatIDs {
|
|
changes.ChatsModified[cid] = &CommunityChatChanges{
|
|
MembersAdded: make(map[string]*protobuf.CommunityMember),
|
|
MembersRemoved: make(map[string]*protobuf.CommunityMember),
|
|
CategoryModified: categoryID,
|
|
PositionModified: i,
|
|
}
|
|
}
|
|
|
|
if o.IsControlNode() {
|
|
o.increaseClock()
|
|
} else {
|
|
err := o.addNewCommunityEvent(o.ToEditCategoryCommunityEvent(categoryID, categoryName, chatIDs))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return changes, nil
|
|
}
|
|
|
|
func (o *Community) ReorderCategories(categoryID string, newPosition int) (*CommunityChanges, error) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CATEGORY_REORDER)) {
|
|
return nil, ErrNotAuthorized
|
|
}
|
|
|
|
changes, err := o.reorderCategories(categoryID, newPosition)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if o.IsControlNode() {
|
|
o.increaseClock()
|
|
} else {
|
|
err := o.addNewCommunityEvent(o.ToReorderCategoryCommunityEvent(categoryID, newPosition))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return changes, nil
|
|
}
|
|
|
|
func (o *Community) setModifiedCategories(changes *CommunityChanges, s sortSlice) {
|
|
sort.Sort(s)
|
|
for i, catSortHelper := range s {
|
|
if o.config.CommunityDescription.Categories[catSortHelper.catID].Position != int32(i) {
|
|
o.config.CommunityDescription.Categories[catSortHelper.catID].Position = int32(i)
|
|
changes.CategoriesModified[catSortHelper.catID] = o.config.CommunityDescription.Categories[catSortHelper.catID]
|
|
}
|
|
}
|
|
}
|
|
|
|
func (o *Community) ReorderChat(categoryID string, chatID string, newPosition int) (*CommunityChanges, error) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CHANNEL_REORDER)) {
|
|
return nil, ErrNotAuthorized
|
|
}
|
|
|
|
changes, err := o.reorderChat(categoryID, chatID, newPosition)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if o.IsControlNode() {
|
|
o.increaseClock()
|
|
} else {
|
|
err := o.addNewCommunityEvent(o.ToReorderChannelCommunityEvent(categoryID, chatID, newPosition))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return changes, nil
|
|
}
|
|
|
|
func (o *Community) SortCategoryChats(changes *CommunityChanges, categoryID string) {
|
|
var catChats []string
|
|
for k, c := range o.config.CommunityDescription.Chats {
|
|
if c.CategoryId == categoryID {
|
|
catChats = append(catChats, k)
|
|
}
|
|
}
|
|
|
|
sortedChats := make(sortSlice, 0, len(catChats))
|
|
for _, k := range catChats {
|
|
sortedChats = append(sortedChats, sorterHelperIdx{
|
|
pos: o.config.CommunityDescription.Chats[k].Position,
|
|
chatID: k,
|
|
})
|
|
}
|
|
|
|
sort.Sort(sortedChats)
|
|
|
|
for i, chatSortHelper := range sortedChats {
|
|
if o.config.CommunityDescription.Chats[chatSortHelper.chatID].Position != int32(i) {
|
|
o.config.CommunityDescription.Chats[chatSortHelper.chatID].Position = int32(i)
|
|
if changes.ChatsModified[chatSortHelper.chatID] != nil {
|
|
changes.ChatsModified[chatSortHelper.chatID].PositionModified = i
|
|
} else {
|
|
changes.ChatsModified[chatSortHelper.chatID] = &CommunityChatChanges{
|
|
PositionModified: i,
|
|
MembersAdded: make(map[string]*protobuf.CommunityMember),
|
|
MembersRemoved: make(map[string]*protobuf.CommunityMember),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (o *Community) insertAndSort(changes *CommunityChanges, oldCategoryID string, categoryID string, chatID string, chat *protobuf.CommunityChat, newPosition int) {
|
|
// We sort the chats here because maps are not guaranteed to keep order
|
|
var catChats []string
|
|
sortedChats := make(sortSlice, 0, len(o.config.CommunityDescription.Chats))
|
|
for k, v := range o.config.CommunityDescription.Chats {
|
|
sortedChats = append(sortedChats, sorterHelperIdx{
|
|
pos: v.Position,
|
|
chatID: k,
|
|
})
|
|
}
|
|
sort.Sort(sortedChats)
|
|
for _, k := range sortedChats {
|
|
if o.config.CommunityDescription.Chats[k.chatID].CategoryId == categoryID {
|
|
catChats = append(catChats, k.chatID)
|
|
}
|
|
}
|
|
|
|
if newPosition > 0 && newPosition >= len(catChats) {
|
|
newPosition = len(catChats) - 1
|
|
} else if newPosition < 0 {
|
|
newPosition = 0
|
|
}
|
|
|
|
decrease := false
|
|
if chat.Position > int32(newPosition) {
|
|
decrease = true
|
|
}
|
|
|
|
for k, v := range o.config.CommunityDescription.Chats {
|
|
if k != chatID && newPosition == int(v.Position) && v.CategoryId == categoryID {
|
|
if oldCategoryID == categoryID {
|
|
if decrease {
|
|
v.Position++
|
|
} else {
|
|
v.Position--
|
|
}
|
|
} else {
|
|
v.Position++
|
|
}
|
|
}
|
|
}
|
|
|
|
idx := -1
|
|
currChatID := ""
|
|
var sortedChatIDs []string
|
|
for i, k := range catChats {
|
|
if o.config.CommunityDescription.Chats[k] != chat && ((decrease && o.config.CommunityDescription.Chats[k].Position < int32(newPosition)) || (!decrease && o.config.CommunityDescription.Chats[k].Position <= int32(newPosition))) {
|
|
sortedChatIDs = append(sortedChatIDs, k)
|
|
} else {
|
|
if o.config.CommunityDescription.Chats[k] == chat {
|
|
idx = i
|
|
currChatID = k
|
|
}
|
|
}
|
|
}
|
|
|
|
sortedChatIDs = append(sortedChatIDs, currChatID)
|
|
|
|
for i, k := range catChats {
|
|
if i == idx || (decrease && o.config.CommunityDescription.Chats[k].Position < int32(newPosition)) || (!decrease && o.config.CommunityDescription.Chats[k].Position <= int32(newPosition)) {
|
|
continue
|
|
}
|
|
sortedChatIDs = append(sortedChatIDs, k)
|
|
}
|
|
|
|
for i, sortedChatID := range sortedChatIDs {
|
|
if o.config.CommunityDescription.Chats[sortedChatID].Position != int32(i) {
|
|
o.config.CommunityDescription.Chats[sortedChatID].Position = int32(i)
|
|
if changes.ChatsModified[sortedChatID] != nil {
|
|
changes.ChatsModified[sortedChatID].PositionModified = i
|
|
} else {
|
|
changes.ChatsModified[sortedChatID] = &CommunityChatChanges{
|
|
MembersAdded: make(map[string]*protobuf.CommunityMember),
|
|
MembersRemoved: make(map[string]*protobuf.CommunityMember),
|
|
PositionModified: i,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (o *Community) getCategoryChatCount(categoryID string) int {
|
|
result := 0
|
|
for _, chat := range o.config.CommunityDescription.Chats {
|
|
if chat.CategoryId == categoryID {
|
|
result = result + 1
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (o *Community) DeleteCategory(categoryID string) (*CommunityChanges, error) {
|
|
o.mutex.Lock()
|
|
defer o.mutex.Unlock()
|
|
|
|
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CATEGORY_DELETE)) {
|
|
return nil, ErrNotAuthorized
|
|
}
|
|
|
|
changes, err := o.deleteCategory(categoryID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if o.IsControlNode() {
|
|
o.increaseClock()
|
|
} else {
|
|
err := o.addNewCommunityEvent(o.ToDeleteCategoryCommunityEvent(categoryID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return changes, nil
|
|
}
|
|
|
|
func (o *Community) createCategory(categoryID string, categoryName string, chatIDs []string) (*CommunityChanges, error) {
|
|
if o.config.CommunityDescription.Categories == nil {
|
|
o.config.CommunityDescription.Categories = make(map[string]*protobuf.CommunityCategory)
|
|
}
|
|
if _, ok := o.config.CommunityDescription.Categories[categoryID]; ok {
|
|
return nil, ErrCategoryAlreadyExists
|
|
}
|
|
|
|
for _, cid := range chatIDs {
|
|
c, exists := o.config.CommunityDescription.Chats[cid]
|
|
if !exists {
|
|
return nil, ErrChatNotFound
|
|
}
|
|
|
|
if exists && c.CategoryId != categoryID && c.CategoryId != "" {
|
|
return nil, ErrChatAlreadyAssigned
|
|
}
|
|
}
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
o.config.CommunityDescription.Categories[categoryID] = &protobuf.CommunityCategory{
|
|
CategoryId: categoryID,
|
|
Name: categoryName,
|
|
Position: int32(len(o.config.CommunityDescription.Categories)),
|
|
}
|
|
|
|
for i, cid := range chatIDs {
|
|
o.config.CommunityDescription.Chats[cid].CategoryId = categoryID
|
|
o.config.CommunityDescription.Chats[cid].Position = int32(i)
|
|
}
|
|
|
|
o.SortCategoryChats(changes, "")
|
|
|
|
return changes, nil
|
|
}
|
|
|
|
func (o *Community) editCategory(categoryID string, categoryName string, chatIDs []string) (*CommunityChanges, error) {
|
|
if o.config.CommunityDescription.Categories == nil {
|
|
o.config.CommunityDescription.Categories = make(map[string]*protobuf.CommunityCategory)
|
|
}
|
|
if _, ok := o.config.CommunityDescription.Categories[categoryID]; !ok {
|
|
return nil, ErrCategoryNotFound
|
|
}
|
|
|
|
for _, cid := range chatIDs {
|
|
c, exists := o.config.CommunityDescription.Chats[cid]
|
|
if !exists {
|
|
return nil, ErrChatNotFound
|
|
}
|
|
|
|
if exists && c.CategoryId != categoryID && c.CategoryId != "" {
|
|
return nil, ErrChatAlreadyAssigned
|
|
}
|
|
}
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
emptyCatLen := o.getCategoryChatCount("")
|
|
|
|
// remove any chat that might have been assigned before and now it's not part of the category
|
|
var chatsToRemove []string
|
|
for k, chat := range o.config.CommunityDescription.Chats {
|
|
if chat.CategoryId == categoryID {
|
|
found := false
|
|
for _, c := range chatIDs {
|
|
if k == c {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
chat.CategoryId = ""
|
|
chatsToRemove = append(chatsToRemove, k)
|
|
}
|
|
}
|
|
}
|
|
|
|
o.config.CommunityDescription.Categories[categoryID].Name = categoryName
|
|
|
|
for i, cid := range chatIDs {
|
|
o.config.CommunityDescription.Chats[cid].CategoryId = categoryID
|
|
o.config.CommunityDescription.Chats[cid].Position = int32(i)
|
|
}
|
|
|
|
for i, cid := range chatsToRemove {
|
|
o.config.CommunityDescription.Chats[cid].Position = int32(emptyCatLen + i)
|
|
changes.ChatsModified[cid] = &CommunityChatChanges{
|
|
MembersAdded: make(map[string]*protobuf.CommunityMember),
|
|
MembersRemoved: make(map[string]*protobuf.CommunityMember),
|
|
CategoryModified: "",
|
|
PositionModified: int(o.config.CommunityDescription.Chats[cid].Position),
|
|
}
|
|
}
|
|
|
|
o.SortCategoryChats(changes, "")
|
|
|
|
return changes, nil
|
|
}
|
|
|
|
func (o *Community) deleteCategory(categoryID string) (*CommunityChanges, error) {
|
|
if _, exists := o.config.CommunityDescription.Categories[categoryID]; !exists {
|
|
return nil, ErrCategoryNotFound
|
|
}
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
emptyCategoryChatCount := o.getCategoryChatCount("")
|
|
i := 0
|
|
for _, chat := range o.config.CommunityDescription.Chats {
|
|
if chat.CategoryId == categoryID {
|
|
i++
|
|
chat.CategoryId = ""
|
|
chat.Position = int32(emptyCategoryChatCount + i)
|
|
}
|
|
}
|
|
|
|
o.SortCategoryChats(changes, "")
|
|
|
|
delete(o.config.CommunityDescription.Categories, categoryID)
|
|
|
|
changes.CategoriesRemoved = append(changes.CategoriesRemoved, categoryID)
|
|
|
|
// Reorder
|
|
s := make(sortSlice, 0, len(o.config.CommunityDescription.Categories))
|
|
for _, cat := range o.config.CommunityDescription.Categories {
|
|
s = append(s, sorterHelperIdx{
|
|
pos: cat.Position,
|
|
catID: cat.CategoryId,
|
|
})
|
|
}
|
|
|
|
o.setModifiedCategories(changes, s)
|
|
|
|
return changes, nil
|
|
}
|
|
|
|
func (o *Community) reorderCategories(categoryID string, newPosition int) (*CommunityChanges, error) {
|
|
if _, exists := o.config.CommunityDescription.Categories[categoryID]; !exists {
|
|
return nil, ErrCategoryNotFound
|
|
}
|
|
|
|
if newPosition > 0 && newPosition >= len(o.config.CommunityDescription.Categories) {
|
|
newPosition = len(o.config.CommunityDescription.Categories) - 1
|
|
} else if newPosition < 0 {
|
|
newPosition = 0
|
|
}
|
|
|
|
category := o.config.CommunityDescription.Categories[categoryID]
|
|
if category.Position == int32(newPosition) {
|
|
return nil, ErrNoChangeInPosition
|
|
}
|
|
|
|
decrease := false
|
|
if category.Position > int32(newPosition) {
|
|
decrease = true
|
|
}
|
|
|
|
// Sorting the categories because maps are not guaranteed to keep order
|
|
s := make(sortSlice, 0, len(o.config.CommunityDescription.Categories))
|
|
for k, v := range o.config.CommunityDescription.Categories {
|
|
s = append(s, sorterHelperIdx{
|
|
pos: v.Position,
|
|
catID: k,
|
|
})
|
|
}
|
|
sort.Sort(s)
|
|
var communityCategories []*protobuf.CommunityCategory
|
|
for _, currCat := range s {
|
|
communityCategories = append(communityCategories, o.config.CommunityDescription.Categories[currCat.catID])
|
|
}
|
|
|
|
var sortedCategoryIDs []string
|
|
for _, v := range communityCategories {
|
|
if v != category && ((decrease && v.Position < int32(newPosition)) || (!decrease && v.Position <= int32(newPosition))) {
|
|
sortedCategoryIDs = append(sortedCategoryIDs, v.CategoryId)
|
|
}
|
|
}
|
|
|
|
sortedCategoryIDs = append(sortedCategoryIDs, categoryID)
|
|
|
|
for _, v := range communityCategories {
|
|
if v.CategoryId == categoryID || (decrease && v.Position < int32(newPosition)) || (!decrease && v.Position <= int32(newPosition)) {
|
|
continue
|
|
}
|
|
sortedCategoryIDs = append(sortedCategoryIDs, v.CategoryId)
|
|
}
|
|
|
|
s = make(sortSlice, 0, len(o.config.CommunityDescription.Categories))
|
|
for i, k := range sortedCategoryIDs {
|
|
s = append(s, sorterHelperIdx{
|
|
pos: int32(i),
|
|
catID: k,
|
|
})
|
|
}
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
o.setModifiedCategories(changes, s)
|
|
|
|
return changes, nil
|
|
}
|
|
|
|
func (o *Community) reorderChat(categoryID string, chatID string, newPosition int) (*CommunityChanges, error) {
|
|
if categoryID != "" {
|
|
if _, exists := o.config.CommunityDescription.Categories[categoryID]; !exists {
|
|
return nil, ErrCategoryNotFound
|
|
}
|
|
}
|
|
|
|
var chat *protobuf.CommunityChat
|
|
var exists bool
|
|
if chat, exists = o.config.CommunityDescription.Chats[chatID]; !exists {
|
|
return nil, ErrChatNotFound
|
|
}
|
|
|
|
oldCategoryID := chat.CategoryId
|
|
chat.CategoryId = categoryID
|
|
|
|
changes := o.emptyCommunityChanges()
|
|
|
|
o.SortCategoryChats(changes, oldCategoryID)
|
|
o.insertAndSort(changes, oldCategoryID, categoryID, chatID, chat, newPosition)
|
|
|
|
return changes, nil
|
|
}
|