github.com/status-im/status-go@v1.1.0/protocol/communities/community_categories.go (about)

     1  package communities
     2  
     3  import (
     4  	"sort"
     5  
     6  	"github.com/status-im/status-go/protocol/protobuf"
     7  )
     8  
     9  func (o *Community) ChatsByCategoryID(categoryID string) []string {
    10  	o.mutex.Lock()
    11  	defer o.mutex.Unlock()
    12  	var chatIDs []string
    13  	if o.config == nil || o.config.CommunityDescription == nil {
    14  		return chatIDs
    15  	}
    16  
    17  	for chatID, chat := range o.config.CommunityDescription.Chats {
    18  		if chat.CategoryId == categoryID {
    19  			chatIDs = append(chatIDs, chatID)
    20  		}
    21  	}
    22  	return chatIDs
    23  }
    24  
    25  func (o *Community) CommunityChatsIDs() []string {
    26  	o.mutex.Lock()
    27  	defer o.mutex.Unlock()
    28  	var chatIDs []string
    29  	if o.config == nil || o.config.CommunityDescription == nil {
    30  		return chatIDs
    31  	}
    32  
    33  	for chatID := range o.config.CommunityDescription.Chats {
    34  		chatIDs = append(chatIDs, chatID)
    35  	}
    36  	return chatIDs
    37  }
    38  
    39  func (o *Community) CreateCategory(categoryID string, categoryName string, chatIDs []string) (*CommunityChanges, error) {
    40  	o.mutex.Lock()
    41  	defer o.mutex.Unlock()
    42  
    43  	if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CATEGORY_CREATE)) {
    44  		return nil, ErrNotAuthorized
    45  	}
    46  
    47  	changes, err := o.createCategory(categoryID, categoryName, chatIDs)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	changes.CategoriesAdded[categoryID] = o.config.CommunityDescription.Categories[categoryID]
    53  	for i, cid := range chatIDs {
    54  		changes.ChatsModified[cid] = &CommunityChatChanges{
    55  			MembersAdded:     make(map[string]*protobuf.CommunityMember),
    56  			MembersRemoved:   make(map[string]*protobuf.CommunityMember),
    57  			CategoryModified: categoryID,
    58  			PositionModified: i,
    59  		}
    60  	}
    61  
    62  	if o.IsControlNode() {
    63  		o.increaseClock()
    64  	} else {
    65  		err := o.addNewCommunityEvent(o.ToCreateCategoryCommunityEvent(categoryID, categoryName, chatIDs))
    66  		if err != nil {
    67  			return nil, err
    68  		}
    69  	}
    70  
    71  	return changes, nil
    72  }
    73  
    74  func (o *Community) EditCategory(categoryID string, categoryName string, chatIDs []string) (*CommunityChanges, error) {
    75  	o.mutex.Lock()
    76  	defer o.mutex.Unlock()
    77  
    78  	if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CATEGORY_EDIT)) {
    79  		return nil, ErrNotAuthorized
    80  	}
    81  
    82  	changes, err := o.editCategory(categoryID, categoryName, chatIDs)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	changes.CategoriesModified[categoryID] = o.config.CommunityDescription.Categories[categoryID]
    88  	for i, cid := range chatIDs {
    89  		changes.ChatsModified[cid] = &CommunityChatChanges{
    90  			MembersAdded:     make(map[string]*protobuf.CommunityMember),
    91  			MembersRemoved:   make(map[string]*protobuf.CommunityMember),
    92  			CategoryModified: categoryID,
    93  			PositionModified: i,
    94  		}
    95  	}
    96  
    97  	if o.IsControlNode() {
    98  		o.increaseClock()
    99  	} else {
   100  		err := o.addNewCommunityEvent(o.ToEditCategoryCommunityEvent(categoryID, categoryName, chatIDs))
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  	}
   105  
   106  	return changes, nil
   107  }
   108  
   109  func (o *Community) ReorderCategories(categoryID string, newPosition int) (*CommunityChanges, error) {
   110  	o.mutex.Lock()
   111  	defer o.mutex.Unlock()
   112  
   113  	if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CATEGORY_REORDER)) {
   114  		return nil, ErrNotAuthorized
   115  	}
   116  
   117  	changes, err := o.reorderCategories(categoryID, newPosition)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	if o.IsControlNode() {
   123  		o.increaseClock()
   124  	} else {
   125  		err := o.addNewCommunityEvent(o.ToReorderCategoryCommunityEvent(categoryID, newPosition))
   126  		if err != nil {
   127  			return nil, err
   128  		}
   129  	}
   130  
   131  	return changes, nil
   132  }
   133  
   134  func (o *Community) setModifiedCategories(changes *CommunityChanges, s sortSlice) {
   135  	sort.Sort(s)
   136  	for i, catSortHelper := range s {
   137  		if o.config.CommunityDescription.Categories[catSortHelper.catID].Position != int32(i) {
   138  			o.config.CommunityDescription.Categories[catSortHelper.catID].Position = int32(i)
   139  			changes.CategoriesModified[catSortHelper.catID] = o.config.CommunityDescription.Categories[catSortHelper.catID]
   140  		}
   141  	}
   142  }
   143  
   144  func (o *Community) ReorderChat(categoryID string, chatID string, newPosition int) (*CommunityChanges, error) {
   145  	o.mutex.Lock()
   146  	defer o.mutex.Unlock()
   147  
   148  	if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CHANNEL_REORDER)) {
   149  		return nil, ErrNotAuthorized
   150  	}
   151  
   152  	changes, err := o.reorderChat(categoryID, chatID, newPosition)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	if o.IsControlNode() {
   158  		o.increaseClock()
   159  	} else {
   160  		err := o.addNewCommunityEvent(o.ToReorderChannelCommunityEvent(categoryID, chatID, newPosition))
   161  		if err != nil {
   162  			return nil, err
   163  		}
   164  	}
   165  
   166  	return changes, nil
   167  }
   168  
   169  func (o *Community) SortCategoryChats(changes *CommunityChanges, categoryID string) {
   170  	var catChats []string
   171  	for k, c := range o.config.CommunityDescription.Chats {
   172  		if c.CategoryId == categoryID {
   173  			catChats = append(catChats, k)
   174  		}
   175  	}
   176  
   177  	sortedChats := make(sortSlice, 0, len(catChats))
   178  	for _, k := range catChats {
   179  		sortedChats = append(sortedChats, sorterHelperIdx{
   180  			pos:    o.config.CommunityDescription.Chats[k].Position,
   181  			chatID: k,
   182  		})
   183  	}
   184  
   185  	sort.Sort(sortedChats)
   186  
   187  	for i, chatSortHelper := range sortedChats {
   188  		if o.config.CommunityDescription.Chats[chatSortHelper.chatID].Position != int32(i) {
   189  			o.config.CommunityDescription.Chats[chatSortHelper.chatID].Position = int32(i)
   190  			if changes.ChatsModified[chatSortHelper.chatID] != nil {
   191  				changes.ChatsModified[chatSortHelper.chatID].PositionModified = i
   192  			} else {
   193  				changes.ChatsModified[chatSortHelper.chatID] = &CommunityChatChanges{
   194  					PositionModified: i,
   195  					MembersAdded:     make(map[string]*protobuf.CommunityMember),
   196  					MembersRemoved:   make(map[string]*protobuf.CommunityMember),
   197  				}
   198  			}
   199  		}
   200  	}
   201  }
   202  
   203  func (o *Community) insertAndSort(changes *CommunityChanges, oldCategoryID string, categoryID string, chatID string, chat *protobuf.CommunityChat, newPosition int) {
   204  	// We sort the chats here because maps are not guaranteed to keep order
   205  	var catChats []string
   206  	sortedChats := make(sortSlice, 0, len(o.config.CommunityDescription.Chats))
   207  	for k, v := range o.config.CommunityDescription.Chats {
   208  		sortedChats = append(sortedChats, sorterHelperIdx{
   209  			pos:    v.Position,
   210  			chatID: k,
   211  		})
   212  	}
   213  	sort.Sort(sortedChats)
   214  	for _, k := range sortedChats {
   215  		if o.config.CommunityDescription.Chats[k.chatID].CategoryId == categoryID {
   216  			catChats = append(catChats, k.chatID)
   217  		}
   218  	}
   219  
   220  	if newPosition > 0 && newPosition >= len(catChats) {
   221  		newPosition = len(catChats) - 1
   222  	} else if newPosition < 0 {
   223  		newPosition = 0
   224  	}
   225  
   226  	decrease := false
   227  	if chat.Position > int32(newPosition) {
   228  		decrease = true
   229  	}
   230  
   231  	for k, v := range o.config.CommunityDescription.Chats {
   232  		if k != chatID && newPosition == int(v.Position) && v.CategoryId == categoryID {
   233  			if oldCategoryID == categoryID {
   234  				if decrease {
   235  					v.Position++
   236  				} else {
   237  					v.Position--
   238  				}
   239  			} else {
   240  				v.Position++
   241  			}
   242  		}
   243  	}
   244  
   245  	idx := -1
   246  	currChatID := ""
   247  	var sortedChatIDs []string
   248  	for i, k := range catChats {
   249  		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))) {
   250  			sortedChatIDs = append(sortedChatIDs, k)
   251  		} else {
   252  			if o.config.CommunityDescription.Chats[k] == chat {
   253  				idx = i
   254  				currChatID = k
   255  			}
   256  		}
   257  	}
   258  
   259  	sortedChatIDs = append(sortedChatIDs, currChatID)
   260  
   261  	for i, k := range catChats {
   262  		if i == idx || (decrease && o.config.CommunityDescription.Chats[k].Position < int32(newPosition)) || (!decrease && o.config.CommunityDescription.Chats[k].Position <= int32(newPosition)) {
   263  			continue
   264  		}
   265  		sortedChatIDs = append(sortedChatIDs, k)
   266  	}
   267  
   268  	for i, sortedChatID := range sortedChatIDs {
   269  		if o.config.CommunityDescription.Chats[sortedChatID].Position != int32(i) {
   270  			o.config.CommunityDescription.Chats[sortedChatID].Position = int32(i)
   271  			if changes.ChatsModified[sortedChatID] != nil {
   272  				changes.ChatsModified[sortedChatID].PositionModified = i
   273  			} else {
   274  				changes.ChatsModified[sortedChatID] = &CommunityChatChanges{
   275  					MembersAdded:     make(map[string]*protobuf.CommunityMember),
   276  					MembersRemoved:   make(map[string]*protobuf.CommunityMember),
   277  					PositionModified: i,
   278  				}
   279  			}
   280  		}
   281  	}
   282  }
   283  
   284  func (o *Community) getCategoryChatCount(categoryID string) int {
   285  	result := 0
   286  	for _, chat := range o.config.CommunityDescription.Chats {
   287  		if chat.CategoryId == categoryID {
   288  			result = result + 1
   289  		}
   290  	}
   291  	return result
   292  }
   293  
   294  func (o *Community) DeleteCategory(categoryID string) (*CommunityChanges, error) {
   295  	o.mutex.Lock()
   296  	defer o.mutex.Unlock()
   297  
   298  	if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_CATEGORY_DELETE)) {
   299  		return nil, ErrNotAuthorized
   300  	}
   301  
   302  	changes, err := o.deleteCategory(categoryID)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  
   307  	if o.IsControlNode() {
   308  		o.increaseClock()
   309  	} else {
   310  		err := o.addNewCommunityEvent(o.ToDeleteCategoryCommunityEvent(categoryID))
   311  		if err != nil {
   312  			return nil, err
   313  		}
   314  	}
   315  
   316  	return changes, nil
   317  }
   318  
   319  func (o *Community) createCategory(categoryID string, categoryName string, chatIDs []string) (*CommunityChanges, error) {
   320  	if o.config.CommunityDescription.Categories == nil {
   321  		o.config.CommunityDescription.Categories = make(map[string]*protobuf.CommunityCategory)
   322  	}
   323  	if _, ok := o.config.CommunityDescription.Categories[categoryID]; ok {
   324  		return nil, ErrCategoryAlreadyExists
   325  	}
   326  
   327  	for _, cid := range chatIDs {
   328  		c, exists := o.config.CommunityDescription.Chats[cid]
   329  		if !exists {
   330  			return nil, ErrChatNotFound
   331  		}
   332  
   333  		if exists && c.CategoryId != categoryID && c.CategoryId != "" {
   334  			return nil, ErrChatAlreadyAssigned
   335  		}
   336  	}
   337  
   338  	changes := o.emptyCommunityChanges()
   339  
   340  	o.config.CommunityDescription.Categories[categoryID] = &protobuf.CommunityCategory{
   341  		CategoryId: categoryID,
   342  		Name:       categoryName,
   343  		Position:   int32(len(o.config.CommunityDescription.Categories)),
   344  	}
   345  
   346  	for i, cid := range chatIDs {
   347  		o.config.CommunityDescription.Chats[cid].CategoryId = categoryID
   348  		o.config.CommunityDescription.Chats[cid].Position = int32(i)
   349  	}
   350  
   351  	o.SortCategoryChats(changes, "")
   352  
   353  	return changes, nil
   354  }
   355  
   356  func (o *Community) editCategory(categoryID string, categoryName string, chatIDs []string) (*CommunityChanges, error) {
   357  	if o.config.CommunityDescription.Categories == nil {
   358  		o.config.CommunityDescription.Categories = make(map[string]*protobuf.CommunityCategory)
   359  	}
   360  	if _, ok := o.config.CommunityDescription.Categories[categoryID]; !ok {
   361  		return nil, ErrCategoryNotFound
   362  	}
   363  
   364  	for _, cid := range chatIDs {
   365  		c, exists := o.config.CommunityDescription.Chats[cid]
   366  		if !exists {
   367  			return nil, ErrChatNotFound
   368  		}
   369  
   370  		if exists && c.CategoryId != categoryID && c.CategoryId != "" {
   371  			return nil, ErrChatAlreadyAssigned
   372  		}
   373  	}
   374  
   375  	changes := o.emptyCommunityChanges()
   376  
   377  	emptyCatLen := o.getCategoryChatCount("")
   378  
   379  	// remove any chat that might have been assigned before and now it's not part of the category
   380  	var chatsToRemove []string
   381  	for k, chat := range o.config.CommunityDescription.Chats {
   382  		if chat.CategoryId == categoryID {
   383  			found := false
   384  			for _, c := range chatIDs {
   385  				if k == c {
   386  					found = true
   387  				}
   388  			}
   389  			if !found {
   390  				chat.CategoryId = ""
   391  				chatsToRemove = append(chatsToRemove, k)
   392  			}
   393  		}
   394  	}
   395  
   396  	o.config.CommunityDescription.Categories[categoryID].Name = categoryName
   397  
   398  	for i, cid := range chatIDs {
   399  		o.config.CommunityDescription.Chats[cid].CategoryId = categoryID
   400  		o.config.CommunityDescription.Chats[cid].Position = int32(i)
   401  	}
   402  
   403  	for i, cid := range chatsToRemove {
   404  		o.config.CommunityDescription.Chats[cid].Position = int32(emptyCatLen + i)
   405  		changes.ChatsModified[cid] = &CommunityChatChanges{
   406  			MembersAdded:     make(map[string]*protobuf.CommunityMember),
   407  			MembersRemoved:   make(map[string]*protobuf.CommunityMember),
   408  			CategoryModified: "",
   409  			PositionModified: int(o.config.CommunityDescription.Chats[cid].Position),
   410  		}
   411  	}
   412  
   413  	o.SortCategoryChats(changes, "")
   414  
   415  	return changes, nil
   416  }
   417  
   418  func (o *Community) deleteCategory(categoryID string) (*CommunityChanges, error) {
   419  	if _, exists := o.config.CommunityDescription.Categories[categoryID]; !exists {
   420  		return nil, ErrCategoryNotFound
   421  	}
   422  
   423  	changes := o.emptyCommunityChanges()
   424  
   425  	emptyCategoryChatCount := o.getCategoryChatCount("")
   426  	i := 0
   427  	for _, chat := range o.config.CommunityDescription.Chats {
   428  		if chat.CategoryId == categoryID {
   429  			i++
   430  			chat.CategoryId = ""
   431  			chat.Position = int32(emptyCategoryChatCount + i)
   432  		}
   433  	}
   434  
   435  	o.SortCategoryChats(changes, "")
   436  
   437  	delete(o.config.CommunityDescription.Categories, categoryID)
   438  
   439  	changes.CategoriesRemoved = append(changes.CategoriesRemoved, categoryID)
   440  
   441  	// Reorder
   442  	s := make(sortSlice, 0, len(o.config.CommunityDescription.Categories))
   443  	for _, cat := range o.config.CommunityDescription.Categories {
   444  		s = append(s, sorterHelperIdx{
   445  			pos:   cat.Position,
   446  			catID: cat.CategoryId,
   447  		})
   448  	}
   449  
   450  	o.setModifiedCategories(changes, s)
   451  
   452  	return changes, nil
   453  }
   454  
   455  func (o *Community) reorderCategories(categoryID string, newPosition int) (*CommunityChanges, error) {
   456  	if _, exists := o.config.CommunityDescription.Categories[categoryID]; !exists {
   457  		return nil, ErrCategoryNotFound
   458  	}
   459  
   460  	if newPosition > 0 && newPosition >= len(o.config.CommunityDescription.Categories) {
   461  		newPosition = len(o.config.CommunityDescription.Categories) - 1
   462  	} else if newPosition < 0 {
   463  		newPosition = 0
   464  	}
   465  
   466  	category := o.config.CommunityDescription.Categories[categoryID]
   467  	if category.Position == int32(newPosition) {
   468  		return nil, ErrNoChangeInPosition
   469  	}
   470  
   471  	decrease := false
   472  	if category.Position > int32(newPosition) {
   473  		decrease = true
   474  	}
   475  
   476  	// Sorting the categories because maps are not guaranteed to keep order
   477  	s := make(sortSlice, 0, len(o.config.CommunityDescription.Categories))
   478  	for k, v := range o.config.CommunityDescription.Categories {
   479  		s = append(s, sorterHelperIdx{
   480  			pos:   v.Position,
   481  			catID: k,
   482  		})
   483  	}
   484  	sort.Sort(s)
   485  	var communityCategories []*protobuf.CommunityCategory
   486  	for _, currCat := range s {
   487  		communityCategories = append(communityCategories, o.config.CommunityDescription.Categories[currCat.catID])
   488  	}
   489  
   490  	var sortedCategoryIDs []string
   491  	for _, v := range communityCategories {
   492  		if v != category && ((decrease && v.Position < int32(newPosition)) || (!decrease && v.Position <= int32(newPosition))) {
   493  			sortedCategoryIDs = append(sortedCategoryIDs, v.CategoryId)
   494  		}
   495  	}
   496  
   497  	sortedCategoryIDs = append(sortedCategoryIDs, categoryID)
   498  
   499  	for _, v := range communityCategories {
   500  		if v.CategoryId == categoryID || (decrease && v.Position < int32(newPosition)) || (!decrease && v.Position <= int32(newPosition)) {
   501  			continue
   502  		}
   503  		sortedCategoryIDs = append(sortedCategoryIDs, v.CategoryId)
   504  	}
   505  
   506  	s = make(sortSlice, 0, len(o.config.CommunityDescription.Categories))
   507  	for i, k := range sortedCategoryIDs {
   508  		s = append(s, sorterHelperIdx{
   509  			pos:   int32(i),
   510  			catID: k,
   511  		})
   512  	}
   513  
   514  	changes := o.emptyCommunityChanges()
   515  
   516  	o.setModifiedCategories(changes, s)
   517  
   518  	return changes, nil
   519  }
   520  
   521  func (o *Community) reorderChat(categoryID string, chatID string, newPosition int) (*CommunityChanges, error) {
   522  	if categoryID != "" {
   523  		if _, exists := o.config.CommunityDescription.Categories[categoryID]; !exists {
   524  			return nil, ErrCategoryNotFound
   525  		}
   526  	}
   527  
   528  	var chat *protobuf.CommunityChat
   529  	var exists bool
   530  	if chat, exists = o.config.CommunityDescription.Chats[chatID]; !exists {
   531  		return nil, ErrChatNotFound
   532  	}
   533  
   534  	oldCategoryID := chat.CategoryId
   535  	chat.CategoryId = categoryID
   536  
   537  	changes := o.emptyCommunityChanges()
   538  
   539  	o.SortCategoryChats(changes, oldCategoryID)
   540  	o.insertAndSort(changes, oldCategoryID, categoryID, chatID, chat, newPosition)
   541  
   542  	return changes, nil
   543  }