feat(chat): implement new getChats API for the backend

Fixes #4878 but new issues will be created to split the implementation
This commit is contained in:
Jonathan Rainville 2022-03-23 09:21:57 -04:00
parent e26befb6c2
commit 666c865112
16 changed files with 356 additions and 258 deletions

View File

@ -1,4 +1,3 @@
const CHAT_SECTION_ID* = "chat"
const CHAT_SECTION_NAME* = "Chat"
const CHAT_SECTION_ICON* = "chat"

View File

@ -65,7 +65,7 @@ const DEFAULT_SHOW_DELETE_MESSAGE_WARNING = true
const LSS_KEY_DOWNLOAD_CHANNEL_MESSAGES_ENABLED* = "downloadChannelMessagesEnabled"
const DEFAULT_DOWNLOAD_CHANNEL_MESSAGES_ENABLED = false
const LSS_KEY_ACTIVE_SECTION* = "activeSection"
const DEFAULT_ACTIVE_SECTION = "chat"
const DEFAULT_ACTIVE_SECTION = ""
const LSS_KEY_SHOW_BROWSER_SELECTOR* = "showBrowserSelector"
const DEFAULT_SHOW_BROWSER_SELECTOR = true
const LSS_KEY_OPEN_LINKS_IN_STATUS* = "openLinksInStatus"

View File

@ -78,7 +78,10 @@ method convertToItems*(
let chatDetails = self.controller.getChatDetails(n.chatId)
# default section id is `Chat` section
let sectionId = if(chatDetails.communityId.len > 0): chatDetails.communityId else: conf.CHAT_SECTION_ID
let sectionId = if(chatDetails.communityId.len > 0):
chatDetails.communityId
else:
singletonInstance.userProfile.getPubKey()
if (n.message.id != ""):
# If there is a message in the Notification, transfer it to a MessageItem (QObject)

View File

@ -2,7 +2,7 @@ import Tables, chronicles
import io_interface
import ../../../global/app_signals
import ../../../global/app_sections_config as conf
import ../../../global/global_singleton
import ../../../../app_service/service/contacts/service as contact_service
import ../../../../app_service/service/chat/service as chat_service
import ../../../../app_service/service/community/service as community_service
@ -90,7 +90,7 @@ proc getJoinedCommunities*(self: Controller): seq[CommunityDto] =
proc getCommunityById*(self: Controller, communityId: string): CommunityDto =
return self.communityService.getCommunityById(communityId)
proc getAllChatsForCommunity*(self: Controller, communityId: string): seq[Chat] =
proc getAllChatsForCommunity*(self: Controller, communityId: string): seq[ChatDto] =
return self.communityService.getAllChats(communityId)
proc getChatDetailsForChatTypes*(self: Controller, types: seq[ChatType]): seq[ChatDto] =
@ -110,8 +110,8 @@ proc searchMessages*(self: Controller, searchTerm: string) =
if (self.searchSubLocation.len > 0):
chats.add(self.searchSubLocation)
elif (self.searchLocation.len > 0):
# If "Chat" is set for the meassgeSearchLocation that means we need to search in all chats from the chat section.
if (self.searchLocation != conf.CHAT_SECTION_ID):
# If user's pubkey is set for the meassgeSearchLocation that means we need to search in all chats from the personal chat section.
if (self.searchLocation != singletonInstance.userProfile.getPubKey()):
communities.add(self.searchLocation)
else:
let types = @[ChatType.OneToOne, ChatType.Public, ChatType.PrivateGroupChat]

View File

@ -67,8 +67,8 @@ method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant
proc buildLocationMenuForChat(self: Module): location_menu_item.Item =
var item = location_menu_item.initItem(conf.CHAT_SECTION_ID, SEARCH_MENU_LOCATION_CHAT_SECTION_NAME, "", "chat", "",
false)
var item = location_menu_item.initItem(singletonInstance.userProfile.getPubKey(),
SEARCH_MENU_LOCATION_CHAT_SECTION_NAME, "", "chat", "", false)
let types = @[ChatType.OneToOne, ChatType.Public, ChatType.PrivateGroupChat]
let displayedChats = self.controller.getChatDetailsForChatTypes(types)
@ -176,7 +176,8 @@ method onSearchMessagesDone*(self: Module, messages: seq[MessageDto]) =
channels.add(item)
# Add chats
if(self.controller.searchLocation().len == 0 or self.controller.searchLocation() == conf.CHAT_SECTION_ID and
if(self.controller.searchLocation().len == 0 or
self.controller.searchLocation() == singletonInstance.userProfile.getPubKey() and
self.controller.searchSubLocation().len == 0):
let types = @[ChatType.OneToOne, ChatType.Public, ChatType.PrivateGroupChat]
let displayedChats = self.controller.getChatDetailsForChatTypes(types)
@ -196,7 +197,7 @@ method onSearchMessagesDone*(self: Module, messages: seq[MessageDto]) =
let item = result_item.initItem(c.id, "", "", c.id, chatName, SEARCH_RESULT_CHATS_SECTION_NAME, chatImage,
c.color, "", "", chatImage, c.color, isIdenticon)
self.controller.addResultItemDetails(c.id, conf.CHAT_SECTION_ID, c.id)
self.controller.addResultItemDetails(c.id, singletonInstance.userProfile.getPubKey(), c.id)
items.add(item)
# Add channels in order as requested by the design
@ -222,7 +223,8 @@ method onSearchMessagesDone*(self: Module, messages: seq[MessageDto]) =
let item = result_item.initItem(m.id, m.text, $m.timestamp, m.`from`, senderName,
SEARCH_RESULT_MESSAGES_SECTION_NAME, senderImage, "", chatName, "", chatImage, chatDto.color, isIdenticon)
self.controller.addResultItemDetails(m.id, conf.CHAT_SECTION_ID, chatDto.id, m.id)
self.controller.addResultItemDetails(m.id, singletonInstance.userProfile.getPubKey(),
chatDto.id, m.id)
items.add(item)
else:
let community = self.controller.getCommunityById(chatDto.communityId)

View File

@ -207,7 +207,7 @@ proc getMyCommunity*(self: Controller): CommunityDto =
proc getCategories*(self: Controller, communityId: string): seq[Category] =
return self.communityService.getCategories(communityId)
proc getChats*(self: Controller, communityId: string, categoryId: string): seq[Chat] =
proc getChats*(self: Controller, communityId: string, categoryId: string): seq[ChatDto] =
return self.communityService.getChats(communityId, categoryId)
proc getChatDetails*(self: Controller, communityId, chatId: string): ChatDto =

View File

@ -148,10 +148,7 @@ proc buildCommunityUI(self: Module, events: EventEmitter,
mailserversService: mailservers_service.Service) =
var selectedItemId = ""
var selectedSubItemId = ""
let communities = self.controller.getJoinedCommunities()
for comm in communities:
if(self.controller.getMySectionId() != comm.id):
continue
let comm = self.controller.getMyCommunity()
# handle channels which don't belong to any category
let chats = self.controller.getChats(comm.id, "")
@ -635,7 +632,8 @@ method onContactDetailsUpdated*(self: Module, publicKey: string) =
self.view.contactRequestsModel().addItem(item)
self.updateParentBadgeNotifications()
singletonInstance.globalEvents.showNewContactRequestNotification("New Contact Request",
fmt "{contactDetails.displayName} added you as contact", conf.CHAT_SECTION_ID)
fmt "{contactDetails.displayName} added you as contact",
singletonInstance.userProfile.getPubKey())
let chatName = contactDetails.displayName
let chatImage = contactDetails.icon
@ -663,8 +661,10 @@ method onNewMessagesReceived*(self: Module, chatId: string, unviewedMessagesCoun
self.controller.getMySectionId(), chatId, m.id)
method onMeMentionedInEditedMessage*(self: Module, chatId: string, editedMessage : MessageDto) =
if(editedMessage.communityId.len == 0 and self.controller.getMySectionId() != conf.CHAT_SECTION_ID or
editedMessage.communityId.len > 0 and self.controller.getMySectionId() != editedMessage.communityId):
if((editedMessage.communityId.len == 0 and
self.controller.getMySectionId() != singletonInstance.userProfile.getPubKey()) or
(editedMessage.communityId.len > 0 and
self.controller.getMySectionId() != editedMessage.communityId)):
return
var (sectionHasUnreadMessages, sectionNotificationCount) = self.view.chatsModel().getAllNotifications()
self.updateBadgeNotifications(chatId, sectionHasUnreadMessages, sectionNotificationCount + 1)
@ -777,7 +777,7 @@ method addChatIfDontExist*(self: Module,
let sectionId = self.controller.getMySectionId()
if(belongsToCommunity and sectionId != chat.communityId or
not belongsToCommunity and sectionId != conf.CHAT_SECTION_ID):
not belongsToCommunity and sectionId != singletonInstance.userProfile.getPubKey()):
return
if self.doesCatOrChatExist(chat.id):

View File

@ -9,6 +9,7 @@ import ../../../global/global_singleton
import ../../../core/eventemitter
import ../../../../app_service/service/community/service as community_service
import ../../../../app_service/service/contacts/service as contacts_service
import ../../../../app_service/service/chat/dto/chat
export io_interface

View File

@ -171,7 +171,10 @@ proc init*(self: Controller) =
self.events.on(SIGNAL_MAKE_SECTION_CHAT_ACTIVE) do(e: Args):
var args = ActiveSectionChatArgs(e)
let sectionType = if args.sectionId == conf.CHAT_SECTION_ID: SectionType.Chat else: SectionType.Community
let sectionType = if args.sectionId == singletonInstance.userProfile.getPubKey():
SectionType.Chat
else:
SectionType.Community
self.setActiveSection(args.sectionId, sectionType)
self.events.on(SIGNAL_OS_NOTIFICATION_CLICKED) do(e: Args):
@ -194,6 +197,9 @@ proc isConnected*(self: Controller): bool =
proc getJoinedCommunities*(self: Controller): seq[CommunityDto] =
return self.communityService.getJoinedCommunities()
proc getChannelGroups*(self: Controller): seq[ChannelGroupDto] =
return self.chatService.getChannelGroups()
proc checkForStoringPassword*(self: Controller) =
# This proc is called once user is logged in irrespective he is logged in
# through the onboarding or login view.

View File

@ -62,8 +62,7 @@ type
view: View
viewVariant: QVariant
controller: Controller
chatSectionModule: chat_section_module.AccessInterface
communitySectionsModule: OrderedTable[string, chat_section_module.AccessInterface]
channelGroupModules: OrderedTable[string, chat_section_module.AccessInterface]
walletSectionModule: wallet_section_module.AccessInterface
browserSectionModule: browser_section_module.AccessInterface
profileSectionModule: profile_section_module.AccessInterface
@ -133,9 +132,7 @@ proc newModule*[T](
result.moduleLoaded = false
# Submodules
result.chatSectionModule = chat_section_module.newModule(result, events, conf.CHAT_SECTION_ID, false, settingsService,
contactsService, chatService, communityService, messageService, gifService, mailserversService)
result.communitySectionsModule = initOrderedTable[string, chat_section_module.AccessInterface]()
result.channelGroupModules = initOrderedTable[string, chat_section_module.AccessInterface]()
result.walletSectionModule = wallet_section_module.newModule(
result, events, tokenService,
transactionService, collectible_service, walletAccountService,
@ -158,14 +155,13 @@ proc newModule*[T](
result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService)
method delete*[T](self: Module[T]) =
self.chatSectionModule.delete
self.profileSectionModule.delete
self.stickersModule.delete
self.activityCenterModule.delete
self.communitiesModule.delete
for cModule in self.communitySectionsModule.values:
for cModule in self.channelGroupModules.values:
cModule.delete
self.communitySectionsModule.clear
self.channelGroupModules.clear
self.walletSectionModule.delete
self.browserSectionModule.delete
self.appSearchModule.delete
@ -175,32 +171,43 @@ method delete*[T](self: Module[T]) =
self.viewVariant.delete
self.controller.delete
proc createCommunityItem[T](self: Module[T], c: CommunityDto): SectionItem =
let (unviewedCount, mentionsCount) = self.controller.getNumOfNotificationsForCommunity(c.id)
proc createChannelGroupItem[T](self: Module[T], c: ChannelGroupDto): SectionItem =
let isCommunity = c.channelGroupType == ChannelGroupType.Community
var communityDetails: CommunityDto
var unviewedCount, mentionsCount: int
if (isCommunity):
(unviewedCount, mentionsCount) = self.controller.getNumOfNotificationsForCommunity(c.id)
communityDetails = self.controller.getCommunityById(c.id)
else:
let receivedContactRequests = self.controller.getContacts(ContactsGroup.IncomingPendingContactRequests)
(unviewedCount, mentionsCount) = self.controller.getNumOfNotificaitonsForChat()
mentionsCount = mentionsCount + receivedContactRequests.len
let hasNotification = unviewedCount > 0 or mentionsCount > 0
let notificationsCount = mentionsCount # we need to add here number of requests
let active = self.getActiveSectionId() == c.id # We must pass on if the current item section is currently active to keep that property as it is
result = initItem(
c.id,
SectionType.Community,
if isCommunity: SectionType.Community else: SectionType.Chat,
c.name,
c.admin,
c.description,
c.images.thumbnail,
icon = "",
icon = if (isCommunity): "" else: conf.CHAT_SECTION_ICON,
c.color,
hasNotification,
notificationsCount,
active,
enabled = singletonInstance.localAccountSensitiveSettings.getCommunitiesEnabled(),
c.joined,
c.canJoin,
enabled = (not isCommunity or
singletonInstance.localAccountSensitiveSettings.getCommunitiesEnabled()),
if (isCommunity): communityDetails.joined else: true,
if (isCommunity): communityDetails.canJoin else: true,
c.canManageUsers,
c.canRequestAccess,
c.isMember,
if (isCommunity): communityDetails.canRequestAccess else: true,
if (isCommunity): communityDetails.isMember else: true,
c.permissions.access,
c.permissions.ensOnly,
c.members.map(proc(member: Member): user_item.Item =
c.members.map(proc(member: ChatMember): user_item.Item =
let contactDetails = self.controller.getContactDetails(member.id)
result = user_item.initItem(
member.id,
@ -214,14 +221,14 @@ proc createCommunityItem[T](self: Module[T], c: CommunityDto): SectionItem =
contactDetails.isidenticon,
contactDetails.details.added
)),
c.pendingRequestsToJoin.map(x => pending_request_item.initItem(
if (isCommunity): communityDetails.pendingRequestsToJoin.map(x => pending_request_item.initItem(
x.id,
x.publicKey,
x.chatId,
x.communityId,
x.state,
x.our
))
)) else: @[]
)
method load*[T](
@ -239,15 +246,18 @@ method load*[T](
self.controller.init()
self.view.load()
# Create community modules here, since we don't know earlier how many joined communities we have.
let joinedCommunities = self.controller.getJoinedCommunities()
var activeSection: SectionItem
var activeSectionId = singletonInstance.localAccountSensitiveSettings.getActiveSection()
if (activeSectionId == ""):
activeSectionId = singletonInstance.userProfile.getPubKey()
for c in joinedCommunities:
self.communitySectionsModule[c.id] = chat_section_module.newModule(
let channelGroups = self.controller.getChannelGroups()
for channelGroup in channelGroups:
self.channelGroupModules[channelGroup.id] = chat_section_module.newModule(
self,
events,
c.id,
isCommunity = true,
channelGroup.id,
isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community,
settingsService,
contactsService,
chatService,
@ -256,35 +266,10 @@ method load*[T](
gifService,
mailserversService
)
var activeSection: SectionItem
var activeSectionId = singletonInstance.localAccountSensitiveSettings.getActiveSection()
# Chat Section
let receivedContactRequests = self.controller.getContacts(ContactsGroup.IncomingPendingContactRequests)
let (unviewedCount, mentionsCount) = self.controller.getNumOfNotificaitonsForChat()
let notificationsCount = mentionsCount + receivedContactRequests.len
let hasNotification = unviewedCount > 0 or notificationsCount > 0
let chatSectionItem = initItem(conf.CHAT_SECTION_ID, SectionType.Chat, conf.CHAT_SECTION_NAME,
amISectionAdmin = false,
description = "",
image = "",
conf.CHAT_SECTION_ICON,
color = "",
hasNotification,
notificationsCount,
active = false,
enabled = true)
self.view.model().addItem(chatSectionItem)
if(activeSectionId == chatSectionItem.id):
activeSection = chatSectionItem
# Community Section
for c in joinedCommunities:
let communitySectionItem = self.createCommunityItem(c)
self.view.model().addItem(communitySectionItem)
if(activeSectionId == communitySectionItem.id):
activeSection = communitySectionItem
let channelGroupItem = self.createChannelGroupItem(channelGroup)
self.view.model().addItem(channelGroupItem)
if(activeSectionId == channelGroupItem.id):
activeSection = channelGroupItem
# Wallet Section
let walletSectionItem = initItem(conf.WALLET_SECTION_ID, SectionType.Wallet, conf.WALLET_SECTION_NAME,
@ -349,9 +334,9 @@ method load*[T](
activeSection = profileSettingsSectionItem
# Load all sections
self.chatSectionModule.load(events, settingsService, contactsService, chatService, communityService, messageService, gifService, mailserversService)
for cModule in self.communitySectionsModule.values:
cModule.load(events, settingsService, contactsService, chatService, communityService, messageService, gifService, mailserversService)
for cModule in self.channelGroupModules.values:
cModule.load(events, settingsService, contactsService, chatService, communityService,
messageService, gifService, mailserversService)
self.browserSectionModule.load()
# self.nodeManagementSectionModule.load()
@ -372,10 +357,7 @@ proc checkIfModuleDidLoad [T](self: Module[T]) =
if self.moduleLoaded:
return
if(not self.chatSectionModule.isLoaded()):
return
for cModule in self.communitySectionsModule.values:
for cModule in self.channelGroupModules.values:
if(not cModule.isLoaded()):
return
@ -471,9 +453,7 @@ method setActiveSection*[T](self: Module[T], item: SectionItem) =
self.controller.setActiveSection(item.id, item.sectionType)
proc notifySubModulesAboutChange[T](self: Module[T], sectionId: string) =
self.chatSectionModule.onActiveSectionChange(sectionId)
for cModule in self.communitySectionsModule.values:
for cModule in self.channelGroupModules.values:
cModule.onActiveSectionChange(sectionId)
# If there is a need other section may be notified the same way from here...
@ -518,14 +498,14 @@ method setUserStatus*[T](self: Module[T], status: bool) =
self.controller.setUserStatus(status)
method getChatSectionModule*[T](self: Module[T]): QVariant =
return self.chatSectionModule.getModuleAsVariant()
return self.channelGroupModules[singletonInstance.userProfile.getPubKey()].getModuleAsVariant()
method getCommunitySectionModule*[T](self: Module[T], communityId: string): QVariant =
if(not self.communitySectionsModule.contains(communityId)):
if(not self.channelGroupModules.contains(communityId)):
echo "main-module, unexisting community key: ", communityId
return
return self.communitySectionsModule[communityId].getModuleAsVariant()
return self.channelGroupModules[communityId].getModuleAsVariant()
method rebuildChatSearchModel*[T](self: Module[T]) =
let transformItem = proc(item: chat_section_base_item.BaseItem, sectionId, sectionName: string): chat_search_item.Item =
@ -539,9 +519,10 @@ method rebuildChatSearchModel*[T](self: Module[T]) =
for subItem in item.subItems().items():
result.add(transformItem(subItem, sectionId, sectionName))
var items = transform(self.chatSectionModule.chatsModel().items(), conf.CHAT_SECTION_ID, conf.CHAT_SECTION_NAME)
for cId in self.communitySectionsModule.keys:
items.add(transform(self.communitySectionsModule[cId].chatsModel().items(), cId, self.view.model().getItemById(cId).name()))
var items: seq[chat_search_item.Item] = @[]
for cId in self.channelGroupModules.keys:
items.add(transform(self.channelGroupModules[cId].chatsModel().items(), cId,
self.view.model().getItemById(cId).name()))
self.view.chatSearchModel().setItems(items)
@ -580,9 +561,9 @@ method communityJoined*[T](
mailserversService: mailservers_service.Service,
) =
var firstCommunityJoined = false
if (self.communitySectionsModule.len == 0):
if (self.channelGroupModules.len == 1): # First one is personal chat section
firstCommunityJoined = true
self.communitySectionsModule[community.id] = chat_section_module.newModule(
self.channelGroupModules[community.id] = chat_section_module.newModule(
self,
events,
community.id,
@ -595,33 +576,37 @@ method communityJoined*[T](
gifService,
mailserversService
)
self.communitySectionsModule[community.id].load(events, settingsService, contactsService, chatService, communityService, messageService, gifService, mailserversService)
self.channelGroupModules[community.id].load(events, settingsService, contactsService, chatService,
communityService, messageService, gifService, mailserversService)
let communitySectionItem = self.createCommunityItem(community)
let channelGroup = community.toChannelGroupDto()
let communitySectionItem = self.createChannelGroupItem(channelGroup)
if (firstCommunityJoined):
# If there are no other communities, add the first community after the Chat section in the model so that the order is respected
self.view.model().addItem(communitySectionItem, self.view.model().getItemIndex(conf.CHAT_SECTION_ID) + 1)
self.view.model().addItem(communitySectionItem,
self.view.model().getItemIndex(singletonInstance.userProfile.getPubKey()) + 1)
else:
self.view.model().addItem(communitySectionItem)
self.setActiveSection(communitySectionItem)
method communityLeft*[T](self: Module[T], communityId: string) =
if(not self.communitySectionsModule.contains(communityId)):
if(not self.channelGroupModules.contains(communityId)):
echo "main-module, unexisting community key to leave: ", communityId
return
self.communitySectionsModule.del(communityId)
self.channelGroupModules.del(communityId)
self.view.model().removeItem(communityId)
if (self.controller.getActiveSectionId() == communityId):
let item = self.view.model().getItemById(conf.CHAT_SECTION_ID)
let item = self.view.model().getItemById(singletonInstance.userProfile.getPubKey())
self.setActiveSection(item)
method communityEdited*[T](
self: Module[T],
community: CommunityDto) =
self.view.editItem(self.createCommunityItem(community))
let channelGroup = community.toChannelGroupDto()
self.view.editItem(self.createChannelGroupItem(channelGroup))
method getContactDetailsAsJson*[T](self: Module[T], publicKey: string): string =
let contact = self.controller.getContact(publicKey)

View File

@ -1,6 +1,6 @@
{.used.}
import json, strformat
import json, strformat, strutils
include ../../../common/json_utils
@ -12,6 +12,26 @@ type ChatType* {.pure.}= enum
Profile = 4,
CommunityChat = 6
type ChannelGroupType* {.pure.}= enum
Unknown = "unknown",
Personal = "personal",
Community = "community"
type Category* = object
id*: string
name*: string
position*: int
type
Permission* = object
access*: int
ensOnly*: bool
type
Images* = object
thumbnail*: string
large*: string
type ChatMember* = object
id*: string
admin*: bool
@ -48,6 +68,24 @@ type ChatDto* = object
position*: int
categoryId*: string
highlight*: bool
permissions*: Permission
type ChannelGroupDto* = object
id*: string
channelGroupType*: ChannelGroupType
admin*: bool
verified*: bool
name*: string
ensName*: string
description*: string
chats*: seq[ChatDto]
categories*: seq[Category]
images*: Images
permissions*: Permission
members*: seq[ChatMember]
canManageUsers*: bool
color*: string
muted*: bool
proc `$`*(self: ChatDto): string =
result = fmt"""ChatDto(
@ -64,12 +102,14 @@ proc `$`*(self: ChatDto): string =
readMessagesAtClockValue: {self.readMessagesAtClockValue},
unviewedMessagesCount: {self.unviewedMessagesCount},
unviewedMentionsCount: {self.unviewedMentionsCount},
members: {self.members},
alias: {self.alias},
identicon: {self.identicon},
muted: {self.muted},
communityId: {self.communityId},
profile: {self.profile},
joined: {self.joined},
canPost: {self.canPost},
syncedTo: {self.syncedTo},
syncedFrom: {self.syncedFrom},
categoryId: {self.categoryId},
@ -77,12 +117,41 @@ proc `$`*(self: ChatDto): string =
highlight: {self.highlight}
)"""
proc toPermission*(jsonObj: JsonNode): Permission =
result = Permission()
discard jsonObj.getProp("access", result.access)
discard jsonObj.getProp("ens_only", result.ensOnly)
proc toImages*(jsonObj: JsonNode): Images =
result = Images()
var largeObj: JsonNode
if(jsonObj.getProp("large", largeObj)):
discard largeObj.getProp("uri", result.large)
var thumbnailObj: JsonNode
if(jsonObj.getProp("thumbnail", thumbnailObj)):
discard thumbnailObj.getProp("uri", result.thumbnail)
proc toCategory*(jsonObj: JsonNode): Category =
result = Category()
if (not jsonObj.getProp("category_id", result.id)):
discard jsonObj.getProp("id", result.id)
discard jsonObj.getProp("name", result.name)
discard jsonObj.getProp("position", result.position)
proc toChatMember*(jsonObj: JsonNode): ChatMember =
result = ChatMember()
discard jsonObj.getProp("id", result.id)
discard jsonObj.getProp("admin", result.admin)
discard jsonObj.getProp("joined", result.joined)
proc toChatMember(jsonObj: JsonNode, memberId: string): ChatMember =
# Mapping this DTO is not strightforward since only keys are used for id. We
# handle it a bit different.
result = jsonObj.toChatMember()
result.id = memberId
proc toChatDto*(jsonObj: JsonNode): ChatDto =
result = ChatDto()
discard jsonObj.getProp("id", result.id)
@ -94,13 +163,18 @@ proc toChatDto*(jsonObj: JsonNode): ChatDto =
discard jsonObj.getProp("timestamp", result.timestamp)
discard jsonObj.getProp("lastClockValue", result.lastClockValue)
discard jsonObj.getProp("deletedAtClockValue", result.deletedAtClockValue)
discard jsonObj.getProp("ReadMessagesAtClockValue", result.readMessagesAtClockValue)
discard jsonObj.getProp("readMessagesAtClockValue", result.readMessagesAtClockValue)
discard jsonObj.getProp("unviewedMessagesCount", result.unviewedMessagesCount)
discard jsonObj.getProp("unviewedMentionsCount", result.unviewedMentionsCount)
discard jsonObj.getProp("canPost", result.canPost)
discard jsonObj.getProp("alias", result.alias)
discard jsonObj.getProp("identicon", result.identicon)
discard jsonObj.getProp("muted", result.muted)
discard jsonObj.getProp("categoryId", result.categoryId)
if (result.categoryId == ""):
# Communities have `categoryID` and chats have `categoryId`
# This should be fixed in status-go, but would be a breaking change
discard jsonObj.getProp("categoryID", result.categoryId)
discard jsonObj.getProp("position", result.position)
discard jsonObj.getProp("communityId", result.communityId)
discard jsonObj.getProp("profile", result.profile)
@ -108,6 +182,9 @@ proc toChatDto*(jsonObj: JsonNode): ChatDto =
discard jsonObj.getProp("syncedTo", result.syncedTo)
discard jsonObj.getProp("syncedFrom", result.syncedFrom)
discard jsonObj.getProp("highlight", result.highlight)
var permissionObj: JsonNode
if(jsonObj.getProp("permissions", permissionObj)):
result.permissions = toPermission(permissionObj)
result.chatType = ChatType.Unknown
var chatTypeInt: int
@ -120,5 +197,54 @@ proc toChatDto*(jsonObj: JsonNode): ChatDto =
for memberObj in membersObj:
result.members.add(toChatMember(memberObj))
# Add community ID if needed
if (result.communityId != "" and not result.id.contains(result.communityId)):
result.id = result.communityId & result.id
proc toChannelGroupDto*(jsonObj: JsonNode): ChannelGroupDto =
result = ChannelGroupDto()
discard jsonObj.getProp("admin", result.admin)
discard jsonObj.getProp("verified", result.verified)
discard jsonObj.getProp("name", result.name)
discard jsonObj.getProp("description", result.description)
result.channelGroupType = ChannelGroupType.Unknown
var channelGroupTypeString: string
if (jsonObj.getProp("channelGroupType", channelGroupTypeString)):
result.channelGroupType = parseEnum[ChannelGroupType](channelGroupTypeString)
var chatsObj: JsonNode
if(jsonObj.getProp("chats", chatsObj)):
for _, chatObj in chatsObj:
result.chats.add(toChatDto(chatObj))
var categoriesObj: JsonNode
if(jsonObj.getProp("categories", categoriesObj)):
for _, categoryObj in categoriesObj:
result.categories.add(toCategory(categoryObj))
var imagesObj: JsonNode
if(jsonObj.getProp("images", imagesObj)):
result.images = toImages(imagesObj)
var permissionObj: JsonNode
if(jsonObj.getProp("permissions", permissionObj)):
result.permissions = toPermission(permissionObj)
var membersObj: JsonNode
if(jsonObj.getProp("members", membersObj) and membersObj.kind == JObject):
for memberId, memberObj in membersObj:
result.members.add(toChatMember(memberObj, memberId))
discard jsonObj.getProp("canManageUsers", result.canManageUsers)
discard jsonObj.getProp("color", result.color)
discard jsonObj.getProp("muted", result.muted)
# To parse Community chats to ChatDto, we need to add the commuity ID and type
proc toChatDto*(jsonObj: JsonNode, communityId: string): ChatDto =
result = jsonObj.toChatDto()
result.chatType = ChatType.CommunityChat
result.communityId = communityId
proc isPublicChat*(chatDto: ChatDto): bool =
return chatDto.chatType == ChatType.Public

View File

@ -90,6 +90,7 @@ QtObject:
type Service* = ref object of QObject
events: EventEmitter
chats: Table[string, ChatDto] # [chat_id, ChatDto]
channelGroups: OrderedTable[string, ChannelGroupDto] # [chatGroup_id, ChannelGroupDto]
contactService: contact_service.Service
proc delete*(self: Service) =
@ -117,23 +118,39 @@ QtObject:
self.updateOrAddChat(chatDto)
self.events.emit(SIGNAL_CHAT_UPDATE, ChatUpdateArgsNew(messages: receivedData.messages, chats: chats))
proc sortPersonnalChatAsFirst[T, D](x, y: (T, D)): int =
if (x[1].channelGroupType == Personal): return -1
if (y[1].channelGroupType == Personal): return 1
return 0
proc init*(self: Service) =
self.doConnect()
try:
let response = status_chat.getChats()
let chats = map(response.result.getElems(), proc(x: JsonNode): ChatDto = x.toChatDto())
var chats: seq[ChatDto] = @[]
for (sectionId, section) in response.result.pairs:
var channelGroup = section.toChannelGroupDto()
channelGroup.id = sectionId
self.channelGroups[sectionId] = channelGroup
for (chatId, chat) in section["chats"].pairs:
chats.add(chat.toChatDto())
# Make the personal channelGroup the first one
self.channelGroups.sort(sortPersonnalChatAsFirst[string, ChannelGroupDto], SortOrder.Ascending)
for chat in chats:
if chat.active and chat.chatType != chat_dto.ChatType.Unknown:
self.chats[chat.id] = chat
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
return
proc getChannelGroups*(self: Service): seq[ChannelGroupDto] =
return toSeq(self.channelGroups.values)
proc hasChannel*(self: Service, chatId: string): bool =
self.chats.hasKey(chatId)

View File

@ -1,41 +1,32 @@
{.used.}
import json, sequtils
import json, sequtils, sugar
import ../../../../backend/communities
include ../../../common/json_utils
type
Permission* = object
access*: int
ensOnly*: bool
import ../../chat/dto/chat
type
Images* = object
thumbnail*: string
large*: string
type Chat* = object
id*: string
name*: string
color*: string
emoji*: string
description*: string
#members*: seq[ChatMember] ???? It's always null and a question is why do we need it here within this context ????
permissions*: Permission
canPost*: bool
position*: int
categoryId*: string
type Category* = object
id*: string
name*: string
position*: int
CommunityMemberRoles* {.pure.} = enum
Unknown = 0,
All = 1,
ManagerUsers = 2
type Member* = object
id*: string
roles*: seq[int]
proc toMember*(jsonObj: JsonNode, memberId: string): Member =
# Mapping this DTO is not strightforward since only keys are used for id. We
# handle it a bit different.
result = Member()
result.id = memberId
var rolesObj: JsonNode
if(jsonObj.getProp("roles", rolesObj)):
for roleObj in rolesObj:
result.roles.add(roleObj.getInt)
type CommunityMembershipRequestDto* = object
id*: string
publicKey*: string
@ -52,7 +43,7 @@ type CommunityDto* = object
requestedAccessAt: int64
name*: string
description*: string
chats*: seq[Chat]
chats*: seq[ChatDto]
categories*: seq[Category]
images*: Images
permissions*: Permission
@ -66,53 +57,6 @@ type CommunityDto* = object
muted*: bool
pendingRequestsToJoin*: seq[CommunityMembershipRequestDto]
proc toPermission(jsonObj: JsonNode): Permission =
result = Permission()
discard jsonObj.getProp("access", result.access)
discard jsonObj.getProp("ens_only", result.ensOnly)
proc toImages(jsonObj: JsonNode): Images =
result = Images()
var largeObj: JsonNode
if(jsonObj.getProp("large", largeObj)):
discard largeObj.getProp("uri", result.large)
var thumbnailObj: JsonNode
if(jsonObj.getProp("thumbnail", thumbnailObj)):
discard thumbnailObj.getProp("uri", result.thumbnail)
proc toChat*(jsonObj: JsonNode): Chat =
result = Chat()
discard jsonObj.getProp("id", result.id)
discard jsonObj.getProp("name", result.name)
discard jsonObj.getProp("color", result.color)
discard jsonObj.getProp("emoji", result.emoji)
discard jsonObj.getProp("description", result.description)
var permissionObj: JsonNode
if(jsonObj.getProp("permissions", permissionObj)):
result.permissions = toPermission(permissionObj)
discard jsonObj.getProp("canPost", result.canPost)
discard jsonObj.getProp("position", result.position)
discard jsonObj.getProp("categoryID", result.categoryId)
proc toCategory*(jsonObj: JsonNode): Category =
result = Category()
if (not jsonObj.getProp("category_id", result.id)):
discard jsonObj.getProp("id", result.id)
discard jsonObj.getProp("name", result.name)
discard jsonObj.getProp("position", result.position)
proc toMember*(jsonObj: JsonNode, memberId: string): Member =
# Mapping this DTO is not strightforward since only keys are used for id. We
# handle it a bit different.
result = Member()
result.id = memberId
var rolesObj: JsonNode
if(jsonObj.getProp("roles", rolesObj)):
for roleObj in rolesObj:
result.roles.add(roleObj.getInt)
proc toCommunityDto*(jsonObj: JsonNode): CommunityDto =
result = CommunityDto()
discard jsonObj.getProp("id", result.id)
@ -126,7 +70,7 @@ proc toCommunityDto*(jsonObj: JsonNode): CommunityDto =
var chatsObj: JsonNode
if(jsonObj.getProp("chats", chatsObj)):
for _, chatObj in chatsObj:
result.chats.add(toChat(chatObj))
result.chats.add(chatObj.toChatDto(result.id))
var categoriesObj: JsonNode
if(jsonObj.getProp("categories", categoriesObj)):
@ -166,3 +110,33 @@ proc toCommunityMembershipRequestDto*(jsonObj: JsonNode): CommunityMembershipReq
proc parseCommunities*(response: RpcResponse[JsonNode]): seq[CommunityDto] =
result = map(response.result.getElems(),
proc(x: JsonNode): CommunityDto = x.toCommunityDto())
proc contains(arrayToSearch: seq[int], searched: int): bool =
for element in arrayToSearch:
if element == searched:
return true
return false
proc toChannelGroupDto*(communityDto: CommunityDto): ChannelGroupDto =
ChannelGroupDto(
id: communityDto.id,
channelGroupType: ChannelGroupType.Community,
name: communityDto.name,
images: communityDto.images,
chats: communityDto.chats,
categories: communityDto.categories,
# Community doesn't have an ensName yet. Add this when it is added in status-go
# ensName: communityDto.ensName,
admin: communityDto.admin,
verified: communityDto.verified,
description: communityDto.description,
color: communityDto.color,
permissions: communityDto.permissions,
members: communityDto.members.map(m => ChatMember(
id: m.id,
joined: true,
admin: m.roles.contains(CommunityMemberRoles.ManagerUsers.int)
)),
canManageUsers: communityDto.canManageUsers,
muted: communityDto.muted
)

View File

@ -144,22 +144,8 @@ QtObject:
self.events.emit(SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY, CommunityRequestArgs(communityRequest: membershipRequest))
proc mapChatToChatDto(chat: Chat, communityId: string): ChatDto =
result = ChatDto()
result.id = chat.id
result.communityId = communityId
result.name = chat.name
result.chatType = ChatType.CommunityChat
result.color = chat.color
result.emoji = chat.emoji
result.description = chat.description
result.canPost = chat.canPost
result.position = chat.position
result.categoryId = chat.categoryId
result.communityId = communityId
proc updateMissingFields(chatDto: var ChatDto, chat: Chat) =
# This proc sets fields of `chatDto` which are available only for comminity channels.
proc updateMissingFields(chatDto: var ChatDto, chat: ChatDto) =
# This proc sets fields of `chatDto` which are available only for community channels.
chatDto.position = chat.position
chatDto.canPost = chat.canPost
chatDto.categoryId = chat.categoryId
@ -169,7 +155,7 @@ QtObject:
if(chat.id == id):
return chat
proc findIndexById(id: string, chats: seq[Chat]): int =
proc findIndexById(id: string, chats: seq[ChatDto]): int =
var idx = -1
for chat in chats:
inc idx
@ -199,7 +185,6 @@ QtObject:
if (chat.categoryId == categoryId):
let fullChatId = community.id & chat.id
var chatDetails = self.chatService.getChatById(fullChatId)
chatDetails.updateMissingFields(chat)
result.add(chatDetails)
proc handleCommunityUpdates(self: Service, communities: seq[CommunityDto], updatedChats: seq[ChatDto]) =
@ -368,7 +353,7 @@ QtObject:
else:
result.sort(sortDesc[Category])
proc getChats*(self: Service, communityId: string, categoryId = "", order = SortOrder.Ascending): seq[Chat] =
proc getChats*(self: Service, communityId: string, categoryId = "", order = SortOrder.Ascending): seq[ChatDto] =
## By default returns chats which don't belong to any category, for passed `communityId`.
## If `categoryId` is set then only chats belonging to that category for passed `communityId` will be returned.
## Returned chats are sorted by position following set `order` parameter.
@ -383,11 +368,11 @@ QtObject:
result.add(chat)
if(order == SortOrder.Ascending):
result.sort(sortAsc[Chat])
result.sort(sortAsc[ChatDto])
else:
result.sort(sortDesc[Chat])
result.sort(sortDesc[ChatDto])
proc getAllChats*(self: Service, communityId: string, order = SortOrder.Ascending): seq[Chat] =
proc getAllChats*(self: Service, communityId: string, order = SortOrder.Ascending): seq[ChatDto] =
## Returns all chats belonging to the community with passed `communityId`, sorted by position.
## Returned chats are sorted by position following set `order` parameter.
if(not self.joinedCommunities.contains(communityId)):
@ -397,9 +382,9 @@ QtObject:
result = self.joinedCommunities[communityId].chats
if(order == SortOrder.Ascending):
result.sort(sortAsc[Chat])
result.sort(sortAsc[ChatDto])
else:
result.sort(sortDesc[Chat])
result.sort(sortDesc[ChatDto])
proc isUserMemberOfCommunity*(self: Service, communityId: string): bool =
if(not self.allCommunities.contains(communityId)):
@ -439,7 +424,7 @@ QtObject:
if (currentChat.id != ""):
# The chat service already knows that about that chat
continue
var chatDto = mapChatToChatDto(chat, communityId)
var chatDto = chat
chatDto.id = fullChatId
# TODO find a way to populate missing infos like the color
self.chatService.updateOrAddChat(chatDto)
@ -602,7 +587,7 @@ QtObject:
raise newException(RpcException, fmt"createCommunityChannel; there is no `chats` key in the response for community id: {communityId}")
for chatObj in chatsJArr:
var chatDto = chatObj.toChatDto()
var chatDto = chatObj.toChatDto(communityId)
self.chatService.updateOrAddChat(chatDto)
let data = CommunityChatArgs(chat: chatDto)
self.events.emit(SIGNAL_COMMUNITY_CHANNEL_CREATED, data)
@ -642,7 +627,7 @@ QtObject:
raise newException(RpcException, fmt"editCommunityChannel; there is no `chats` key in the response for community id: {communityId}")
for chatObj in chatsJArr:
var chatDto = chatObj.toChatDto()
var chatDto = chatObj.toChatDto(communityId)
self.chatService.updateOrAddChat(chatDto) # we have to update chats stored in the chat service.
@ -863,7 +848,7 @@ QtObject:
self.joinedCommunities[communityDto.id] = communityDto
for chatObj in chatsJArr:
let chatDto = chatObj.toChatDto()
let chatDto = chatObj.toChatDto(communityDto.id)
self.chatService.updateOrAddChat(chatDto) # we have to update chats stored in the chat service.
for chat in communityDto.chats:

View File

@ -33,7 +33,7 @@ proc saveChat*(
proc getChats*(): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* []
result = callPrivateRPC("chats".prefix, payload)
result = callPrivateRPC("chat_getChats", payload)
proc createPublicChat*(chatId: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let communityId = ""

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 16311512cbf66c9eeaf03194707faa19c9390649
Subproject commit c342c8bb1af9183795f652a41065244c0a0bbe09