diff --git a/app/controllers/api/v1/accounts/contacts/attachments_controller.rb b/app/controllers/api/v1/accounts/contacts/attachments_controller.rb new file mode 100644 index 000000000..761f00130 --- /dev/null +++ b/app/controllers/api/v1/accounts/contacts/attachments_controller.rb @@ -0,0 +1,18 @@ +class Api::V1::Accounts::Contacts::AttachmentsController < Api::V1::Accounts::Contacts::BaseController + RESULTS_PER_PAGE = 100 + + def index + conversations = Conversations::PermissionFilterService.new( + Current.account.conversations.where(contact_id: @contact.id), + Current.user, + Current.account + ).perform + + @attachments = Attachment.where(message_id: Message.where(conversation_id: conversations).select(:id)) + .includes({ file_attachment: :blob }, message: [:conversation, :inbox, { sender: { avatar_attachment: :blob } }]) + .order(created_at: :desc) + .page(params[:page]) + .per(RESULTS_PER_PAGE) + @attachments_count = @attachments.total_count + end +end diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 6159f804d..6cc77cd54 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -28,7 +28,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro def attachments @attachments_count = @conversation.attachments.count @attachments = @conversation.attachments - .includes(:message) + .includes({ file_attachment: :blob }, message: [:inbox, { sender: { avatar_attachment: :blob } }]) .order(created_at: :desc) .page(attachment_params[:page]) .per(ATTACHMENT_RESULTS_PER_PAGE) diff --git a/app/views/api/v1/accounts/contacts/attachments/index.json.jbuilder b/app/views/api/v1/accounts/contacts/attachments/index.json.jbuilder new file mode 100644 index 000000000..9dfcb3b66 --- /dev/null +++ b/app/views/api/v1/accounts/contacts/attachments/index.json.jbuilder @@ -0,0 +1,8 @@ +json.meta do + json.total_count @attachments_count +end + +json.payload @attachments do |attachment| + json.partial! 'api/v1/models/attachment', formats: [:json], attachment: attachment + json.conversation_id attachment.message.conversation.display_id +end diff --git a/app/views/api/v1/accounts/conversations/attachments.json.jbuilder b/app/views/api/v1/accounts/conversations/attachments.json.jbuilder index 8bd647f27..e40436072 100644 --- a/app/views/api/v1/accounts/conversations/attachments.json.jbuilder +++ b/app/views/api/v1/accounts/conversations/attachments.json.jbuilder @@ -3,15 +3,5 @@ json.meta do end json.payload @attachments do |attachment| - json.id attachment.push_event_data[:id] - json.message_id attachment.push_event_data[:message_id] - json.thumb_url attachment.push_event_data[:thumb_url] - json.data_url attachment.push_event_data[:data_url] - json.file_size attachment.push_event_data[:file_size] - json.file_type attachment.push_event_data[:file_type] - json.extension attachment.push_event_data[:extension] - json.width attachment.push_event_data[:width] - json.height attachment.push_event_data[:height] - json.created_at attachment.message.created_at.to_i - json.sender attachment.message.sender.push_event_data if attachment.message.sender + json.partial! 'api/v1/models/attachment', formats: [:json], attachment: attachment end diff --git a/app/views/api/v1/models/_attachment.json.jbuilder b/app/views/api/v1/models/_attachment.json.jbuilder new file mode 100644 index 000000000..c700ce4f8 --- /dev/null +++ b/app/views/api/v1/models/_attachment.json.jbuilder @@ -0,0 +1,13 @@ +attachment_data = attachment.push_event_data + +json.id attachment_data[:id] +json.message_id attachment_data[:message_id] +json.thumb_url attachment_data[:thumb_url] +json.data_url attachment_data[:data_url] +json.file_size attachment_data[:file_size] +json.file_type attachment_data[:file_type] +json.extension attachment_data[:extension] +json.width attachment_data[:width] +json.height attachment_data[:height] +json.created_at attachment.message.created_at.to_i +json.sender attachment.message.sender.push_event_data if attachment.message.sender diff --git a/config/routes.rb b/config/routes.rb index dec54c093..01dab6a02 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -209,6 +209,7 @@ Rails.application.routes.draw do resources :contact_inboxes, only: [:create] resources :labels, only: [:create, :index] resources :notes + get :attachments, to: 'attachments#index' post :call, on: :member, to: 'calls#create' if ChatwootApp.enterprise? end end diff --git a/spec/controllers/api/v1/accounts/contacts/attachments_controller_spec.rb b/spec/controllers/api/v1/accounts/contacts/attachments_controller_spec.rb new file mode 100644 index 000000000..cc806aae6 --- /dev/null +++ b/spec/controllers/api/v1/accounts/contacts/attachments_controller_spec.rb @@ -0,0 +1,84 @@ +require 'rails_helper' + +RSpec.describe '/api/v1/accounts/{account.id}/contacts/:id/attachments', type: :request do + let(:account) { create(:account) } + let(:contact) { create(:contact, account: account) } + let(:inbox_1) { create(:inbox, account: account) } + let(:inbox_2) { create(:inbox, account: account) } + let(:contact_inbox_1) { create(:contact_inbox, contact: contact, inbox: inbox_1) } + let(:contact_inbox_2) { create(:contact_inbox, contact: contact, inbox: inbox_2) } + let(:admin) { create(:user, account: account, role: :administrator) } + let(:agent) { create(:user, account: account, role: :agent) } + let(:unknown) { create(:user, account: account, role: nil) } + + before do + create(:inbox_member, user: agent, inbox: inbox_1) + + conversation_1 = create(:conversation, account: account, inbox: inbox_1, contact: contact, contact_inbox: contact_inbox_1) + conversation_2 = create(:conversation, account: account, inbox: inbox_2, contact: contact, contact_inbox: contact_inbox_2) + + create(:message, :with_attachment, conversation: conversation_1, account: account, inbox: inbox_1, message_type: 'incoming') + create(:message, :with_attachment, conversation: conversation_2, account: account, inbox: inbox_2, message_type: 'incoming') + end + + describe 'GET /api/v1/accounts/{account.id}/contacts/:id/attachments' do + context 'when unauthenticated user' do + it 'returns unauthorized' do + get "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/attachments" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when user is logged in' do + context 'with user as administrator' do + it 'returns attachments from all the contact conversations' do + get "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/attachments", + headers: admin.create_new_auth_token + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response['payload'].length).to eq 2 + expect(json_response['meta']['total_count']).to eq 2 + end + + it 'serialises the conversation display id as conversation_id' do + conversation = contact.conversations.first + + get "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/attachments", + headers: admin.create_new_auth_token + + payload = response.parsed_body['payload'] + attachment = payload.find { |a| a['conversation_id'] == conversation.display_id } + expect(attachment).not_to be_nil + expect(attachment).to include('id', 'message_id', 'data_url', 'file_type', 'created_at', 'sender') + end + end + + context 'with user as agent' do + it 'returns attachments only from inboxes the agent has access to' do + get "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/attachments", + headers: agent.create_new_auth_token + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response['payload'].length).to eq 1 + expect(json_response['meta']['total_count']).to eq 1 + end + end + + context 'with user as unknown role' do + it 'returns no attachments' do + get "/api/v1/accounts/#{account.id}/contacts/#{contact.id}/attachments", + headers: unknown.create_new_auth_token + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response['payload']).to be_empty + end + end + end + end +end