From 827f977a37b7d1caa813846cc53db6bd0576a60c Mon Sep 17 00:00:00 2001 From: giquieu Date: Tue, 12 Jul 2022 05:03:16 -0300 Subject: [PATCH] feat: Update API for contact avatar (#4719) Added the ability to update the contact's avatar via API and Dashboard. - Contact create and update APIs can now accept avatar attachment parameters [form data]. - Contact create and update endpoints can now accept the avatar_url parameter.[json] - API endpoint to remove a contact avatar. - Updated Contact create/edit UI components with avatar support Fixes: #3428 --- .../api/v1/accounts/contacts_controller.rb | 29 ++++++++--- app/javascript/dashboard/api/contacts.js | 4 ++ .../dashboard/api/specs/contacts.spec.js | 8 +++ .../dashboard/components/ui/WootButton.vue | 5 ++ .../widgets/forms/AvatarUploader.vue | 14 ++++- .../dashboard/i18n/locale/en/contact.json | 6 +++ .../conversation/contact/ContactForm.vue | 45 +++++++++++++++- .../store/modules/contacts/actions.js | 51 ++++++++++++++++-- .../modules/specs/contacts/actions.spec.js | 52 ++++++++++++++++--- app/jobs/contact_avatar_job.rb | 2 + app/policies/contact_policy.rb | 4 ++ .../v1/accounts/contacts/avatar.json.jbuilder | 3 ++ config/routes.rb | 1 + .../v1/accounts/contacts_controller_spec.rb | 52 +++++++++++++++++-- spec/factories/contacts.rb | 5 +- .../definitions/request/contact/create.yml | 6 +++ .../definitions/request/contact/update.yml | 8 ++- swagger/swagger.json | 16 ++++++ 18 files changed, 283 insertions(+), 28 deletions(-) create mode 100644 app/views/api/v1/accounts/contacts/avatar.json.jbuilder diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index 76fd206f4..44b1280ee 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -12,7 +12,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController before_action :check_authorization before_action :set_current_page, only: [:index, :active, :search, :filter] - before_action :fetch_contact, only: [:show, :update, :destroy, :contactable_inboxes, :destroy_custom_attributes] + before_action :fetch_contact, only: [:show, :update, :destroy, :avatar, :contactable_inboxes, :destroy_custom_attributes] before_action :set_include_contact_inboxes, only: [:index, :search, :filter] def index @@ -72,15 +72,17 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController def create ActiveRecord::Base.transaction do - @contact = Current.account.contacts.new(contact_params) + @contact = Current.account.contacts.new(permitted_params.except(:avatar_url)) @contact.save! @contact_inbox = build_contact_inbox + process_avatar end end def update @contact.assign_attributes(contact_update_params) @contact.save! + process_avatar if permitted_params[:avatar].present? || permitted_params[:avatar_url].present? end def destroy @@ -95,6 +97,11 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController head :ok end + def avatar + @contact.avatar.purge if @contact.avatar.attached? + @contact + end + private # TODO: Move this to a finder class @@ -131,19 +138,19 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController ContactInbox.create(contact: @contact, inbox: inbox, source_id: source_id) end - def contact_params - params.require(:contact).permit(:name, :identifier, :email, :phone_number, additional_attributes: {}, custom_attributes: {}) + def permitted_params + params.permit(:name, :identifier, :email, :phone_number, :avatar, :avatar_url, additional_attributes: {}, custom_attributes: {}) end def contact_custom_attributes - return @contact.custom_attributes.merge(contact_params[:custom_attributes]) if contact_params[:custom_attributes] + return @contact.custom_attributes.merge(permitted_params[:custom_attributes]) if permitted_params[:custom_attributes] @contact.custom_attributes end def contact_update_params # we want the merged custom attributes not the original one - contact_params.except(:custom_attributes).merge({ custom_attributes: contact_custom_attributes }) + permitted_params.except(:custom_attributes, :avatar_url).merge({ custom_attributes: contact_custom_attributes }) end def set_include_contact_inboxes @@ -158,6 +165,16 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController @contact = Current.account.contacts.includes(contact_inboxes: [:inbox]).find(params[:id]) end + def process_avatar + if permitted_params[:avatar].blank? && permitted_params[:avatar_url].present? + ::ContactAvatarJob.perform_later(@contact, params[:avatar_url]) + elsif permitted_params[:avatar].blank? && permitted_params[:email].present? + hash = Digest::MD5.hexdigest(params[:email]) + gravatar_url = "https://www.gravatar.com/avatar/#{hash}?d=404" + ::ContactAvatarJob.perform_later(@contact, gravatar_url) + end + end + def render_error(error, error_status) render json: error, status: error_status end diff --git a/app/javascript/dashboard/api/contacts.js b/app/javascript/dashboard/api/contacts.js index 8bea5f74b..3599961e1 100644 --- a/app/javascript/dashboard/api/contacts.js +++ b/app/javascript/dashboard/api/contacts.js @@ -71,6 +71,10 @@ class ContactAPI extends ApiClient { custom_attributes: customAttributes, }); } + + destroyAvatar(contactId) { + return axios.delete(`${this.url}/${contactId}/avatar`); + } } export default new ContactAPI(); diff --git a/app/javascript/dashboard/api/specs/contacts.spec.js b/app/javascript/dashboard/api/specs/contacts.spec.js index dfdc3a48b..dc034480a 100644 --- a/app/javascript/dashboard/api/specs/contacts.spec.js +++ b/app/javascript/dashboard/api/specs/contacts.spec.js @@ -12,6 +12,7 @@ describe('#ContactsAPI', () => { expect(contactAPI).toHaveProperty('delete'); expect(contactAPI).toHaveProperty('getConversations'); expect(contactAPI).toHaveProperty('filter'); + expect(contactAPI).toHaveProperty('destroyAvatar'); }); describeWithAPIMock('API calls', context => { @@ -100,6 +101,13 @@ describe('#ContactsAPI', () => { queryPayload ); }); + + it('#destroyAvatar', () => { + contactAPI.destroyAvatar(1); + expect(context.axiosMock.delete).toHaveBeenCalledWith( + '/api/v1/contacts/1/avatar' + ); + }); }); }); diff --git a/app/javascript/dashboard/components/ui/WootButton.vue b/app/javascript/dashboard/components/ui/WootButton.vue index e87c12e90..16cb240be 100644 --- a/app/javascript/dashboard/components/ui/WootButton.vue +++ b/app/javascript/dashboard/components/ui/WootButton.vue @@ -1,6 +1,7 @@