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 }