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) CreateCategory(categoryID string, categoryName string, chatIDs []string) (*CommunityChanges, error) { o.mutex.Lock() defer o.mutex.Unlock() isOwner := o.IsOwner() isAdmin := o.IsAdmin() if !isOwner && !isAdmin { return nil, ErrNotAdmin } if isAdmin { err := o.addNewCommunityEvent(o.ToCreateCategoryCommunityEvent(categoryID, categoryName, chatIDs)) if err != nil { return nil, err } } changes, err := o.createCategory(categoryID, categoryName, chatIDs) if err != nil { return nil, err } if isOwner { o.increaseClock() } 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, } } return changes, nil } func (o *Community) EditCategory(categoryID string, categoryName string, chatIDs []string) (*CommunityChanges, error) { o.mutex.Lock() defer o.mutex.Unlock() isOwner := o.IsOwner() isAdmin := o.IsAdmin() if !isOwner && !isAdmin { return nil, ErrNotAdmin } if isAdmin { err := o.addNewCommunityEvent(o.ToEditCategoryCommunityEvent(categoryID, categoryName, chatIDs)) if err != nil { return nil, err } } changes, err := o.editCategory(categoryID, categoryName, chatIDs) if err != nil { return nil, err } if isOwner { o.increaseClock() } 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, } } return changes, nil } func (o *Community) ReorderCategories(categoryID string, newPosition int) (*CommunityChanges, error) { o.mutex.Lock() defer o.mutex.Unlock() isOwner := o.IsOwner() isAdmin := o.IsAdmin() if !isOwner && !isAdmin { return nil, ErrNotAdmin } if isAdmin { err := o.addNewCommunityEvent(o.ToReorderCategoryCommunityEvent(categoryID, newPosition)) if err != nil { return nil, err } } changes, err := o.reorderCategories(categoryID, newPosition) if err != nil { return nil, err } if isOwner { o.increaseClock() } 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() isOwner := o.IsOwner() isAdmin := o.IsAdmin() if !isOwner && !isAdmin { return nil, ErrNotAdmin } if isAdmin { err := o.addNewCommunityEvent(o.ToReorderChannelCommunityEvent(categoryID, chatID, newPosition)) if err != nil { return nil, err } } changes, err := o.reorderChat(categoryID, chatID, newPosition) if err != nil { return nil, err } if isOwner { o.increaseClock() } 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() isOwner := o.IsOwner() isAdmin := o.IsAdmin() if !isOwner && !isAdmin { return nil, ErrNotAdmin } if isAdmin { err := o.addNewCommunityEvent(o.ToDeleteCategoryCommunityEvent(categoryID)) if err != nil { return nil, err } } changes, err := o.deleteCategory(categoryID) if err != nil { return nil, err } if isOwner { o.increaseClock() } 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 }