feat: New APIs for search (#6564)
- Adding new API endpoints for search - Migrations to add appropriate indexes
This commit is contained in:
28
app/controllers/api/v1/accounts/search_controller.rb
Normal file
28
app/controllers/api/v1/accounts/search_controller.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class Api::V1::Accounts::SearchController < Api::V1::Accounts::BaseController
|
||||
def index
|
||||
@result = search('all')
|
||||
end
|
||||
|
||||
def conversations
|
||||
@result = search('Conversation')
|
||||
end
|
||||
|
||||
def contacts
|
||||
@result = search('Contact')
|
||||
end
|
||||
|
||||
def messages
|
||||
@result = search('Message')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search(search_type)
|
||||
SearchService.new(
|
||||
current_user: Current.user,
|
||||
current_account: Current.account,
|
||||
search_type: search_type,
|
||||
params: params
|
||||
).perform
|
||||
end
|
||||
end
|
||||
17
app/jobs/migration/add_search_indexes_job.rb
Normal file
17
app/jobs/migration/add_search_indexes_job.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# Delete migration and spec after 2 consecutive releases.
|
||||
class Migration::AddSearchIndexesJob < ApplicationJob
|
||||
queue_as :scheduled_jobs
|
||||
|
||||
def perform
|
||||
ActiveRecord::Migration[6.1].add_index(:messages, [:account_id, :inbox_id], algorithm: :concurrently)
|
||||
ActiveRecord::Migration[6.1].add_index(:messages, :content, using: 'gin', opclass: :gin_trgm_ops, algorithm: :concurrently)
|
||||
ActiveRecord::Migration[6.1].add_index(
|
||||
:contacts,
|
||||
[:name, :email, :phone_number, :identifier],
|
||||
using: 'gin',
|
||||
opclass: :gin_trgm_ops,
|
||||
name: 'index_contacts_on_name_email_phone_number_identifier',
|
||||
algorithm: :concurrently
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -16,10 +16,11 @@
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_contacts_on_account_id (account_id)
|
||||
# index_contacts_on_phone_number_and_account_id (phone_number,account_id)
|
||||
# uniq_email_per_account_contact (email,account_id) UNIQUE
|
||||
# uniq_identifier_per_account_contact (identifier,account_id) UNIQUE
|
||||
# index_contacts_on_account_id (account_id)
|
||||
# index_contacts_on_name_email_phone_number_identifier (name,email,phone_number,identifier) USING gin
|
||||
# index_contacts_on_phone_number_and_account_id (phone_number,account_id)
|
||||
# uniq_email_per_account_contact (email,account_id) UNIQUE
|
||||
# uniq_identifier_per_account_contact (identifier,account_id) UNIQUE
|
||||
#
|
||||
|
||||
class Contact < ApplicationRecord
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
# Indexes
|
||||
#
|
||||
# index_messages_on_account_id (account_id)
|
||||
# index_messages_on_account_id_and_inbox_id (account_id,inbox_id)
|
||||
# index_messages_on_additional_attributes_campaign_id (((additional_attributes -> 'campaign_id'::text))) USING gin
|
||||
# index_messages_on_content (content) USING gin
|
||||
# index_messages_on_conversation_id (conversation_id)
|
||||
# index_messages_on_inbox_id (inbox_id)
|
||||
# index_messages_on_sender_type_and_sender_id (sender_type,sender_id)
|
||||
|
||||
43
app/services/search_service.rb
Normal file
43
app/services/search_service.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
class SearchService
|
||||
pattr_initialize [:current_user!, :current_account!, :params!, :search_type!]
|
||||
|
||||
def perform
|
||||
case search_type
|
||||
when 'Message'
|
||||
{ messages: filter_messages }
|
||||
when 'Conversation'
|
||||
{ conversations: filter_conversations }
|
||||
when 'Contact'
|
||||
{ contacts: filter_contacts }
|
||||
else
|
||||
{ contacts: filter_contacts, messages: filter_messages, conversations: filter_conversations }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def accessable_inbox_ids
|
||||
@accessable_inbox_ids ||= @current_user.assigned_inboxes.pluck(:id)
|
||||
end
|
||||
|
||||
def filter_conversations
|
||||
@conversations = current_account.conversations.where(inbox_id: accessable_inbox_ids)
|
||||
.joins('INNER JOIN contacts ON conversations.contact_id = contacts.id')
|
||||
.where("cast(conversations.display_id as text) ILIKE :search OR contacts.name ILIKE :search OR contacts.email
|
||||
ILIKE :search OR contacts.phone_number ILIKE :search OR contacts.identifier ILIKE :search", search: "%#{params[:q]}%")
|
||||
.limit(10)
|
||||
end
|
||||
|
||||
def filter_messages
|
||||
@messages = current_account.messages.where(inbox_id: accessable_inbox_ids)
|
||||
.where('messages.content ILIKE :search', search: "%#{params[:q]}%")
|
||||
.where('created_at >= ?', 3.months.ago).limit(10)
|
||||
end
|
||||
|
||||
def filter_contacts
|
||||
@contacts = current_account.contacts.where(
|
||||
"name ILIKE :search OR email ILIKE :search OR phone_number
|
||||
ILIKE :search OR identifier ILIKE :search", search: "%#{params[:q]}%"
|
||||
).limit(10)
|
||||
end
|
||||
end
|
||||
5
app/views/api/v1/accounts/search/_agent.json.jbuilder
Normal file
5
app/views/api/v1/accounts/search/_agent.json.jbuilder
Normal file
@@ -0,0 +1,5 @@
|
||||
json.id agent.id
|
||||
json.available_name agent.available_name
|
||||
json.email agent.email
|
||||
json.name agent.name
|
||||
json.role agent.role
|
||||
5
app/views/api/v1/accounts/search/_contact.json.jbuilder
Normal file
5
app/views/api/v1/accounts/search/_contact.json.jbuilder
Normal file
@@ -0,0 +1,5 @@
|
||||
json.email contact.email
|
||||
json.id contact.id
|
||||
json.name contact.name
|
||||
json.phone_number contact.phone_number
|
||||
json.identifier contact.identifier
|
||||
4
app/views/api/v1/accounts/search/_inbox.json.jbuilder
Normal file
4
app/views/api/v1/accounts/search/_inbox.json.jbuilder
Normal file
@@ -0,0 +1,4 @@
|
||||
json.id inbox.id
|
||||
json.channel_id inbox.channel_id
|
||||
json.name inbox.name
|
||||
json.channel_type inbox.channel_type
|
||||
14
app/views/api/v1/accounts/search/_message.json.jbuilder
Normal file
14
app/views/api/v1/accounts/search/_message.json.jbuilder
Normal file
@@ -0,0 +1,14 @@
|
||||
json.id message.id
|
||||
json.content message.content
|
||||
json.message_type message.message_type_before_type_cast
|
||||
json.content_type message.content_type
|
||||
json.source_id message.source_id
|
||||
json.inbox_id message.inbox_id
|
||||
json.conversation_id message.conversation.try(:display_id)
|
||||
json.created_at message.created_at.to_i
|
||||
json.agent do
|
||||
json.partial! 'agent', formats: [:json], agent: message.conversation.try(:assignee) if message.conversation.try(:assignee).present?
|
||||
end
|
||||
json.inbox do
|
||||
json.partial! 'inbox', formats: [:json], inbox: message.inbox if message.inbox.present? && message.try(:inbox).present?
|
||||
end
|
||||
7
app/views/api/v1/accounts/search/contacts.json.jbuilder
Normal file
7
app/views/api/v1/accounts/search/contacts.json.jbuilder
Normal file
@@ -0,0 +1,7 @@
|
||||
json.payload do
|
||||
json.contacts do
|
||||
json.array! @result[:contacts] do |contact|
|
||||
json.partial! 'contact', formats: [:json], contact: contact
|
||||
end
|
||||
end
|
||||
end
|
||||
21
app/views/api/v1/accounts/search/conversations.json.jbuilder
Normal file
21
app/views/api/v1/accounts/search/conversations.json.jbuilder
Normal file
@@ -0,0 +1,21 @@
|
||||
json.payload do
|
||||
json.conversations do
|
||||
json.array! @result[:conversations] do |conversation|
|
||||
json.id conversation.display_id
|
||||
json.account_id conversation.account_id
|
||||
json.created_at conversation.created_at.to_i
|
||||
json.message do
|
||||
json.partial! 'message', formats: [:json], message: conversation.messages.try(:first)
|
||||
end
|
||||
json.contact do
|
||||
json.partial! 'contact', formats: [:json], contact: conversation.contact if conversation.try(:contact).present?
|
||||
end
|
||||
json.inbox do
|
||||
json.partial! 'inbox', formats: [:json], inbox: conversation.inbox if conversation.try(:inbox).present?
|
||||
end
|
||||
json.agent do
|
||||
json.partial! 'agent', formats: [:json], agent: conversation.assignee if conversation.try(:assignee).present?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
32
app/views/api/v1/accounts/search/index.json.jbuilder
Normal file
32
app/views/api/v1/accounts/search/index.json.jbuilder
Normal file
@@ -0,0 +1,32 @@
|
||||
json.payload do
|
||||
json.conversations do
|
||||
json.array! @result[:conversations] do |conversation|
|
||||
json.id conversation.display_id
|
||||
json.account_id conversation.account_id
|
||||
json.created_at conversation.created_at.to_i
|
||||
json.message do
|
||||
json.partial! 'message', formats: [:json], message: conversation.messages.try(:first)
|
||||
end
|
||||
json.contact do
|
||||
json.partial! 'contact', formats: [:json], contact: conversation.contact if conversation.try(:contact).present?
|
||||
end
|
||||
json.inbox do
|
||||
json.partial! 'inbox', formats: [:json], inbox: conversation.inbox if conversation.try(:inbox).present?
|
||||
end
|
||||
json.agent do
|
||||
json.partial! 'agent', formats: [:json], agent: conversation.assignee if conversation.try(:assignee).present?
|
||||
end
|
||||
end
|
||||
end
|
||||
json.contacts do
|
||||
json.array! @result[:contacts] do |contact|
|
||||
json.partial! 'contact', formats: [:json], contact: contact
|
||||
end
|
||||
end
|
||||
|
||||
json.messages do
|
||||
json.array! @result[:messages] do |message|
|
||||
json.partial! 'message', formats: [:json], message: message
|
||||
end
|
||||
end
|
||||
end
|
||||
7
app/views/api/v1/accounts/search/messages.json.jbuilder
Normal file
7
app/views/api/v1/accounts/search/messages.json.jbuilder
Normal file
@@ -0,0 +1,7 @@
|
||||
json.payload do
|
||||
json.messages do
|
||||
json.array! @result[:messages] do |message|
|
||||
json.partial! 'message', formats: [:json], message: message
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user