mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-04 04:57:51 +00:00 
			
		
		
		
	feat: Ability to delete a contact (#2984)
This change allows the administrator user to delete a contact and its related data like conversations, contact inboxes, and reports. Fixes #1929
This commit is contained in:
		@@ -10,7 +10,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  before_action :check_authorization
 | 
					  before_action :check_authorization
 | 
				
			||||||
  before_action :set_current_page, only: [:index, :active, :search]
 | 
					  before_action :set_current_page, only: [:index, :active, :search]
 | 
				
			||||||
  before_action :fetch_contact, only: [:show, :update, :contactable_inboxes]
 | 
					  before_action :fetch_contact, only: [:show, :update, :destroy, :contactable_inboxes]
 | 
				
			||||||
  before_action :set_include_contact_inboxes, only: [:index, :search]
 | 
					  before_action :set_include_contact_inboxes, only: [:index, :search]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def index
 | 
					  def index
 | 
				
			||||||
@@ -73,6 +73,18 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
 | 
				
			|||||||
    }, status: :unprocessable_entity
 | 
					    }, status: :unprocessable_entity
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def destroy
 | 
				
			||||||
 | 
					    if ::OnlineStatusTracker.get_presence(
 | 
				
			||||||
 | 
					      @contact.account.id, 'Contact', @contact.id
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					      return render_error({ message: I18n.t('contacts.online.delete', contact_name: @contact.name.capitalize) },
 | 
				
			||||||
 | 
					                          :unprocessable_entity)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @contact.destroy!
 | 
				
			||||||
 | 
					    head :ok
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # TODO: Move this to a finder class
 | 
					  # TODO: Move this to a finder class
 | 
				
			||||||
@@ -137,4 +149,8 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
 | 
				
			|||||||
  def fetch_contact
 | 
					  def fetch_contact
 | 
				
			||||||
    @contact = Current.account.contacts.includes(contact_inboxes: [:inbox]).find(params[:id])
 | 
					    @contact = Current.account.contacts.includes(contact_inboxes: [:inbox]).find(params[:id])
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def render_error(error, error_status)
 | 
				
			||||||
 | 
					    render json: error, status: error_status
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@ class ActionCableConnector extends BaseActionCableConnector {
 | 
				
			|||||||
      'conversation.typing_off': this.onTypingOff,
 | 
					      'conversation.typing_off': this.onTypingOff,
 | 
				
			||||||
      'conversation.contact_changed': this.onConversationContactChange,
 | 
					      'conversation.contact_changed': this.onConversationContactChange,
 | 
				
			||||||
      'presence.update': this.onPresenceUpdate,
 | 
					      'presence.update': this.onPresenceUpdate,
 | 
				
			||||||
 | 
					      'contact.deleted': this.onContactDelete,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -115,6 +116,14 @@ class ActionCableConnector extends BaseActionCableConnector {
 | 
				
			|||||||
  fetchConversationStats = () => {
 | 
					  fetchConversationStats = () => {
 | 
				
			||||||
    bus.$emit('fetch_conversation_stats');
 | 
					    bus.$emit('fetch_conversation_stats');
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onContactDelete = data => {
 | 
				
			||||||
 | 
					    this.app.$store.dispatch(
 | 
				
			||||||
 | 
					      'contacts/deleteContactThroughConversations',
 | 
				
			||||||
 | 
					      data.id
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    this.fetchConversationStats();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,6 +54,22 @@
 | 
				
			|||||||
    "TITLE": "Create new contact",
 | 
					    "TITLE": "Create new contact",
 | 
				
			||||||
    "DESC": "Add basic information details about the contact."
 | 
					    "DESC": "Add basic information details about the contact."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  "DELETE_CONTACT": {
 | 
				
			||||||
 | 
					    "BUTTON_LABEL": "Delete Contact",
 | 
				
			||||||
 | 
					    "TITLE": "Delete contact",
 | 
				
			||||||
 | 
					    "DESC": "Delete contact details",
 | 
				
			||||||
 | 
					    "CONFIRM": {
 | 
				
			||||||
 | 
					      "TITLE": "Confirm Deletion",
 | 
				
			||||||
 | 
					      "MESSAGE": "Are you sure to delete ",
 | 
				
			||||||
 | 
					      "PLACE_HOLDER": "Please type {contactName} to confirm",
 | 
				
			||||||
 | 
					      "YES": "Yes, Delete ",
 | 
				
			||||||
 | 
					      "NO": "No, Keep "
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "API": {
 | 
				
			||||||
 | 
					      "SUCCESS_MESSAGE": "Contact deleted successfully",
 | 
				
			||||||
 | 
					      "ERROR_MESSAGE": "Could not delete contact. Please try again later."
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  "CONTACT_FORM": {
 | 
					  "CONTACT_FORM": {
 | 
				
			||||||
    "FORM": {
 | 
					    "FORM": {
 | 
				
			||||||
      "SUBMIT": "Submit",
 | 
					      "SUBMIT": "Submit",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
    <span class="close-button" @click="onClose">
 | 
					    <span class="close-button" @click="onClose">
 | 
				
			||||||
      <i class="ion-android-close close-icon" />
 | 
					      <i class="ion-android-close close-icon" />
 | 
				
			||||||
    </span>
 | 
					    </span>
 | 
				
			||||||
    <contact-info show-new-message :contact="contact" />
 | 
					    <contact-info show-new-message :contact="contact" @panel-close="onClose" />
 | 
				
			||||||
    <accordion-item
 | 
					    <accordion-item
 | 
				
			||||||
      :title="$t('CONTACT_PANEL.SIDEBAR_SECTIONS.CUSTOM_ATTRIBUTES')"
 | 
					      :title="$t('CONTACT_PANEL.SIDEBAR_SECTIONS.CUSTOM_ATTRIBUTES')"
 | 
				
			||||||
      :is-open="isContactSidebarItemOpen('is_ct_custom_attr_open')"
 | 
					      :is-open="isContactSidebarItemOpen('is_ct_custom_attr_open')"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,30 +48,59 @@
 | 
				
			|||||||
          />
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <woot-button
 | 
					      <div v-if="!showNewMessage">
 | 
				
			||||||
        v-if="!showNewMessage"
 | 
					        <div>
 | 
				
			||||||
        class="edit-contact"
 | 
					          <woot-button
 | 
				
			||||||
        variant="link"
 | 
					            class="edit-contact"
 | 
				
			||||||
        size="small"
 | 
					            variant="link"
 | 
				
			||||||
        @click="toggleEditModal"
 | 
					            size="small"
 | 
				
			||||||
      >
 | 
					            @click="toggleEditModal"
 | 
				
			||||||
        {{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
 | 
					          >
 | 
				
			||||||
      </woot-button>
 | 
					            {{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
 | 
				
			||||||
      <div v-else class="contact-actions">
 | 
					          </woot-button>
 | 
				
			||||||
        <woot-button
 | 
					        </div>
 | 
				
			||||||
          class="new-message"
 | 
					        <div v-if="isAdmin">
 | 
				
			||||||
          size="small expanded"
 | 
					          <woot-button
 | 
				
			||||||
          @click="toggleConversationModal"
 | 
					            class="delete-contact"
 | 
				
			||||||
        >
 | 
					            variant="link"
 | 
				
			||||||
          {{ $t('CONTACT_PANEL.NEW_MESSAGE') }}
 | 
					            size="small"
 | 
				
			||||||
        </woot-button>
 | 
					            color-scheme="alert"
 | 
				
			||||||
        <woot-button
 | 
					            @click="toggleDeleteModal"
 | 
				
			||||||
          variant="smooth"
 | 
					            :disabled="uiFlags.isDeleting"
 | 
				
			||||||
          size="small expanded"
 | 
					          >
 | 
				
			||||||
          @click="toggleEditModal"
 | 
					            {{ $t('DELETE_CONTACT.BUTTON_LABEL') }}
 | 
				
			||||||
        >
 | 
					          </woot-button>
 | 
				
			||||||
          {{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
 | 
					        </div>
 | 
				
			||||||
        </woot-button>
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div v-else>
 | 
				
			||||||
 | 
					        <div class="contact-actions">
 | 
				
			||||||
 | 
					          <woot-button
 | 
				
			||||||
 | 
					            v-tooltip="$t('CONTACT_PANEL.NEW_MESSAGE')"
 | 
				
			||||||
 | 
					            class="new-message"
 | 
				
			||||||
 | 
					            icon="ion-chatboxes"
 | 
				
			||||||
 | 
					            size="small expanded"
 | 
				
			||||||
 | 
					            @click="toggleConversationModal"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          <woot-button
 | 
				
			||||||
 | 
					            v-tooltip="$t('EDIT_CONTACT.BUTTON_LABEL')"
 | 
				
			||||||
 | 
					            class="edit-contact"
 | 
				
			||||||
 | 
					            icon="ion-edit"
 | 
				
			||||||
 | 
					            variant="smooth"
 | 
				
			||||||
 | 
					            size="small expanded"
 | 
				
			||||||
 | 
					            @click="toggleEditModal"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					          <woot-button
 | 
				
			||||||
 | 
					            v-if="isAdmin"
 | 
				
			||||||
 | 
					            v-tooltip="$t('DELETE_CONTACT.BUTTON_LABEL')"
 | 
				
			||||||
 | 
					            class="delete-contact"
 | 
				
			||||||
 | 
					            icon="ion-trash-a"
 | 
				
			||||||
 | 
					            variant="hollow"
 | 
				
			||||||
 | 
					            size="small expanded"
 | 
				
			||||||
 | 
					            color-scheme="alert"
 | 
				
			||||||
 | 
					            @click="toggleDeleteModal"
 | 
				
			||||||
 | 
					            :disabled="uiFlags.isDeleting"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <edit-contact
 | 
					      <edit-contact
 | 
				
			||||||
        v-if="showEditModal"
 | 
					        v-if="showEditModal"
 | 
				
			||||||
@@ -80,11 +109,24 @@
 | 
				
			|||||||
        @cancel="toggleEditModal"
 | 
					        @cancel="toggleEditModal"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
      <new-conversation
 | 
					      <new-conversation
 | 
				
			||||||
 | 
					        v-if="contact.id"
 | 
				
			||||||
        :show="showConversationModal"
 | 
					        :show="showConversationModal"
 | 
				
			||||||
        :contact="contact"
 | 
					        :contact="contact"
 | 
				
			||||||
        @cancel="toggleConversationModal"
 | 
					        @cancel="toggleConversationModal"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					    <woot-confirm-delete-modal
 | 
				
			||||||
 | 
					      v-if="showDeleteModal"
 | 
				
			||||||
 | 
					      :show.sync="showDeleteModal"
 | 
				
			||||||
 | 
					      :title="$t('DELETE_CONTACT.CONFIRM.TITLE')"
 | 
				
			||||||
 | 
					      :message="confirmDeleteMessage"
 | 
				
			||||||
 | 
					      :confirm-text="deleteConfirmText"
 | 
				
			||||||
 | 
					      :reject-text="deleteRejectText"
 | 
				
			||||||
 | 
					      :confirm-value="contact.name"
 | 
				
			||||||
 | 
					      :confirm-place-holder-text="confirmPlaceHolderText"
 | 
				
			||||||
 | 
					      @on-confirm="confirmDeletion"
 | 
				
			||||||
 | 
					      @on-close="closeDelete"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
@@ -93,6 +135,9 @@ import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
 | 
				
			|||||||
import SocialIcons from './SocialIcons';
 | 
					import SocialIcons from './SocialIcons';
 | 
				
			||||||
import EditContact from './EditContact';
 | 
					import EditContact from './EditContact';
 | 
				
			||||||
import NewConversation from './NewConversation';
 | 
					import NewConversation from './NewConversation';
 | 
				
			||||||
 | 
					import alertMixin from 'shared/mixins/alertMixin';
 | 
				
			||||||
 | 
					import adminMixin from '../../../../mixins/isAdmin';
 | 
				
			||||||
 | 
					import { mapGetters } from 'vuex';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  components: {
 | 
					  components: {
 | 
				
			||||||
@@ -102,6 +147,7 @@ export default {
 | 
				
			|||||||
    SocialIcons,
 | 
					    SocialIcons,
 | 
				
			||||||
    NewConversation,
 | 
					    NewConversation,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  mixins: [alertMixin, adminMixin],
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
    contact: {
 | 
					    contact: {
 | 
				
			||||||
      type: Object,
 | 
					      type: Object,
 | 
				
			||||||
@@ -120,9 +166,11 @@ export default {
 | 
				
			|||||||
    return {
 | 
					    return {
 | 
				
			||||||
      showEditModal: false,
 | 
					      showEditModal: false,
 | 
				
			||||||
      showConversationModal: false,
 | 
					      showConversationModal: false,
 | 
				
			||||||
 | 
					      showDeleteModal: false,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  computed: {
 | 
					  computed: {
 | 
				
			||||||
 | 
					    ...mapGetters({ uiFlags: 'contacts/getUIFlags' }),
 | 
				
			||||||
    additionalAttributes() {
 | 
					    additionalAttributes() {
 | 
				
			||||||
      return this.contact.additional_attributes || {};
 | 
					      return this.contact.additional_attributes || {};
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -134,6 +182,23 @@ export default {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      return { twitter: twitterScreenName, ...(socialProfiles || {}) };
 | 
					      return { twitter: twitterScreenName, ...(socialProfiles || {}) };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    // Delete Modal
 | 
				
			||||||
 | 
					    deleteConfirmText() {
 | 
				
			||||||
 | 
					      return `${this.$t('DELETE_CONTACT.CONFIRM.YES')} ${this.contact.name}`;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    deleteRejectText() {
 | 
				
			||||||
 | 
					      return `${this.$t('DELETE_CONTACT.CONFIRM.NO')} ${this.contact.name}`;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    confirmDeleteMessage() {
 | 
				
			||||||
 | 
					      return `${this.$t('DELETE_CONTACT.CONFIRM.MESSAGE')} ${
 | 
				
			||||||
 | 
					        this.contact.name
 | 
				
			||||||
 | 
					      } ?`;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    confirmPlaceHolderText() {
 | 
				
			||||||
 | 
					      return `${this.$t('DELETE_CONTACT.CONFIRM.PLACE_HOLDER', {
 | 
				
			||||||
 | 
					        contactName: this.contact.name,
 | 
				
			||||||
 | 
					      })}`;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    toggleEditModal() {
 | 
					    toggleEditModal() {
 | 
				
			||||||
@@ -142,6 +207,31 @@ export default {
 | 
				
			|||||||
    toggleConversationModal() {
 | 
					    toggleConversationModal() {
 | 
				
			||||||
      this.showConversationModal = !this.showConversationModal;
 | 
					      this.showConversationModal = !this.showConversationModal;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    toggleDeleteModal() {
 | 
				
			||||||
 | 
					      this.showDeleteModal = !this.showDeleteModal;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    confirmDeletion() {
 | 
				
			||||||
 | 
					      this.deleteContact(this.contact);
 | 
				
			||||||
 | 
					      this.closeDelete();
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    closeDelete() {
 | 
				
			||||||
 | 
					      this.showDeleteModal = false;
 | 
				
			||||||
 | 
					      this.showConversationModal = false;
 | 
				
			||||||
 | 
					      this.showEditModal = false;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    async deleteContact({ id }) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await this.$store.dispatch('contacts/delete', id);
 | 
				
			||||||
 | 
					        this.$emit('panel-close');
 | 
				
			||||||
 | 
					        this.showAlert(this.$t('DELETE_CONTACT.API.SUCCESS_MESSAGE'));
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        this.showAlert(
 | 
				
			||||||
 | 
					          error.message
 | 
				
			||||||
 | 
					            ? error.message
 | 
				
			||||||
 | 
					            : this.$t('DELETE_CONTACT.API.ERROR_MESSAGE')
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
@@ -179,17 +269,32 @@ export default {
 | 
				
			|||||||
.contact-actions {
 | 
					.contact-actions {
 | 
				
			||||||
  margin-top: var(--space-small);
 | 
					  margin-top: var(--space-small);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
.button.edit-contact {
 | 
					
 | 
				
			||||||
 | 
					.edit-contact {
 | 
				
			||||||
  margin-left: var(--space-medium);
 | 
					  margin-left: var(--space-medium);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.button.new-message {
 | 
					.delete-contact {
 | 
				
			||||||
  margin-right: var(--space-small);
 | 
					  margin-left: var(--space-medium);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.contact-actions {
 | 
					.contact-actions {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  align-items: center;
 | 
					  align-items: center;
 | 
				
			||||||
  width: 100%;
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .new-message {
 | 
				
			||||||
 | 
					    font-size: var(--font-size-medium);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .edit-contact {
 | 
				
			||||||
 | 
					    margin-left: var(--space-small);
 | 
				
			||||||
 | 
					    font-size: var(--font-size-medium);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .delete-contact {
 | 
				
			||||||
 | 
					    margin-left: var(--space-small);
 | 
				
			||||||
 | 
					    font-size: var(--font-size-medium);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,6 +82,9 @@ export const mutations = {
 | 
				
			|||||||
    const conversations = $state.records[id] || [];
 | 
					    const conversations = $state.records[id] || [];
 | 
				
			||||||
    Vue.set($state.records, id, [...conversations, data]);
 | 
					    Vue.set($state.records, id, [...conversations, data]);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  [types.default.DELETE_CONTACT_CONVERSATION]: ($state, id) => {
 | 
				
			||||||
 | 
					    Vue.delete($state.records, id);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,6 +83,21 @@ export const actions = {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  delete: async ({ commit }, id) => {
 | 
				
			||||||
 | 
					    commit(types.SET_CONTACT_UI_FLAG, { isDeleting: true });
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await ContactAPI.delete(id);
 | 
				
			||||||
 | 
					      commit(types.SET_CONTACT_UI_FLAG, { isDeleting: false });
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      commit(types.SET_CONTACT_UI_FLAG, { isDeleting: false });
 | 
				
			||||||
 | 
					      if (error.response?.data?.message) {
 | 
				
			||||||
 | 
					        throw new Error(error.response.data.message);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        throw new Error(error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fetchContactableInbox: async ({ commit }, id) => {
 | 
					  fetchContactableInbox: async ({ commit }, id) => {
 | 
				
			||||||
    commit(types.SET_CONTACT_UI_FLAG, { isFetchingInboxes: true });
 | 
					    commit(types.SET_CONTACT_UI_FLAG, { isFetchingInboxes: true });
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
@@ -110,4 +125,12 @@ export const actions = {
 | 
				
			|||||||
  setContact({ commit }, data) {
 | 
					  setContact({ commit }, data) {
 | 
				
			||||||
    commit(types.SET_CONTACT_ITEM, data);
 | 
					    commit(types.SET_CONTACT_ITEM, data);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  deleteContactThroughConversations: ({ commit }, id) => {
 | 
				
			||||||
 | 
					    commit(types.DELETE_CONTACT, id);
 | 
				
			||||||
 | 
					    commit(types.CLEAR_CONTACT_CONVERSATIONS, id, { root: true });
 | 
				
			||||||
 | 
					    commit(`contactConversations/${types.DELETE_CONTACT_CONVERSATION}`, id, {
 | 
				
			||||||
 | 
					      root: true,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@ const state = {
 | 
				
			|||||||
    isFetchingItem: false,
 | 
					    isFetchingItem: false,
 | 
				
			||||||
    isFetchingInboxes: false,
 | 
					    isFetchingInboxes: false,
 | 
				
			||||||
    isUpdating: false,
 | 
					    isUpdating: false,
 | 
				
			||||||
 | 
					    isDeleting: false,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  sortOrder: [],
 | 
					  sortOrder: [],
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,6 +46,12 @@ export const mutations = {
 | 
				
			|||||||
    Vue.set($state.records, data.id, data);
 | 
					    Vue.set($state.records, data.id, data);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  [types.DELETE_CONTACT]: ($state, id) => {
 | 
				
			||||||
 | 
					    const index = $state.sortOrder.findIndex(item => item === id);
 | 
				
			||||||
 | 
					    Vue.delete($state.sortOrder, index);
 | 
				
			||||||
 | 
					    Vue.delete($state.records, id);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  [types.UPDATE_CONTACTS_PRESENCE]: ($state, data) => {
 | 
					  [types.UPDATE_CONTACTS_PRESENCE]: ($state, data) => {
 | 
				
			||||||
    Object.values($state.records).forEach(element => {
 | 
					    Object.values($state.records).forEach(element => {
 | 
				
			||||||
      const availabilityStatus = data[element.id];
 | 
					      const availabilityStatus = data[element.id];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -177,6 +177,13 @@ export const mutations = {
 | 
				
			|||||||
      Vue.set(chat, 'can_reply', canReply);
 | 
					      Vue.set(chat, 'can_reply', canReply);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  [types.default.CLEAR_CONTACT_CONVERSATIONS](_state, contactId) {
 | 
				
			||||||
 | 
					    const chats = _state.allConversations.filter(
 | 
				
			||||||
 | 
					      c => c.meta.sender.id !== contactId
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    Vue.set(_state, 'allConversations', chats);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -139,6 +139,27 @@ describe('#actions', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('#delete', () => {
 | 
				
			||||||
 | 
					    it('sends correct mutations if API is success', async () => {
 | 
				
			||||||
 | 
					      axios.delete.mockResolvedValue();
 | 
				
			||||||
 | 
					      await actions.delete({ commit }, contactList[0].id);
 | 
				
			||||||
 | 
					      expect(commit.mock.calls).toEqual([
 | 
				
			||||||
 | 
					        [types.SET_CONTACT_UI_FLAG, { isDeleting: true }],
 | 
				
			||||||
 | 
					        [types.SET_CONTACT_UI_FLAG, { isDeleting: false }],
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('sends correct actions if API is error', async () => {
 | 
				
			||||||
 | 
					      axios.delete.mockRejectedValue({ message: 'Incorrect header' });
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        actions.delete({ commit }, contactList[0].id)
 | 
				
			||||||
 | 
					      ).rejects.toThrow(Error);
 | 
				
			||||||
 | 
					      expect(commit.mock.calls).toEqual([
 | 
				
			||||||
 | 
					        [types.SET_CONTACT_UI_FLAG, { isDeleting: true }],
 | 
				
			||||||
 | 
					        [types.SET_CONTACT_UI_FLAG, { isDeleting: false }],
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('#setContact', () => {
 | 
					  describe('#setContact', () => {
 | 
				
			||||||
    it('returns correct mutations', () => {
 | 
					    it('returns correct mutations', () => {
 | 
				
			||||||
      const data = { id: 1, name: 'john doe', availability_status: 'online' };
 | 
					      const data = { id: 1, name: 'john doe', availability_status: 'online' };
 | 
				
			||||||
@@ -146,4 +167,19 @@ describe('#actions', () => {
 | 
				
			|||||||
      expect(commit.mock.calls).toEqual([[types.SET_CONTACT_ITEM, data]]);
 | 
					      expect(commit.mock.calls).toEqual([[types.SET_CONTACT_ITEM, data]]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('#deleteContactThroughConversations', () => {
 | 
				
			||||||
 | 
					    it('returns correct mutations', () => {
 | 
				
			||||||
 | 
					      actions.deleteContactThroughConversations({ commit }, contactList[0].id);
 | 
				
			||||||
 | 
					      expect(commit.mock.calls).toEqual([
 | 
				
			||||||
 | 
					        [types.DELETE_CONTACT, contactList[0].id],
 | 
				
			||||||
 | 
					        [types.CLEAR_CONTACT_CONVERSATIONS, contactList[0].id, { root: true }],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					          `contactConversations/${types.DELETE_CONTACT_CONVERSATION}`,
 | 
				
			||||||
 | 
					          contactList[0].id,
 | 
				
			||||||
 | 
					          { root: true },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,7 @@ export default {
 | 
				
			|||||||
  CHANGE_CHAT_STATUS_FILTER: 'CHANGE_CHAT_STATUS_FILTER',
 | 
					  CHANGE_CHAT_STATUS_FILTER: 'CHANGE_CHAT_STATUS_FILTER',
 | 
				
			||||||
  UPDATE_ASSIGNEE: 'UPDATE_ASSIGNEE',
 | 
					  UPDATE_ASSIGNEE: 'UPDATE_ASSIGNEE',
 | 
				
			||||||
  UPDATE_CONVERSATION_CONTACT: 'UPDATE_CONVERSATION_CONTACT',
 | 
					  UPDATE_CONVERSATION_CONTACT: 'UPDATE_CONVERSATION_CONTACT',
 | 
				
			||||||
 | 
					  CLEAR_CONTACT_CONVERSATIONS: 'CLEAR_CONTACT_CONVERSATIONS',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW',
 | 
					  SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW',
 | 
				
			||||||
  CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW',
 | 
					  CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW',
 | 
				
			||||||
@@ -101,6 +102,7 @@ export default {
 | 
				
			|||||||
  SET_CONTACTS: 'SET_CONTACTS',
 | 
					  SET_CONTACTS: 'SET_CONTACTS',
 | 
				
			||||||
  CLEAR_CONTACTS: 'CLEAR_CONTACTS',
 | 
					  CLEAR_CONTACTS: 'CLEAR_CONTACTS',
 | 
				
			||||||
  EDIT_CONTACT: 'EDIT_CONTACT',
 | 
					  EDIT_CONTACT: 'EDIT_CONTACT',
 | 
				
			||||||
 | 
					  DELETE_CONTACT: 'DELETE_CONTACT',
 | 
				
			||||||
  UPDATE_CONTACTS_PRESENCE: 'UPDATE_CONTACTS_PRESENCE',
 | 
					  UPDATE_CONTACTS_PRESENCE: 'UPDATE_CONTACTS_PRESENCE',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Notifications
 | 
					  // Notifications
 | 
				
			||||||
@@ -119,6 +121,7 @@ export default {
 | 
				
			|||||||
  SET_CONTACT_CONVERSATIONS_UI_FLAG: 'SET_CONTACT_CONVERSATIONS_UI_FLAG',
 | 
					  SET_CONTACT_CONVERSATIONS_UI_FLAG: 'SET_CONTACT_CONVERSATIONS_UI_FLAG',
 | 
				
			||||||
  SET_CONTACT_CONVERSATIONS: 'SET_CONTACT_CONVERSATIONS',
 | 
					  SET_CONTACT_CONVERSATIONS: 'SET_CONTACT_CONVERSATIONS',
 | 
				
			||||||
  ADD_CONTACT_CONVERSATION: 'ADD_CONTACT_CONVERSATION',
 | 
					  ADD_CONTACT_CONVERSATION: 'ADD_CONTACT_CONVERSATION',
 | 
				
			||||||
 | 
					  DELETE_CONTACT_CONVERSATION: 'DELETE_CONTACT_CONVERSATION',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Contact Label
 | 
					  // Contact Label
 | 
				
			||||||
  SET_CONTACT_LABELS_UI_FLAG: 'SET_CONTACT_LABELS_UI_FLAG',
 | 
					  SET_CONTACT_LABELS_UI_FLAG: 'SET_CONTACT_LABELS_UI_FLAG',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -111,6 +111,13 @@ class ActionCableListener < BaseListener
 | 
				
			|||||||
    broadcast(account, tokens, CONTACT_MERGED, contact.push_event_data)
 | 
					    broadcast(account, tokens, CONTACT_MERGED, contact.push_event_data)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def contact_deleted(event)
 | 
				
			||||||
 | 
					    contact, account = extract_contact_and_account(event)
 | 
				
			||||||
 | 
					    tokens = user_tokens(account, account.agents)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    broadcast(account, tokens, CONTACT_DELETED, contact.push_event_data)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def typing_event_listener_tokens(account, conversation, user)
 | 
					  def typing_event_listener_tokens(account, conversation, user)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,6 +48,7 @@ class Contact < ApplicationRecord
 | 
				
			|||||||
  before_validation :prepare_email_attribute
 | 
					  before_validation :prepare_email_attribute
 | 
				
			||||||
  after_create_commit :dispatch_create_event, :ip_lookup
 | 
					  after_create_commit :dispatch_create_event, :ip_lookup
 | 
				
			||||||
  after_update_commit :dispatch_update_event
 | 
					  after_update_commit :dispatch_update_event
 | 
				
			||||||
 | 
					  after_destroy_commit :dispatch_destroy_event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def get_source_id(inbox_id)
 | 
					  def get_source_id(inbox_id)
 | 
				
			||||||
    contact_inboxes.find_by!(inbox_id: inbox_id).source_id
 | 
					    contact_inboxes.find_by!(inbox_id: inbox_id).source_id
 | 
				
			||||||
@@ -73,7 +74,8 @@ class Contact < ApplicationRecord
 | 
				
			|||||||
      id: id,
 | 
					      id: id,
 | 
				
			||||||
      name: name,
 | 
					      name: name,
 | 
				
			||||||
      avatar: avatar_url,
 | 
					      avatar: avatar_url,
 | 
				
			||||||
      type: 'contact'
 | 
					      type: 'contact',
 | 
				
			||||||
 | 
					      account: account.webhook_data
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -98,4 +100,8 @@ class Contact < ApplicationRecord
 | 
				
			|||||||
  def dispatch_update_event
 | 
					  def dispatch_update_event
 | 
				
			||||||
    Rails.configuration.dispatcher.dispatch(CONTACT_UPDATED, Time.zone.now, contact: self)
 | 
					    Rails.configuration.dispatcher.dispatch(CONTACT_UPDATED, Time.zone.now, contact: self)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def dispatch_destroy_event
 | 
				
			||||||
 | 
					    Rails.configuration.dispatcher.dispatch(CONTACT_DELETED, Time.zone.now, contact: self)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,4 +30,8 @@ class ContactPolicy < ApplicationPolicy
 | 
				
			|||||||
  def create?
 | 
					  def create?
 | 
				
			||||||
    true
 | 
					    true
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def destroy?
 | 
				
			||||||
 | 
					    @account_user.administrator?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,7 +92,9 @@ en:
 | 
				
			|||||||
      transcript_subject: "Conversation Transcript"
 | 
					      transcript_subject: "Conversation Transcript"
 | 
				
			||||||
    survey:
 | 
					    survey:
 | 
				
			||||||
      response: "Please rate this conversation, %{link}"
 | 
					      response: "Please rate this conversation, %{link}"
 | 
				
			||||||
 | 
					  contacts:
 | 
				
			||||||
 | 
					    online:
 | 
				
			||||||
 | 
					      delete: "%{contact_name} is Online, please try again later"
 | 
				
			||||||
  integration_apps:
 | 
					  integration_apps:
 | 
				
			||||||
    slack:
 | 
					    slack:
 | 
				
			||||||
      name: "Slack"
 | 
					      name: "Slack"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -76,7 +76,7 @@ Rails.application.routes.draw do
 | 
				
			|||||||
            end
 | 
					            end
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          resources :contacts, only: [:index, :show, :update, :create] do
 | 
					          resources :contacts, only: [:index, :show, :update, :create, :destroy] do
 | 
				
			||||||
            collection do
 | 
					            collection do
 | 
				
			||||||
              get :active
 | 
					              get :active
 | 
				
			||||||
              get :search
 | 
					              get :search
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,7 @@ module Events::Types
 | 
				
			|||||||
  CONTACT_CREATED = 'contact.created'
 | 
					  CONTACT_CREATED = 'contact.created'
 | 
				
			||||||
  CONTACT_UPDATED = 'contact.updated'
 | 
					  CONTACT_UPDATED = 'contact.updated'
 | 
				
			||||||
  CONTACT_MERGED = 'contact.merged'
 | 
					  CONTACT_MERGED = 'contact.merged'
 | 
				
			||||||
 | 
					  CONTACT_DELETED = 'contact.deleted'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # agent events
 | 
					  # agent events
 | 
				
			||||||
  AGENT_ADDED = 'agent.added'
 | 
					  AGENT_ADDED = 'agent.added'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -376,4 +376,53 @@ RSpec.describe 'Contacts API', type: :request do
 | 
				
			|||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'DELETE /api/v1/accounts/{account.id}/contacts/:id', :contact_delete do
 | 
				
			||||||
 | 
					    let(:inbox) { create(:inbox, account: account) }
 | 
				
			||||||
 | 
					    let(:contact) { create(:contact, account: account) }
 | 
				
			||||||
 | 
					    let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: inbox) }
 | 
				
			||||||
 | 
					    let(:conversation) { create(:conversation, account: account, inbox: inbox, contact: contact, contact_inbox: contact_inbox) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when it is an unauthenticated user' do
 | 
				
			||||||
 | 
					      it 'returns unauthorized' do
 | 
				
			||||||
 | 
					        delete "/api/v1/accounts/#{account.id}/contacts/#{contact.id}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(:unauthorized)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when it is an authenticated user' do
 | 
				
			||||||
 | 
					      let(:admin) { create(:user, account: account, role: :administrator) }
 | 
				
			||||||
 | 
					      let(:agent) { create(:user, account: account, role: :agent) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'deletes the contact for administrator user' do
 | 
				
			||||||
 | 
					        allow(::OnlineStatusTracker).to receive(:get_presence).and_return(false)
 | 
				
			||||||
 | 
					        delete "/api/v1/accounts/#{account.id}/contacts/#{contact.id}",
 | 
				
			||||||
 | 
					               headers: admin.create_new_auth_token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(contact.conversations).to be_empty
 | 
				
			||||||
 | 
					        expect(contact.inboxes).to be_empty
 | 
				
			||||||
 | 
					        expect(contact.contact_inboxes).to be_empty
 | 
				
			||||||
 | 
					        expect(contact.csat_survey_responses).to be_empty
 | 
				
			||||||
 | 
					        expect { contact.reload }.to raise_error(ActiveRecord::RecordNotFound)
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(:success)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'does not delete the contact if online' do
 | 
				
			||||||
 | 
					        allow(::OnlineStatusTracker).to receive(:get_presence).and_return(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        delete "/api/v1/accounts/#{account.id}/contacts/#{contact.id}",
 | 
				
			||||||
 | 
					               headers: admin.create_new_auth_token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(:unprocessable_entity)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'returns unauthorized for agent user' do
 | 
				
			||||||
 | 
					        delete "/api/v1/accounts/#{account.id}/contacts/#{contact.id}",
 | 
				
			||||||
 | 
					               headers: agent.create_new_auth_token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(:unauthorized)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,4 +65,19 @@ describe ActionCableListener do
 | 
				
			|||||||
      listener.conversation_typing_off(event)
 | 
					      listener.conversation_typing_off(event)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe '#contact_deleted' do
 | 
				
			||||||
 | 
					    let(:event_name) { :'contact.deleted' }
 | 
				
			||||||
 | 
					    let!(:contact) { create(:contact, account: account) }
 | 
				
			||||||
 | 
					    let!(:event) { Events::Base.new(event_name, Time.zone.now, contact: contact) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'sends message to account admins, inbox agents' do
 | 
				
			||||||
 | 
					      expect(ActionCableBroadcastJob).to receive(:perform_later).with(
 | 
				
			||||||
 | 
					        [agent.pubsub_token, admin.pubsub_token],
 | 
				
			||||||
 | 
					        'contact.deleted',
 | 
				
			||||||
 | 
					        contact.push_event_data.merge(account_id: account.id)
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      listener.contact_deleted(event)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,3 +48,22 @@ put:
 | 
				
			|||||||
      description: Contact not found
 | 
					      description: Contact not found
 | 
				
			||||||
    403:
 | 
					    403:
 | 
				
			||||||
      description: Access denied
 | 
					      description: Access denied
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					delete:
 | 
				
			||||||
 | 
					  tags:
 | 
				
			||||||
 | 
					    - Contact
 | 
				
			||||||
 | 
					  operationId: contactDelete
 | 
				
			||||||
 | 
					  summary: Delete Contact
 | 
				
			||||||
 | 
					  parameters:
 | 
				
			||||||
 | 
					    - name: id
 | 
				
			||||||
 | 
					      in: path
 | 
				
			||||||
 | 
					      type: number
 | 
				
			||||||
 | 
					      description: ID of the contact
 | 
				
			||||||
 | 
					      required: true
 | 
				
			||||||
 | 
					  responses:
 | 
				
			||||||
 | 
					    200:
 | 
				
			||||||
 | 
					      description: Success
 | 
				
			||||||
 | 
					    401:
 | 
				
			||||||
 | 
					      description: Unauthorized
 | 
				
			||||||
 | 
					    404:
 | 
				
			||||||
 | 
					      description: Contact not found
 | 
				
			||||||
@@ -1249,6 +1249,33 @@
 | 
				
			|||||||
            "description": "Access denied"
 | 
					            "description": "Access denied"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "delete": {
 | 
				
			||||||
 | 
					        "tags": [
 | 
				
			||||||
 | 
					          "Contact"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "operationId": "contactDelete",
 | 
				
			||||||
 | 
					        "summary": "Delete Contact",
 | 
				
			||||||
 | 
					        "parameters": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "name": "id",
 | 
				
			||||||
 | 
					            "in": "path",
 | 
				
			||||||
 | 
					            "type": "number",
 | 
				
			||||||
 | 
					            "description": "ID of the contact",
 | 
				
			||||||
 | 
					            "required": true
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "responses": {
 | 
				
			||||||
 | 
					          "200": {
 | 
				
			||||||
 | 
					            "description": "Success"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "401": {
 | 
				
			||||||
 | 
					            "description": "Unauthorized"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "404": {
 | 
				
			||||||
 | 
					            "description": "Contact not found"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "/api/v1/accounts/{account_id}/contacts/{id}/conversations": {
 | 
					    "/api/v1/accounts/{account_id}/contacts/{id}/conversations": {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user