From b8959e3f66159b142fb6706b89f4ad8295944ee4 Mon Sep 17 00:00:00 2001 From: saledjenic <86303051+saledjenic@users.noreply.github.com> Date: Thu, 19 Aug 2021 21:47:03 +0200 Subject: [PATCH] feature(@status-go/chat): implement search results for communities, channels (#2303) AllMessagesFromChatsAndCommunitiesWhichMatchTerm method added which returns all messages which match the search term, if they belong to either any chat from the chatIds array or any channel of any community from communityIds array. This api point is necessary for desktop issue 2934. Fixes: #2934 --- protocol/message_persistence.go | 100 ++++++++++++++++++++++++++++++++ protocol/messenger.go | 4 ++ services/ext/api.go | 13 ++++- 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/protocol/message_persistence.go b/protocol/message_persistence.go index 9f5a00bc1..e6e878467 100644 --- a/protocol/message_persistence.go +++ b/protocol/message_persistence.go @@ -652,6 +652,106 @@ func (db sqlitePersistence) AllMessageByChatIDWhichMatchTerm(chatID string, sear return result, nil } +// AllMessagesFromChatsAndCommunitiesWhichMatchTerm returns all messages which match the search +// term, if they belong to either any chat from the chatIds array or any channel of any community +// from communityIds array. +// Ordering is accomplished using two concatenated values: ClockValue and ID. +// These two values are also used to compose a cursor which is returned to the result. +func (db sqlitePersistence) AllMessagesFromChatsAndCommunitiesWhichMatchTerm(communityIds []string, chatIds []string, searchTerm string, caseSensitive bool) ([]*common.Message, error) { + if searchTerm == "" { + return nil, fmt.Errorf("empty search term") + } + + chatsCond := "" + if len(chatIds) > 0 { + inVector := strings.Repeat("?, ", len(chatIds)-1) + "?" + chatsCond = `m1.local_chat_id IN (%s)` + chatsCond = fmt.Sprintf(chatsCond, inVector) + } + + communitiesCond := "" + if len(communityIds) > 0 { + inVector := strings.Repeat("?, ", len(communityIds)-1) + "?" + communitiesCond = `m1.local_chat_id IN (SELECT id FROM chats WHERE community_id IN (%s))` + communitiesCond = fmt.Sprintf(communitiesCond, inVector) + } + + searchCond := "" + if caseSensitive { + searchCond = "m1.text LIKE '%' || ? || '%'" + } else { + searchCond = "LOWER(m1.text) LIKE LOWER('%' || ? || '%')" + } + + finalCond := "AND %s AND %s" + if len(communityIds) > 0 && len(chatIds) > 0 { + finalCond = "AND (%s OR %s) AND %s" + finalCond = fmt.Sprintf(finalCond, chatsCond, communitiesCond, searchCond) + } else if len(chatIds) > 0 { + finalCond = fmt.Sprintf(finalCond, chatsCond, searchCond) + } else if len(communityIds) > 0 { + finalCond = fmt.Sprintf(finalCond, communitiesCond, searchCond) + } else { + return nil, fmt.Errorf("you must specify either community ids or chat ids or both") + } + + var parameters []string + parameters = append(parameters, chatIds...) + parameters = append(parameters, communityIds...) + parameters = append(parameters, searchTerm) + + idsArgs := make([]interface{}, 0, len(parameters)) + for _, param := range parameters { + idsArgs = append(idsArgs, param) + } + + allFields := db.tableUserMessagesAllFieldsJoin() + + finalQuery := fmt.Sprintf(` + SELECT + %s, + substr('0000000000000000000000000000000000000000000000000000000000000000' || m1.clock_value, -64, 64) || m1.id as cursor + FROM + user_messages m1 + LEFT JOIN + user_messages m2 + ON + m1.response_to = m2.id + + LEFT JOIN + contacts c + ON + + m1.source = c.id + WHERE + NOT(m1.hide) %s + ORDER BY cursor DESC + `, allFields, finalCond) + + rows, err := db.db.Query(finalQuery, idsArgs...) + + if err != nil { + return nil, err + } + defer rows.Close() + + var ( + result []*common.Message + ) + for rows.Next() { + var ( + message common.Message + cursor string + ) + if err := db.tableUserMessagesScanAllFields(rows, &message, &cursor); err != nil { + return nil, err + } + result = append(result, &message) + } + + return result, nil +} + // PinnedMessageByChatID returns all pinned messages for a given chatID in descending order. // Ordering is accomplished using two concatenated values: ClockValue and ID. // These two values are also used to compose a cursor which is returned to the result. diff --git a/protocol/messenger.go b/protocol/messenger.go index ebe2e0eed..aecb34498 100644 --- a/protocol/messenger.go +++ b/protocol/messenger.go @@ -3170,6 +3170,10 @@ func (m *Messenger) AllMessageByChatIDWhichMatchTerm(chatID string, searchTerm s return m.persistence.AllMessageByChatIDWhichMatchTerm(chatID, searchTerm, caseSensitive) } +func (m *Messenger) AllMessagesFromChatsAndCommunitiesWhichMatchTerm(communityIds []string, chatIds []string, searchTerm string, caseSensitive bool) ([]*common.Message, error) { + return m.persistence.AllMessagesFromChatsAndCommunitiesWhichMatchTerm(communityIds, chatIds, searchTerm, caseSensitive) +} + func (m *Messenger) SaveMessages(messages []*common.Message) error { return m.persistence.SaveMessages(messages) } diff --git a/services/ext/api.go b/services/ext/api.go index aaea947a8..29c27f5b8 100644 --- a/services/ext/api.go +++ b/services/ext/api.go @@ -490,7 +490,7 @@ func (api *PublicAPI) ChatMessages(chatID, cursor string, limit int) (*Applicati }, nil } -func (api *PublicAPI) AllChatMessagesWhichMatchTerm(chatID, searchTerm string, caseSensitive bool) (*ApplicationMessagesResponse, error) { +func (api *PublicAPI) AllMessagesFromChatWhichMatchTerm(chatID, searchTerm string, caseSensitive bool) (*ApplicationMessagesResponse, error) { messages, err := api.service.messenger.AllMessageByChatIDWhichMatchTerm(chatID, searchTerm, caseSensitive) if err != nil { return nil, err @@ -501,6 +501,17 @@ func (api *PublicAPI) AllChatMessagesWhichMatchTerm(chatID, searchTerm string, c }, nil } +func (api *PublicAPI) AllMessagesFromChatsAndCommunitiesWhichMatchTerm(communityIds []string, chatIds []string, searchTerm string, caseSensitive bool) (*ApplicationMessagesResponse, error) { + messages, err := api.service.messenger.AllMessagesFromChatsAndCommunitiesWhichMatchTerm(communityIds, chatIds, searchTerm, caseSensitive) + if err != nil { + return nil, err + } + + return &ApplicationMessagesResponse{ + Messages: messages, + }, nil +} + func (api *PublicAPI) ChatPinnedMessages(chatID, cursor string, limit int) (*ApplicationPinnedMessagesResponse, error) { pinnedMessages, cursor, err := api.service.messenger.PinnedMessageByChatID(chatID, cursor, limit) if err != nil {