mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 11:37:58 +00:00
Feature: Ability to switch between multiple accounts (#881)
* Feature: Ability to switch between multiple accounts * Fix rubocop * Fix assigned inboxes * fix auth json * Add account switcher in UI * fix ordering on administrate * Add switch accounts to sidebar * add account id * Fix schema.rb timestamp * Revert "add account id" This reverts commit 27570f50ef584cb9a5f69454f43f630b318c8807. * Add a check for account Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
This commit is contained in:
@@ -38,6 +38,7 @@ class AccountBuilder
|
|||||||
|
|
||||||
def create_account
|
def create_account
|
||||||
@account = Account.create!(name: @account_name)
|
@account = Account.create!(name: @account_name)
|
||||||
|
Current.account = @account
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_and_link_user
|
def create_and_link_user
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
|||||||
skip_before_action :authenticate_user!, :set_current_user, :check_subscription, :handle_with_exception,
|
skip_before_action :authenticate_user!, :set_current_user, :check_subscription, :handle_with_exception,
|
||||||
only: [:create], raise: false
|
only: [:create], raise: false
|
||||||
before_action :check_signup_enabled, only: [:create]
|
before_action :check_signup_enabled, only: [:create]
|
||||||
before_action :check_authorization, except: [:create]
|
|
||||||
before_action :fetch_account, except: [:create]
|
before_action :fetch_account, except: [:create]
|
||||||
|
before_action :check_authorization, except: [:create]
|
||||||
|
|
||||||
rescue_from CustomExceptions::Account::InvalidEmail,
|
rescue_from CustomExceptions::Account::InvalidEmail,
|
||||||
CustomExceptions::Account::UserExists,
|
CustomExceptions::Account::UserExists,
|
||||||
@@ -21,7 +21,7 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
|||||||
).perform
|
).perform
|
||||||
if @user
|
if @user
|
||||||
send_auth_headers(@user)
|
send_auth_headers(@user)
|
||||||
render 'devise/auth.json', locals: { resource: @user }
|
render partial: 'devise/auth.json', locals: { resource: @user }
|
||||||
else
|
else
|
||||||
render_error_response(CustomExceptions::Account::SignupFailed.new({}))
|
render_error_response(CustomExceptions::Account::SignupFailed.new({}))
|
||||||
end
|
end
|
||||||
@@ -35,6 +35,12 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
|||||||
@account.update!(account_params.slice(:name, :locale, :domain, :support_email, :domain_emails_enabled))
|
@account.update!(account_params.slice(:name, :locale, :domain, :support_email, :domain_emails_enabled))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_active_at
|
||||||
|
@current_account_user.active_at = Time.now.utc
|
||||||
|
@current_account_user.save!
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def check_authorization
|
def check_authorization
|
||||||
@@ -47,6 +53,7 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
|||||||
|
|
||||||
def fetch_account
|
def fetch_account
|
||||||
@account = current_user.accounts.find(params[:id])
|
@account = current_user.accounts.find(params[:id])
|
||||||
|
@current_account_user = @account.account_users.find_by(user_id: current_user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
@@ -56,4 +63,12 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
|||||||
def check_signup_enabled
|
def check_signup_enabled
|
||||||
raise ActionController::RoutingError, 'Not Found' if ENV.fetch('ENABLE_ACCOUNT_SIGNUP', true) == 'false'
|
raise ActionController::RoutingError, 'Not Found' if ENV.fetch('ENABLE_ACCOUNT_SIGNUP', true) == 'false'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pundit_user
|
||||||
|
{
|
||||||
|
user: current_user,
|
||||||
|
account: @account,
|
||||||
|
account_user: @current_account_user
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ class Api::V1::Accounts::AgentsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@agent.account_user.destroy
|
@agent.current_account_user.destroy
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@agent.update!(agent_params.except(:role))
|
@agent.update!(agent_params.except(:role))
|
||||||
@agent.account_user.update!(role: agent_params[:role]) if agent_params[:role]
|
@agent.current_account_user.update!(role: agent_params[:role]) if agent_params[:role]
|
||||||
render 'api/v1/models/user.json', locals: { resource: @agent }
|
render 'api/v1/models/user.json', locals: { resource: @agent }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseController
|
class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseController
|
||||||
|
before_action :current_account
|
||||||
before_action :authorize_request
|
before_action :authorize_request
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
class Api::V1::Accounts::ConversationsController < Api::BaseController
|
class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||||
include Events::Types
|
include Events::Types
|
||||||
|
before_action :current_account
|
||||||
before_action :conversation, except: [:index]
|
before_action :conversation, except: [:index]
|
||||||
before_action :contact_inbox, only: [:create]
|
before_action :contact_inbox, only: [:create]
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
class Api::V1::Accounts::InboxesController < Api::BaseController
|
class Api::V1::Accounts::InboxesController < Api::BaseController
|
||||||
before_action :check_authorization
|
before_action :current_account
|
||||||
before_action :fetch_inbox, except: [:index, :create]
|
before_action :fetch_inbox, except: [:index, :create]
|
||||||
before_action :fetch_agent_bot, only: [:set_agent_bot]
|
before_action :fetch_agent_bot, only: [:set_agent_bot]
|
||||||
|
before_action :check_authorization
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@inboxes = policy_scope(current_account.inboxes)
|
@inboxes = policy_scope(current_account.inboxes)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
class Api::V1::Accounts::WebhooksController < Api::BaseController
|
class Api::V1::Accounts::WebhooksController < Api::BaseController
|
||||||
|
before_action :current_account
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
before_action :fetch_webhook, only: [:update, :destroy]
|
before_action :fetch_webhook, only: [:update, :destroy]
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,6 @@ class Api::V2::Accounts::ReportsController < Api::BaseController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def current_account
|
|
||||||
current_user.account
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_summary_params
|
def account_summary_params
|
||||||
{
|
{
|
||||||
type: :account,
|
type: :account,
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ class ApplicationController < ActionController::Base
|
|||||||
private
|
private
|
||||||
|
|
||||||
def current_account
|
def current_account
|
||||||
@_ ||= find_current_account
|
@current_account ||= find_current_account
|
||||||
|
Current.account = @current_account
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_current_account
|
def find_current_account
|
||||||
@@ -37,7 +38,9 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def account_accessible_for_user?(account)
|
def account_accessible_for_user?(account)
|
||||||
render_unauthorized('You are not authorized to access this account') unless account.account_users.find_by(user_id: current_user.id)
|
@current_account_user = account.account_users.find_by(user_id: current_user.id)
|
||||||
|
Current.account_user = @current_account_user
|
||||||
|
render_unauthorized('You are not authorized to access this account') unless @current_account_user
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_accessible_for_bot?(account)
|
def account_accessible_for_bot?(account)
|
||||||
@@ -102,4 +105,12 @@ class ApplicationController < ActionController::Base
|
|||||||
render json: { error: 'Account Suspended' }, status: :account_suspended
|
render json: { error: 'Account Suspended' }, status: :account_suspended
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pundit_user
|
||||||
|
{
|
||||||
|
user: Current.user,
|
||||||
|
account: Current.account,
|
||||||
|
account_user: Current.account_user
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController
|
|||||||
@recoverable = User.find_by(reset_password_token: reset_password_token)
|
@recoverable = User.find_by(reset_password_token: reset_password_token)
|
||||||
if @recoverable && reset_password_and_confirmation(@recoverable)
|
if @recoverable && reset_password_and_confirmation(@recoverable)
|
||||||
send_auth_headers(@recoverable)
|
send_auth_headers(@recoverable)
|
||||||
render 'devise/auth.json', locals: { resource: @recoverable }
|
render partial: 'devise/auth.json', locals: { resource: @recoverable }
|
||||||
else
|
else
|
||||||
render json: { "message": 'Invalid token', "redirect_url": '/' }, status: 422
|
render json: { "message": 'Invalid token', "redirect_url": '/' }, status: 422
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ class DeviseOverrides::SessionsController < ::DeviseTokenAuth::SessionsControlle
|
|||||||
wrap_parameters format: []
|
wrap_parameters format: []
|
||||||
|
|
||||||
def render_create_success
|
def render_create_success
|
||||||
render 'devise/auth.json', locals: { resource: @resource }
|
render partial: 'devise/auth.json', locals: { resource: @resource }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ class AccountUserDashboard < Administrate::BaseDashboard
|
|||||||
# which determines how the attribute is displayed
|
# which determines how the attribute is displayed
|
||||||
# on pages throughout the dashboard.
|
# on pages throughout the dashboard.
|
||||||
ATTRIBUTE_TYPES = {
|
ATTRIBUTE_TYPES = {
|
||||||
account: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name'),
|
account: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name', order: 'id DESC'),
|
||||||
user: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name'),
|
user: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name', order: 'id DESC'),
|
||||||
inviter: Field::BelongsTo.with_options(class_name: 'User', searchable: true, searchable_field: 'name'),
|
inviter: Field::BelongsTo.with_options(class_name: 'User', searchable: true, searchable_field: 'name'),
|
||||||
id: Field::Number,
|
id: Field::Number,
|
||||||
role: Field::Select.with_options(collection: AccountUser.roles.keys),
|
role: Field::Select.with_options(collection: AccountUser.roles.keys),
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
@include border-light;
|
@include border-light;
|
||||||
display: block;
|
display: block;
|
||||||
left: 18%;
|
left: 18%;
|
||||||
top: -110%;
|
top: -110px;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
:key="item.toState"
|
:key="item.toState"
|
||||||
:menu-item="item"
|
:menu-item="item"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<sidebar-item
|
<sidebar-item
|
||||||
v-if="shouldShowInboxes"
|
v-if="shouldShowInboxes"
|
||||||
:key="inboxSection.toState"
|
:key="inboxSection.toState"
|
||||||
@@ -30,7 +29,7 @@
|
|||||||
:button-text="$t('APP_GLOBAL.TRAIL_BUTTON')"
|
:button-text="$t('APP_GLOBAL.TRAIL_BUTTON')"
|
||||||
:button-route="{ name: 'billing' }"
|
:button-route="{ name: 'billing' }"
|
||||||
:type="statusBarClass"
|
:type="statusBarClass"
|
||||||
:show-button="isAdmin()"
|
:show-button="isAdmin"
|
||||||
/>
|
/>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
@@ -42,6 +41,14 @@
|
|||||||
class="dropdown-pane top"
|
class="dropdown-pane top"
|
||||||
>
|
>
|
||||||
<ul class="vertical dropdown menu">
|
<ul class="vertical dropdown menu">
|
||||||
|
<li v-if="currentUser.accounts.length > 1">
|
||||||
|
<button
|
||||||
|
class="button clear change-accounts--button"
|
||||||
|
@click="changeAccount"
|
||||||
|
>
|
||||||
|
{{ $t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<router-link :to="`/app/accounts/${accountId}/profile/settings`">
|
<router-link :to="`/app/accounts/${accountId}/profile/settings`">
|
||||||
{{ $t('SIDEBAR_ITEMS.PROFILE_SETTINGS') }}
|
{{ $t('SIDEBAR_ITEMS.PROFILE_SETTINGS') }}
|
||||||
@@ -62,13 +69,35 @@
|
|||||||
{{ currentUser.name }}
|
{{ currentUser.name }}
|
||||||
</h3>
|
</h3>
|
||||||
<h5 class="current-user--role">
|
<h5 class="current-user--role">
|
||||||
{{ currentUser.role }}
|
{{ currentRole }}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<span class="current-user--options icon ion-android-more-vertical">
|
<span class="current-user--options icon ion-android-more-vertical" />
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<woot-modal
|
||||||
|
:show="showAccountModal"
|
||||||
|
:on-close="onClose"
|
||||||
|
class="account-selector--modal"
|
||||||
|
>
|
||||||
|
<woot-modal-header
|
||||||
|
:header-title="$t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS')"
|
||||||
|
:header-content="$t('SIDEBAR_ITEMS.SELECTOR_SUBTITLE')"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-for="account in currentUser.accounts"
|
||||||
|
:key="account.id"
|
||||||
|
class="account-selector"
|
||||||
|
>
|
||||||
|
<a :href="`/app/accounts/${account.id}/dashboard`">
|
||||||
|
<i v-if="account.id === accountId" class="ion ion-ios-checkmark" />
|
||||||
|
<label :for="account.name" class="account--details">
|
||||||
|
<div class="account--name">{{ account.name }}</div>
|
||||||
|
<div class="account--role">{{ account.role }}</div>
|
||||||
|
</label>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</woot-modal>
|
||||||
</aside>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -82,7 +111,7 @@ import SidebarItem from './SidebarItem';
|
|||||||
import WootStatusBar from '../widgets/StatusBar';
|
import WootStatusBar from '../widgets/StatusBar';
|
||||||
import { frontendURL } from '../../helper/URLHelper';
|
import { frontendURL } from '../../helper/URLHelper';
|
||||||
import Thumbnail from '../widgets/Thumbnail';
|
import Thumbnail from '../widgets/Thumbnail';
|
||||||
import sidemenuItems from '../../i18n/default-sidebar';
|
import { getSidebarItems } from '../../i18n/default-sidebar';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -100,6 +129,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showOptionsMenu: false,
|
showOptionsMenu: false,
|
||||||
|
showAccountModal: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -109,15 +139,20 @@ export default {
|
|||||||
globalConfig: 'globalConfig/get',
|
globalConfig: 'globalConfig/get',
|
||||||
inboxes: 'inboxes/getInboxes',
|
inboxes: 'inboxes/getInboxes',
|
||||||
subscriptionData: 'getSubscription',
|
subscriptionData: 'getSubscription',
|
||||||
|
accountId: 'getCurrentAccountId',
|
||||||
|
currentRole: 'getCurrentRole',
|
||||||
}),
|
}),
|
||||||
|
sidemenuItems() {
|
||||||
|
return getSidebarItems(this.accountId);
|
||||||
|
},
|
||||||
accessibleMenuItems() {
|
accessibleMenuItems() {
|
||||||
// get all keys in menuGroup
|
// get all keys in menuGroup
|
||||||
const groupKey = Object.keys(sidemenuItems);
|
const groupKey = Object.keys(this.sidemenuItems);
|
||||||
|
|
||||||
let menuItems = [];
|
let menuItems = [];
|
||||||
// Iterate over menuGroup to find the correct group
|
// Iterate over menuGroup to find the correct group
|
||||||
for (let i = 0; i < groupKey.length; i += 1) {
|
for (let i = 0; i < groupKey.length; i += 1) {
|
||||||
const groupItem = sidemenuItems[groupKey[i]];
|
const groupItem = this.sidemenuItems[groupKey[i]];
|
||||||
// Check if current route is included
|
// Check if current route is included
|
||||||
const isRouteIncluded = groupItem.routes.includes(this.currentRoute);
|
const isRouteIncluded = groupItem.routes.includes(this.currentRoute);
|
||||||
if (isRouteIncluded) {
|
if (isRouteIncluded) {
|
||||||
@@ -135,7 +170,7 @@ export default {
|
|||||||
return this.$store.state.route.name;
|
return this.$store.state.route.name;
|
||||||
},
|
},
|
||||||
shouldShowInboxes() {
|
shouldShowInboxes() {
|
||||||
return sidemenuItems.common.routes.includes(this.currentRoute);
|
return this.sidemenuItems.common.routes.includes(this.currentRoute);
|
||||||
},
|
},
|
||||||
inboxSection() {
|
inboxSection() {
|
||||||
return {
|
return {
|
||||||
@@ -177,9 +212,6 @@ export default {
|
|||||||
trialMessage() {
|
trialMessage() {
|
||||||
return `${this.daysLeft} ${this.$t('APP_GLOBAL.TRIAL_MESSAGE')}`;
|
return `${this.daysLeft} ${this.$t('APP_GLOBAL.TRIAL_MESSAGE')}`;
|
||||||
},
|
},
|
||||||
accountId() {
|
|
||||||
return this.currentUser.account_id;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch('inboxes/get');
|
this.$store.dispatch('inboxes/get');
|
||||||
@@ -191,13 +223,14 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
filterMenuItemsByRole(menuItems) {
|
filterMenuItemsByRole(menuItems) {
|
||||||
const { role } = this.currentUser;
|
if (!this.currentRole) {
|
||||||
if (!role) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return menuItems.filter(
|
return menuItems.filter(
|
||||||
menuItem =>
|
menuItem =>
|
||||||
window.roleWiseRoutes[role].indexOf(menuItem.toStateName) > -1
|
window.roleWiseRoutes[this.currentRole].indexOf(
|
||||||
|
menuItem.toStateName
|
||||||
|
) > -1
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
logout() {
|
logout() {
|
||||||
@@ -206,6 +239,80 @@ export default {
|
|||||||
showOptions() {
|
showOptions() {
|
||||||
this.showOptionsMenu = !this.showOptionsMenu;
|
this.showOptionsMenu = !this.showOptionsMenu;
|
||||||
},
|
},
|
||||||
|
changeAccount() {
|
||||||
|
this.showAccountModal = true;
|
||||||
|
},
|
||||||
|
onClose() {
|
||||||
|
this.showAccountModal = false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '~dashboard/assets/scss/variables';
|
||||||
|
|
||||||
|
.account-selector--modal {
|
||||||
|
.modal-container {
|
||||||
|
width: 40rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-top-bar {
|
||||||
|
padding-bottom: $space-two;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-accounts--button.button {
|
||||||
|
font-weight: $font-weight-normal;
|
||||||
|
font-size: $font-size-small;
|
||||||
|
padding: $space-small $space-one;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-pane {
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
padding: $space-small $space-one !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-selector {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: $space-small $space-large;
|
||||||
|
|
||||||
|
.ion-ios-checkmark {
|
||||||
|
font-size: $font-size-big;
|
||||||
|
|
||||||
|
& + .account--details {
|
||||||
|
padding-left: $space-normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account--details {
|
||||||
|
padding-left: $space-large + $space-smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: $space-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.account--name {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: $font-size-medium;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account--role {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: $font-size-mini;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
import router from '../../routes';
|
import router from '../../routes';
|
||||||
import auth from '../../api/auth';
|
import adminMixin from '../../mixins/isAdmin';
|
||||||
|
|
||||||
const INBOX_TYPES = {
|
const INBOX_TYPES = {
|
||||||
WEB: 'Channel::WebWidget',
|
WEB: 'Channel::WebWidget',
|
||||||
@@ -78,6 +78,7 @@ const getInboxClassByType = type => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [adminMixin],
|
||||||
props: {
|
props: {
|
||||||
menuItem: {
|
menuItem: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -119,7 +120,7 @@ export default {
|
|||||||
router.push({ name: 'settings_inbox_new', params: { page: 'new' } });
|
router.push({ name: 'settings_inbox_new', params: { page: 'new' } });
|
||||||
},
|
},
|
||||||
showItem(item) {
|
showItem(item) {
|
||||||
return auth.isAdmin() && item.newLink !== undefined;
|
return this.isAdmin && item.newLink !== undefined;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<!-- No inboxes attached -->
|
<!-- No inboxes attached -->
|
||||||
<div v-if="!inboxesList.length">
|
<div v-if="!inboxesList.length">
|
||||||
<img src="~dashboard/assets/images/inboxes.svg" alt="No Inboxes" />
|
<img src="~dashboard/assets/images/inboxes.svg" alt="No Inboxes" />
|
||||||
<span v-if="isAdmin()">
|
<span v-if="isAdmin">
|
||||||
{{ $t('CONVERSATION.NO_INBOX_1') }}
|
{{ $t('CONVERSATION.NO_INBOX_1') }}
|
||||||
<br />
|
<br />
|
||||||
<router-link :to="newInboxURL">
|
<router-link :to="newInboxURL">
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
{{ $t('CONVERSATION.NO_INBOX_2') }}
|
{{ $t('CONVERSATION.NO_INBOX_2') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="!isAdmin()">
|
<span v-if="!isAdmin">
|
||||||
{{ $t('CONVERSATION.NO_INBOX_AGENT') }}
|
{{ $t('CONVERSATION.NO_INBOX_AGENT') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ class ActionCableConnector extends BaseActionCableConnector {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAValidEvent = data => {
|
||||||
|
return this.app.$store.getters.getCurrentAccountId === data.account_id;
|
||||||
|
};
|
||||||
|
|
||||||
onMessageUpdated = data => {
|
onMessageUpdated = data => {
|
||||||
this.app.$store.dispatch('updateMessage', data);
|
this.app.$store.dispatch('updateMessage', data);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { frontendURL } from '../helper/URLHelper';
|
import { frontendURL } from '../helper/URLHelper';
|
||||||
import auth from '../api/auth';
|
|
||||||
|
|
||||||
const user = auth.getCurrentUser() || {};
|
export const getSidebarItems = accountId => ({
|
||||||
const accountId = user.account_id;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
common: {
|
common: {
|
||||||
routes: [
|
routes: [
|
||||||
'home',
|
'home',
|
||||||
@@ -106,13 +102,13 @@ export default {
|
|||||||
toState: frontendURL(`accounts/${accountId}/settings/integrations`),
|
toState: frontendURL(`accounts/${accountId}/settings/integrations`),
|
||||||
toStateName: 'settings_integrations',
|
toStateName: 'settings_integrations',
|
||||||
},
|
},
|
||||||
general_settings: {
|
general_settings_index: {
|
||||||
icon: 'ion-gear-a',
|
icon: 'ion-gear-a',
|
||||||
label: 'ACCOUNT_SETTINGS',
|
label: 'ACCOUNT_SETTINGS',
|
||||||
hasSubMenu: false,
|
hasSubMenu: false,
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/general`),
|
toState: frontendURL(`accounts/${accountId}/settings/general`),
|
||||||
toStateName: 'general_settings',
|
toStateName: 'general_settings_index',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -66,6 +66,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SIDEBAR_ITEMS": {
|
"SIDEBAR_ITEMS": {
|
||||||
|
"CHANGE_ACCOUNTS": "Switch Account",
|
||||||
|
"SELECTOR_SUBTITLE": "Select an account from the following list",
|
||||||
"PROFILE_SETTINGS": "Profile Settings",
|
"PROFILE_SETTINGS": "Profile Settings",
|
||||||
"LOGOUT": "Logout"
|
"LOGOUT": "Logout"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,6 +66,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SIDEBAR_ITEMS": {
|
"SIDEBAR_ITEMS": {
|
||||||
|
"CHANGE_ACCOUNTS": "Changer de compte",
|
||||||
|
"SELECTOR_SUBTITLE": "Sélectionnez un compte dans la liste suivante",
|
||||||
"PROFILE_SETTINGS": "Paramètres de profil",
|
"PROFILE_SETTINGS": "Paramètres de profil",
|
||||||
"LOGOUT": "Se déconnecter"
|
"LOGOUT": "Se déconnecter"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,6 +66,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SIDEBAR_ITEMS": {
|
"SIDEBAR_ITEMS": {
|
||||||
|
"CHANGE_ACCOUNTS": "Verwissel van profiel",
|
||||||
|
"SELECTOR_SUBTITLE": "Selecteer een account in de volgende lijst",
|
||||||
"PROFILE_SETTINGS": "Profiel instellingen",
|
"PROFILE_SETTINGS": "Profiel instellingen",
|
||||||
"LOGOUT": "Afmelden"
|
"LOGOUT": "Afmelden"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import Auth from '../api/auth';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
methods: {
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
currentUserRole: 'getCurrentRole',
|
||||||
|
}),
|
||||||
isAdmin() {
|
isAdmin() {
|
||||||
return Auth.isAdmin();
|
return this.currentUserRole === 'administrator';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,11 +15,6 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
},
|
},
|
||||||
props: {
|
|
||||||
mainViewComponent: String,
|
|
||||||
sidebarMenu: String,
|
|
||||||
page: String,
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isSidebarOpen: false,
|
isSidebarOpen: false,
|
||||||
@@ -50,6 +45,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.$store.dispatch('setCurrentAccountId', this.$route.params.accountId);
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.handleResize);
|
||||||
this.handleResize();
|
this.handleResize();
|
||||||
bus.$on('sidemenu_icon_click', () => {
|
bus.$on('sidemenu_icon_click', () => {
|
||||||
|
|||||||
@@ -19,11 +19,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import BackButton from '../../../components/widgets/BackButton';
|
import BackButton from '../../../components/widgets/BackButton';
|
||||||
|
import adminMixin from '../../../mixins/isAdmin';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BackButton,
|
BackButton,
|
||||||
},
|
},
|
||||||
|
mixins: [adminMixin],
|
||||||
props: {
|
props: {
|
||||||
headerTitle: {
|
headerTitle: {
|
||||||
default: '',
|
default: '',
|
||||||
@@ -51,10 +53,6 @@ export default {
|
|||||||
iconClass() {
|
iconClass() {
|
||||||
return `icon ${this.icon} header--icon`;
|
return `icon ${this.icon} header--icon`;
|
||||||
},
|
},
|
||||||
isAdmin() {
|
|
||||||
const { role } = this.currentUser;
|
|
||||||
return role === 'administrator';
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ export default {
|
|||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: frontendURL('accounts/:accountId/settings/general'),
|
path: frontendURL('accounts/:accountId/settings/general'),
|
||||||
name: 'general_settings',
|
|
||||||
roles: ['administrator'],
|
roles: ['administrator'],
|
||||||
component: SettingsContent,
|
component: SettingsContent,
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<p v-if="!inboxesList.length" class="no-items-error-message">
|
<p v-if="!inboxesList.length" class="no-items-error-message">
|
||||||
{{ $t('INBOX_MGMT.LIST.404') }}
|
{{ $t('INBOX_MGMT.LIST.404') }}
|
||||||
<router-link
|
<router-link
|
||||||
v-if="isAdmin()"
|
v-if="isAdmin"
|
||||||
:to="addAccountScoping('settings/inboxes/new')"
|
:to="addAccountScoping('settings/inboxes/new')"
|
||||||
>
|
>
|
||||||
{{ $t('SETTINGS.INBOXES.NEW_INBOX') }}
|
{{ $t('SETTINGS.INBOXES.NEW_INBOX') }}
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
:to="addAccountScoping(`settings/inboxes/${item.id}`)"
|
:to="addAccountScoping(`settings/inboxes/${item.id}`)"
|
||||||
>
|
>
|
||||||
<woot-submit-button
|
<woot-submit-button
|
||||||
v-if="isAdmin()"
|
v-if="isAdmin"
|
||||||
:button-text="$t('INBOX_MGMT.SETTINGS')"
|
:button-text="$t('INBOX_MGMT.SETTINGS')"
|
||||||
icon-class="ion-gear-b"
|
icon-class="ion-gear-b"
|
||||||
button-class="link hollow grey-btn"
|
button-class="link hollow grey-btn"
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<woot-submit-button
|
<woot-submit-button
|
||||||
v-if="isAdmin()"
|
v-if="isAdmin"
|
||||||
:button-text="$t('INBOX_MGMT.DELETE.BUTTON_TEXT')"
|
:button-text="$t('INBOX_MGMT.DELETE.BUTTON_TEXT')"
|
||||||
:loading="loading[item.id]"
|
:loading="loading[item.id]"
|
||||||
icon-class="ion-close-circled"
|
icon-class="ion-close-circled"
|
||||||
@@ -111,7 +111,6 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import Settings from './Settings';
|
import Settings from './Settings';
|
||||||
import adminMixin from '../../../../mixins/isAdmin';
|
import adminMixin from '../../../../mixins/isAdmin';
|
||||||
import auth from '../../../../api/auth';
|
|
||||||
import accountMixin from '../../../../mixins/account';
|
import accountMixin from '../../../../mixins/account';
|
||||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||||
|
|
||||||
@@ -149,9 +148,6 @@ export default {
|
|||||||
this.selectedInbox.name
|
this.selectedInbox.name
|
||||||
} ?`;
|
} ?`;
|
||||||
},
|
},
|
||||||
accountId() {
|
|
||||||
return auth.getCurrentUser().account_id;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openSettings(inbox) {
|
openSettings(inbox) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default {
|
|||||||
path: 'list',
|
path: 'list',
|
||||||
name: 'settings_inbox_list',
|
name: 'settings_inbox_list',
|
||||||
component: InboxHome,
|
component: InboxHome,
|
||||||
roles: ['administrator', 'agent'],
|
roles: ['administrator'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'new',
|
path: 'new',
|
||||||
|
|||||||
@@ -52,10 +52,8 @@ export default {
|
|||||||
...mapGetters({
|
...mapGetters({
|
||||||
currentUser: 'getCurrentUser',
|
currentUser: 'getCurrentUser',
|
||||||
globalConfig: 'globalConfig/get',
|
globalConfig: 'globalConfig/get',
|
||||||
|
accountId: 'getCurrentAccountId',
|
||||||
}),
|
}),
|
||||||
accountId() {
|
|
||||||
return this.currentUser.account_id;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
frontendURL,
|
frontendURL,
|
||||||
|
|||||||
@@ -164,7 +164,6 @@ export default {
|
|||||||
registration.pushManager
|
registration.pushManager
|
||||||
.getSubscription()
|
.getSubscription()
|
||||||
.then(subscription => {
|
.then(subscription => {
|
||||||
console.log(subscription);
|
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
this.hasEnabledPushPermissions = false;
|
this.hasEnabledPushPermissions = false;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
name: 'general_settings_index',
|
name: 'profile_settings_index',
|
||||||
component: Index,
|
component: Index,
|
||||||
roles: ['administrator', 'agent'],
|
roles: ['administrator', 'agent'],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { frontendURL } from '../../../helper/URLHelper';
|
import { frontendURL } from '../../../helper/URLHelper';
|
||||||
import agent from './agents/agent.routes';
|
import agent from './agents/agent.routes';
|
||||||
import Auth from '../../../api/auth';
|
|
||||||
import billing from './billing/billing.routes';
|
import billing from './billing/billing.routes';
|
||||||
import canned from './canned/canned.routes';
|
import canned from './canned/canned.routes';
|
||||||
import inbox from './inbox/inbox.routes';
|
import inbox from './inbox/inbox.routes';
|
||||||
@@ -8,6 +7,7 @@ import profile from './profile/profile.routes';
|
|||||||
import reports from './reports/reports.routes';
|
import reports from './reports/reports.routes';
|
||||||
import integrations from './integrations/integrations.routes';
|
import integrations from './integrations/integrations.routes';
|
||||||
import account from './account/account.routes';
|
import account from './account/account.routes';
|
||||||
|
import store from '../../../store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
routes: [
|
routes: [
|
||||||
@@ -16,7 +16,7 @@ export default {
|
|||||||
name: 'settings_home',
|
name: 'settings_home',
|
||||||
roles: ['administrator', 'agent'],
|
roles: ['administrator', 'agent'],
|
||||||
redirect: () => {
|
redirect: () => {
|
||||||
if (Auth.isAdmin()) {
|
if (store.getters.getCurrentRole === 'administrator') {
|
||||||
return frontendURL('accounts/:accountId/settings/agents');
|
return frontendURL('accounts/:accountId/settings/agents');
|
||||||
}
|
}
|
||||||
return frontendURL('accounts/:accountId/settings/canned-response');
|
return frontendURL('accounts/:accountId/settings/canned-response');
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const state = {
|
|||||||
expiry: null,
|
expiry: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
currentAccountId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// getters
|
// getters
|
||||||
@@ -34,6 +35,18 @@ export const getters = {
|
|||||||
return _state.currentUser.id;
|
return _state.currentUser.id;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getCurrentAccountId(_state) {
|
||||||
|
return _state.currentAccountId;
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrentRole(_state) {
|
||||||
|
const { accounts = [] } = _state.currentUser;
|
||||||
|
const [currentAccount = {}] = accounts.filter(
|
||||||
|
account => account.id === _state.currentAccountId
|
||||||
|
);
|
||||||
|
return currentAccount.role;
|
||||||
|
},
|
||||||
|
|
||||||
getCurrentUser(_state) {
|
getCurrentUser(_state) {
|
||||||
return _state.currentUser;
|
return _state.currentUser;
|
||||||
},
|
},
|
||||||
@@ -103,6 +116,10 @@ export const actions = {
|
|||||||
// Ignore error
|
// Ignore error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setCurrentAccountId({ commit }, accountId) {
|
||||||
|
commit(types.default.SET_CURRENT_ACCOUNT_ID, accountId);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// mutations
|
// mutations
|
||||||
@@ -118,6 +135,9 @@ const mutations = {
|
|||||||
|
|
||||||
Vue.set(_state, 'currentUser', currentUser);
|
Vue.set(_state, 'currentUser', currentUser);
|
||||||
},
|
},
|
||||||
|
[types.default.SET_CURRENT_ACCOUNT_ID](_state, accountId) {
|
||||||
|
Vue.set(_state, 'currentAccountId', Number(accountId));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ export default {
|
|||||||
AUTHENTICATE: 'AUTHENTICATE',
|
AUTHENTICATE: 'AUTHENTICATE',
|
||||||
CLEAR_USER: 'LOGOUT',
|
CLEAR_USER: 'LOGOUT',
|
||||||
SET_CURRENT_USER: 'SET_CURRENT_USER',
|
SET_CURRENT_USER: 'SET_CURRENT_USER',
|
||||||
|
SET_CURRENT_ACCOUNT_ID: 'SET_CURRENT_ACCOUNT_ID',
|
||||||
|
|
||||||
// Chat List
|
// Chat List
|
||||||
RECEIVE_CHAT_LIST: 'RECEIVE_CHAT_LIST',
|
RECEIVE_CHAT_LIST: 'RECEIVE_CHAT_LIST',
|
||||||
SET_ALL_CONVERSATION: 'SET_ALL_CONVERSATION',
|
SET_ALL_CONVERSATION: 'SET_ALL_CONVERSATION',
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class BaseActionCableConnector {
|
|||||||
);
|
);
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.events = {};
|
this.events = {};
|
||||||
|
this.isAValidEvent = () => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
@@ -21,8 +22,10 @@ class BaseActionCableConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onReceived = ({ event, data } = {}) => {
|
onReceived = ({ event, data } = {}) => {
|
||||||
if (this.events[event] && typeof this.events[event] === 'function') {
|
if (this.isAValidEvent(data)) {
|
||||||
this.events[event](data);
|
if (this.events[event] && typeof this.events[event] === 'function') {
|
||||||
|
this.events[event](data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,52 +2,55 @@ class ActionCableListener < BaseListener
|
|||||||
include Events::Types
|
include Events::Types
|
||||||
|
|
||||||
def conversation_created(event)
|
def conversation_created(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account = extract_conversation_and_account(event)
|
||||||
|
tokens = user_tokens(account, conversation.inbox.members)
|
||||||
|
|
||||||
broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_CREATED, conversation.push_event_data)
|
broadcast(account, tokens, CONVERSATION_CREATED, conversation.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_read(event)
|
def conversation_read(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account = extract_conversation_and_account(event)
|
||||||
|
tokens = user_tokens(account, conversation.inbox.members)
|
||||||
|
|
||||||
broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_READ, conversation.push_event_data)
|
broadcast(account, tokens, CONVERSATION_READ, conversation.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def message_created(event)
|
def message_created(event)
|
||||||
message, account, timestamp = extract_message_and_account(event)
|
message, account = extract_message_and_account(event)
|
||||||
conversation = message.conversation
|
conversation = message.conversation
|
||||||
tokens = user_tokens(account, conversation.inbox.members) + contact_token(conversation.contact, message)
|
tokens = user_tokens(account, conversation.inbox.members) + contact_token(conversation.contact, message)
|
||||||
|
|
||||||
broadcast(tokens, MESSAGE_CREATED, message.push_event_data)
|
broadcast(account, tokens, MESSAGE_CREATED, message.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def message_updated(event)
|
def message_updated(event)
|
||||||
message, account, timestamp = extract_message_and_account(event)
|
message, account = extract_message_and_account(event)
|
||||||
conversation = message.conversation
|
conversation = message.conversation
|
||||||
contact = conversation.contact
|
contact = conversation.contact
|
||||||
tokens = user_tokens(account, conversation.inbox.members) + contact_token(conversation.contact, message)
|
tokens = user_tokens(account, conversation.inbox.members) + contact_token(conversation.contact, message)
|
||||||
|
|
||||||
broadcast(tokens, MESSAGE_UPDATED, message.push_event_data)
|
broadcast(account, tokens, MESSAGE_UPDATED, message.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_resolved(event)
|
def conversation_resolved(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account = extract_conversation_and_account(event)
|
||||||
tokens = user_tokens(account, conversation.inbox.members) + [conversation.contact&.pubsub_token]
|
tokens = user_tokens(account, conversation.inbox.members) + [conversation.contact&.pubsub_token]
|
||||||
|
|
||||||
broadcast(tokens, CONVERSATION_RESOLVED, conversation.push_event_data)
|
broadcast(account, tokens, CONVERSATION_RESOLVED, conversation.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_opened(event)
|
def conversation_opened(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account = extract_conversation_and_account(event)
|
||||||
tokens = user_tokens(account, conversation.inbox.members) + [conversation.contact&.pubsub_token]
|
tokens = user_tokens(account, conversation.inbox.members) + [conversation.contact&.pubsub_token]
|
||||||
|
|
||||||
broadcast(tokens, CONVERSATION_OPENED, conversation.push_event_data)
|
broadcast(account, tokens, CONVERSATION_OPENED, conversation.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_lock_toggle(event)
|
def conversation_lock_toggle(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account = extract_conversation_and_account(event)
|
||||||
|
tokens = user_tokens(account, conversation.inbox.members)
|
||||||
|
|
||||||
broadcast(user_tokens(account, conversation.inbox.members), CONVERSATION_LOCK_TOGGLE, conversation.lock_event_data)
|
broadcast(account, tokens, CONVERSATION_LOCK_TOGGLE, conversation.lock_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_typing_on(event)
|
def conversation_typing_on(event)
|
||||||
@@ -57,6 +60,7 @@ class ActionCableListener < BaseListener
|
|||||||
tokens = typing_event_listener_tokens(account, conversation, user)
|
tokens = typing_event_listener_tokens(account, conversation, user)
|
||||||
|
|
||||||
broadcast(
|
broadcast(
|
||||||
|
account,
|
||||||
tokens,
|
tokens,
|
||||||
CONVERSATION_TYPING_ON,
|
CONVERSATION_TYPING_ON,
|
||||||
conversation: conversation.push_event_data,
|
conversation: conversation.push_event_data,
|
||||||
@@ -71,6 +75,7 @@ class ActionCableListener < BaseListener
|
|||||||
tokens = typing_event_listener_tokens(account, conversation, user)
|
tokens = typing_event_listener_tokens(account, conversation, user)
|
||||||
|
|
||||||
broadcast(
|
broadcast(
|
||||||
|
account,
|
||||||
tokens,
|
tokens,
|
||||||
CONVERSATION_TYPING_OFF,
|
CONVERSATION_TYPING_OFF,
|
||||||
conversation: conversation.push_event_data,
|
conversation: conversation.push_event_data,
|
||||||
@@ -79,21 +84,24 @@ class ActionCableListener < BaseListener
|
|||||||
end
|
end
|
||||||
|
|
||||||
def assignee_changed(event)
|
def assignee_changed(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account = extract_conversation_and_account(event)
|
||||||
|
tokens = user_tokens(account, conversation.inbox.members)
|
||||||
|
|
||||||
broadcast(user_tokens(account, conversation.inbox.members), ASSIGNEE_CHANGED, conversation.push_event_data)
|
broadcast(account, tokens, ASSIGNEE_CHANGED, conversation.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def contact_created(event)
|
def contact_created(event)
|
||||||
contact, account, timestamp = extract_contact_and_account(event)
|
contact, account = extract_contact_and_account(event)
|
||||||
|
tokens = user_tokens(account, account.agents)
|
||||||
|
|
||||||
broadcast(user_tokens(account, account.agents), CONTACT_CREATED, contact.push_event_data)
|
broadcast(account, tokens, CONTACT_CREATED, contact.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
def contact_updated(event)
|
def contact_updated(event)
|
||||||
contact, account, timestamp = extract_contact_and_account(event)
|
contact, account = extract_contact_and_account(event)
|
||||||
|
tokens = user_tokens(account, account.agents)
|
||||||
|
|
||||||
broadcast(user_tokens(account, account.agents), CONTACT_UPDATED, contact.push_event_data)
|
broadcast(account, tokens, CONTACT_UPDATED, contact.push_event_data)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -117,9 +125,9 @@ class ActionCableListener < BaseListener
|
|||||||
[contact.pubsub_token]
|
[contact.pubsub_token]
|
||||||
end
|
end
|
||||||
|
|
||||||
def broadcast(tokens, event_name, data)
|
def broadcast(account, tokens, event_name, data)
|
||||||
return if tokens.blank?
|
return if tokens.blank?
|
||||||
|
|
||||||
::ActionCableBroadcastJob.perform_later(tokens.uniq, event_name, data)
|
::ActionCableBroadcastJob.perform_later(tokens.uniq, event_name, data.merge(account_id: account.id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ class BaseListener
|
|||||||
|
|
||||||
def extract_conversation_and_account(event)
|
def extract_conversation_and_account(event)
|
||||||
conversation = event.data[:conversation]
|
conversation = event.data[:conversation]
|
||||||
[conversation, conversation.account, event.timestamp]
|
[conversation, conversation.account]
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_message_and_account(event)
|
def extract_message_and_account(event)
|
||||||
message = event.data[:message]
|
message = event.data[:message]
|
||||||
[message, message.account, event.timestamp]
|
[message, message.account]
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_contact_and_account(event)
|
def extract_contact_and_account(event)
|
||||||
contact = event.data[:contact]
|
contact = event.data[:contact]
|
||||||
[contact, contact.account, event.timestamp]
|
[contact, contact.account]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class NotificationListener < BaseListener
|
class NotificationListener < BaseListener
|
||||||
def conversation_created(event)
|
def conversation_created(event)
|
||||||
conversation, account, _timestamp = extract_conversation_and_account(event)
|
conversation, account = extract_conversation_and_account(event)
|
||||||
return if conversation.bot?
|
return if conversation.bot?
|
||||||
|
|
||||||
conversation.inbox.members.each do |agent|
|
conversation.inbox.members.each do |agent|
|
||||||
@@ -14,7 +14,7 @@ class NotificationListener < BaseListener
|
|||||||
end
|
end
|
||||||
|
|
||||||
def assignee_changed(event)
|
def assignee_changed(event)
|
||||||
conversation, account, _timestamp = extract_conversation_and_account(event)
|
conversation, account = extract_conversation_and_account(event)
|
||||||
assignee = conversation.assignee
|
assignee = conversation.assignee
|
||||||
return unless conversation.notifiable_assignee_change?
|
return unless conversation.notifiable_assignee_change?
|
||||||
return if conversation.bot?
|
return if conversation.bot?
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
class ReportingListener < BaseListener
|
class ReportingListener < BaseListener
|
||||||
def conversation_created(event)
|
def conversation_created(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account = extract_conversation_and_account(event)
|
||||||
|
timestamp = event.timestamp
|
||||||
|
|
||||||
::Reports::UpdateAccountIdentity.new(account, timestamp).incr_conversations_count
|
::Reports::UpdateAccountIdentity.new(account, timestamp).incr_conversations_count
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_resolved(event)
|
def conversation_resolved(event)
|
||||||
conversation, account, timestamp = extract_conversation_and_account(event)
|
conversation, account = extract_conversation_and_account(event)
|
||||||
|
timestamp = event.timestamp
|
||||||
|
|
||||||
time_to_resolve = conversation.updated_at.to_i - conversation.created_at.to_i
|
time_to_resolve = conversation.updated_at.to_i - conversation.created_at.to_i
|
||||||
|
|
||||||
if conversation.assignee.present?
|
if conversation.assignee.present?
|
||||||
@@ -19,7 +23,9 @@ class ReportingListener < BaseListener
|
|||||||
end
|
end
|
||||||
|
|
||||||
def first_reply_created(event)
|
def first_reply_created(event)
|
||||||
message, account, timestamp = extract_message_and_account(event)
|
message, account = extract_message_and_account(event)
|
||||||
|
timestamp = event.timestamp
|
||||||
|
|
||||||
conversation = message.conversation
|
conversation = message.conversation
|
||||||
agent = conversation.assignee
|
agent = conversation.assignee
|
||||||
first_response_time = message.created_at.to_i - conversation.created_at.to_i
|
first_response_time = message.created_at.to_i - conversation.created_at.to_i
|
||||||
@@ -28,7 +34,8 @@ class ReportingListener < BaseListener
|
|||||||
end
|
end
|
||||||
|
|
||||||
def message_created(event)
|
def message_created(event)
|
||||||
message, account, timestamp = extract_message_and_account(event)
|
message, account = extract_message_and_account(event)
|
||||||
|
timestamp = event.timestamp
|
||||||
|
|
||||||
return unless message.reportable?
|
return unless message.reportable?
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# Table name: account_users
|
# Table name: account_users
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
|
# active_at :datetime
|
||||||
# role :integer default("agent")
|
# role :integer default("agent")
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
@@ -23,6 +24,8 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
class AccountUser < ApplicationRecord
|
class AccountUser < ApplicationRecord
|
||||||
|
include Events::Types
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :inviter, class_name: 'User', optional: true
|
belongs_to :inviter, class_name: 'User', optional: true
|
||||||
@@ -30,8 +33,8 @@ class AccountUser < ApplicationRecord
|
|||||||
enum role: { agent: 0, administrator: 1 }
|
enum role: { agent: 0, administrator: 1 }
|
||||||
accepts_nested_attributes_for :account
|
accepts_nested_attributes_for :account
|
||||||
|
|
||||||
after_create :create_notification_setting
|
after_create :notify_creation, :create_notification_setting
|
||||||
after_destroy :destroy_notification_setting
|
after_destroy :notify_deletion, :destroy_notification_setting
|
||||||
|
|
||||||
validates :user_id, uniqueness: { scope: :account_id }
|
validates :user_id, uniqueness: { scope: :account_id }
|
||||||
|
|
||||||
@@ -46,4 +49,14 @@ class AccountUser < ApplicationRecord
|
|||||||
setting = user.notification_settings.find_by(account_id: account.id)
|
setting = user.notification_settings.find_by(account_id: account.id)
|
||||||
setting.destroy!
|
setting.destroy!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def notify_creation
|
||||||
|
Rails.configuration.dispatcher.dispatch(AGENT_ADDED, Time.zone.now, account: account)
|
||||||
|
end
|
||||||
|
|
||||||
|
def notify_deletion
|
||||||
|
Rails.configuration.dispatcher.dispatch(AGENT_REMOVED, Time.zone.now, account: account)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class User < ApplicationRecord
|
|||||||
|
|
||||||
has_many :assigned_conversations, foreign_key: 'assignee_id', class_name: 'Conversation', dependent: :nullify
|
has_many :assigned_conversations, foreign_key: 'assignee_id', class_name: 'Conversation', dependent: :nullify
|
||||||
has_many :inbox_members, dependent: :destroy
|
has_many :inbox_members, dependent: :destroy
|
||||||
has_many :assigned_inboxes, through: :inbox_members, source: :inbox
|
has_many :inboxes, through: :inbox_members, source: :inbox
|
||||||
has_many :messages
|
has_many :messages
|
||||||
has_many :invitees, through: :account_users, class_name: 'User', foreign_key: 'inviter_id', dependent: :nullify
|
has_many :invitees, through: :account_users, class_name: 'User', foreign_key: 'inviter_id', dependent: :nullify
|
||||||
|
|
||||||
@@ -74,9 +74,7 @@ class User < ApplicationRecord
|
|||||||
|
|
||||||
before_validation :set_password_and_uid, on: :create
|
before_validation :set_password_and_uid, on: :create
|
||||||
|
|
||||||
after_create :notify_creation, :create_access_token
|
after_create :create_access_token
|
||||||
|
|
||||||
after_destroy :notify_deletion
|
|
||||||
|
|
||||||
def send_devise_notification(notification, *args)
|
def send_devise_notification(notification, *args)
|
||||||
devise_mailer.send(notification, self, *args).deliver_later
|
devise_mailer.send(notification, self, *args).deliver_later
|
||||||
@@ -86,30 +84,36 @@ class User < ApplicationRecord
|
|||||||
self.uid = email
|
self.uid = email
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_user
|
def active_account_user
|
||||||
# FIXME : temporary hack to transition over to multiple accounts per user
|
account_users.order(active_at: :desc)&.first
|
||||||
# We should be fetching the current account user relationship here.
|
end
|
||||||
account_users&.first
|
|
||||||
|
def current_account_user
|
||||||
|
account_users.find_by(account_id: Current.account.id) if Current.account
|
||||||
end
|
end
|
||||||
|
|
||||||
def account
|
def account
|
||||||
account_user&.account
|
current_account_user&.account
|
||||||
|
end
|
||||||
|
|
||||||
|
def assigned_inboxes
|
||||||
|
inboxes.where(account_id: Current.account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def administrator?
|
def administrator?
|
||||||
account_user&.administrator?
|
current_account_user&.administrator?
|
||||||
end
|
end
|
||||||
|
|
||||||
def agent?
|
def agent?
|
||||||
account_user&.agent?
|
current_account_user&.agent?
|
||||||
end
|
end
|
||||||
|
|
||||||
def role
|
def role
|
||||||
account_user&.role
|
current_account_user&.role
|
||||||
end
|
end
|
||||||
|
|
||||||
def inviter
|
def inviter
|
||||||
account_user&.inviter
|
current_account_user&.inviter
|
||||||
end
|
end
|
||||||
|
|
||||||
def serializable_hash(options = nil)
|
def serializable_hash(options = nil)
|
||||||
@@ -118,14 +122,6 @@ class User < ApplicationRecord
|
|||||||
serialized_user
|
serialized_user
|
||||||
end
|
end
|
||||||
|
|
||||||
def notify_creation
|
|
||||||
Rails.configuration.dispatcher.dispatch(AGENT_ADDED, Time.zone.now, account: account)
|
|
||||||
end
|
|
||||||
|
|
||||||
def notify_deletion
|
|
||||||
Rails.configuration.dispatcher.dispatch(AGENT_REMOVED, Time.zone.now, account: account)
|
|
||||||
end
|
|
||||||
|
|
||||||
def push_event_data
|
def push_event_data
|
||||||
{
|
{
|
||||||
id: id,
|
id: id,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
class AccountPolicy < ApplicationPolicy
|
class AccountPolicy < ApplicationPolicy
|
||||||
def show?
|
def show?
|
||||||
# FIXME : temporary hack to transition over to multiple accounts per user
|
@account_user.administrator? || @account_user.agent?
|
||||||
# We should be fetching the current account user relationship here.
|
|
||||||
@user.administrator?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update?
|
def update?
|
||||||
# FIXME : temporary hack to transition over to multiple accounts per user
|
@account_user.administrator?
|
||||||
# We should be fetching the current account user relationship here.
|
end
|
||||||
@user.administrator?
|
|
||||||
|
def update_active_at?
|
||||||
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
class ApplicationPolicy
|
class ApplicationPolicy
|
||||||
attr_reader :user, :record
|
attr_reader :user_context, :user, :record, :account, :account_user
|
||||||
|
|
||||||
def initialize(user, record)
|
def initialize(user_context, record)
|
||||||
@user = user
|
@user_context = user_context = user_context
|
||||||
|
@user = user_context[:user]
|
||||||
|
@account = user_context[:account]
|
||||||
|
@account_user = user_context[:account_user]
|
||||||
@record = record
|
@record = record
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -35,14 +38,17 @@ class ApplicationPolicy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def scope
|
def scope
|
||||||
Pundit.policy_scope!(user, record.class)
|
Pundit.policy_scope!(user_context, record.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
class Scope
|
class Scope
|
||||||
attr_reader :user, :scope
|
attr_reader :user_context, :user, :scope, :account, :account_user
|
||||||
|
|
||||||
def initialize(user, scope)
|
def initialize(user_context, scope)
|
||||||
@user = user
|
@user_context = user_context = user_context
|
||||||
|
@user = user_context[:user]
|
||||||
|
@account = user_context[:account]
|
||||||
|
@account_user = user_context[:account_user]
|
||||||
@scope = scope
|
@scope = scope
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
class ContactPolicy < ApplicationPolicy
|
class ContactPolicy < ApplicationPolicy
|
||||||
def index?
|
def index?
|
||||||
@user.administrator?
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def update?
|
def update?
|
||||||
@user.administrator?
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def show?
|
def show?
|
||||||
@user.administrator?
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def create?
|
def create?
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
class InboxPolicy < ApplicationPolicy
|
class InboxPolicy < ApplicationPolicy
|
||||||
class Scope
|
class Scope
|
||||||
attr_reader :user, :scope
|
attr_reader :user_context, :user, :scope, :account, :account_user
|
||||||
|
|
||||||
def initialize(user, scope)
|
def initialize(user_context, scope)
|
||||||
@user = user
|
@user_context = user_context
|
||||||
|
@user = user_context[:user]
|
||||||
|
@account = user_context[:account]
|
||||||
|
@account_user = user_context[:account_user]
|
||||||
@scope = scope
|
@scope = scope
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve
|
def resolve
|
||||||
if user.administrator?
|
if @account_user.administrator?
|
||||||
scope.all
|
scope.all
|
||||||
elsif user.agent?
|
elsif @account_user.agent?
|
||||||
user.assigned_inboxes
|
user.assigned_inboxes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -21,18 +24,18 @@ class InboxPolicy < ApplicationPolicy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create?
|
def create?
|
||||||
@user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
|
|
||||||
def update?
|
def update?
|
||||||
@user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy?
|
def destroy?
|
||||||
@user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_agent_bot?
|
def set_agent_bot?
|
||||||
@user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ class UserPolicy < ApplicationPolicy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create?
|
def create?
|
||||||
@user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
|
|
||||||
def update?
|
def update?
|
||||||
@user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy?
|
def destroy?
|
||||||
@user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
class WebhookPolicy < ApplicationPolicy
|
class WebhookPolicy < ApplicationPolicy
|
||||||
def index?
|
def index?
|
||||||
@user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
|
|
||||||
def update?
|
def update?
|
||||||
@user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy?
|
def destroy?
|
||||||
@user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
|
|
||||||
def create?
|
def create?
|
||||||
@user.administrator?
|
@account_user.administrator?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ json.uid resource.uid
|
|||||||
json.name resource.name
|
json.name resource.name
|
||||||
json.nickname resource.nickname
|
json.nickname resource.nickname
|
||||||
json.email resource.email
|
json.email resource.email
|
||||||
json.account_id resource.account.id
|
json.account_id resource.current_account_user.account_id
|
||||||
json.pubsub_token resource.pubsub_token
|
json.pubsub_token resource.pubsub_token
|
||||||
json.role resource.role
|
json.role resource.current_account_user.role
|
||||||
json.inviter_id resource.account_user.inviter_id
|
json.inviter_id resource.current_account_user.inviter_id
|
||||||
json.confirmed resource.confirmed?
|
json.confirmed resource.confirmed?
|
||||||
json.avatar_url resource.avatar_url
|
json.avatar_url resource.avatar_url
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ json.uid @user.uid
|
|||||||
json.name @user.name
|
json.name @user.name
|
||||||
json.nickname @user.nickname
|
json.nickname @user.nickname
|
||||||
json.email @user.email
|
json.email @user.email
|
||||||
json.account_id @user.account.id
|
|
||||||
json.pubsub_token @user.pubsub_token
|
json.pubsub_token @user.pubsub_token
|
||||||
json.role @user.role
|
|
||||||
json.confirmed @user.confirmed?
|
json.confirmed @user.confirmed?
|
||||||
json.avatar_url @user.avatar_url
|
json.avatar_url @user.avatar_url
|
||||||
|
|||||||
22
app/views/devise/_auth.json.jbuilder
Normal file
22
app/views/devise/_auth.json.jbuilder
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
json.data do
|
||||||
|
json.id resource.id
|
||||||
|
json.provider resource.provider
|
||||||
|
json.uid resource.uid
|
||||||
|
json.name resource.name
|
||||||
|
json.nickname resource.nickname
|
||||||
|
json.email resource.email
|
||||||
|
json.account_id resource.active_account_user.account_id
|
||||||
|
json.pubsub_token resource.pubsub_token
|
||||||
|
json.role resource.active_account_user.role
|
||||||
|
json.inviter_id resource.active_account_user.inviter_id
|
||||||
|
json.confirmed resource.confirmed?
|
||||||
|
json.avatar_url resource.avatar_url
|
||||||
|
json.accounts do
|
||||||
|
json.array! resource.account_users do |account_user|
|
||||||
|
json.id account_user.account_id
|
||||||
|
json.name account_user.account.name
|
||||||
|
json.active_at account_user.active_at
|
||||||
|
json.role account_user.role
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
json.data do
|
|
||||||
json.id resource.id
|
|
||||||
json.provider resource.provider
|
|
||||||
json.uid resource.uid
|
|
||||||
json.name resource.name
|
|
||||||
json.nickname resource.nickname
|
|
||||||
json.email resource.email
|
|
||||||
json.account_id resource.account.id
|
|
||||||
json.pubsub_token resource.pubsub_token
|
|
||||||
json.role resource.account_user.role
|
|
||||||
json.inviter_id resource.account_user.inviter_id
|
|
||||||
json.confirmed resource.confirmed?
|
|
||||||
json.avatar_url resource.avatar_url
|
|
||||||
end
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<p>Welcome, <%= @resource.name %>!</p>
|
<p>Welcome, <%= @resource.name %>!</p>
|
||||||
|
|
||||||
<% if @resource.inviter.present? %>
|
<% if @resource&.inviter.present? %>
|
||||||
<p><%= @resource.inviter.name %>, with <%= @resource.inviter.account.name %>, has invited you to try out Chatwoot! </p>
|
<p><%= @resource.inviter.name %>, with <%= @resource.inviter.account.name %>, has invited you to try out Chatwoot! </p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,4 @@
|
|||||||
json.payload do
|
json.payload do
|
||||||
json.success true
|
json.success true
|
||||||
json.data do
|
json.partial! 'auth.json.jbuilder', resource: @resource
|
||||||
json.id @resource.id
|
|
||||||
json.provider @resource.provider
|
|
||||||
json.uid @resource.uid
|
|
||||||
json.name @resource.name
|
|
||||||
json.nickname @resource.nickname
|
|
||||||
json.email @resource.email
|
|
||||||
json.account_id @resource.account.id
|
|
||||||
json.pubsub_token @resource.pubsub_token
|
|
||||||
json.role @resource.account_user.role
|
|
||||||
json.inviter_id @resource.account_user.inviter_id
|
|
||||||
json.confirmed @resource.confirmed?
|
|
||||||
json.avatar_url @resource.avatar_url
|
|
||||||
json.access_token @resource.access_token&.token
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ Rails.application.routes.draw do
|
|||||||
# ----------------------------------
|
# ----------------------------------
|
||||||
# start of account scoped api routes
|
# start of account scoped api routes
|
||||||
resources :accounts, only: [:create, :show, :update], module: :accounts do
|
resources :accounts, only: [:create, :show, :update], module: :accounts do
|
||||||
|
member do
|
||||||
|
post :update_active_at
|
||||||
|
end
|
||||||
namespace :actions do
|
namespace :actions do
|
||||||
resource :contact_merge, only: [:create]
|
resource :contact_merge, only: [:create]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class AddActiveAtToAccountUsers < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
add_column :account_users, :active_at, :datetime, default: nil
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -34,6 +34,7 @@ ActiveRecord::Schema.define(version: 2020_05_22_115645) do
|
|||||||
t.bigint "inviter_id"
|
t.bigint "inviter_id"
|
||||||
t.datetime "created_at", precision: 6, null: false
|
t.datetime "created_at", precision: 6, null: false
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.datetime "active_at"
|
||||||
t.index ["account_id", "user_id"], name: "uniq_user_id_per_account_id", unique: true
|
t.index ["account_id", "user_id"], name: "uniq_user_id_per_account_id", unique: true
|
||||||
t.index ["account_id"], name: "index_account_users_on_account_id"
|
t.index ["account_id"], name: "index_account_users_on_account_id"
|
||||||
t.index ["user_id"], name: "index_account_users_on_user_id"
|
t.index ["user_id"], name: "index_account_users_on_user_id"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
module Current
|
module Current
|
||||||
thread_mattr_accessor :user
|
thread_mattr_accessor :user
|
||||||
|
thread_mattr_accessor :account
|
||||||
|
thread_mattr_accessor :account_user
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ RSpec.describe 'Accounts API', type: :request do
|
|||||||
describe 'GET /api/v1/accounts/{account.id}' do
|
describe 'GET /api/v1/accounts/{account.id}' do
|
||||||
let(:account) { create(:account) }
|
let(:account) { create(:account) }
|
||||||
let(:agent) { create(:user, account: account, role: :agent) }
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
let(:user_without_access) { create(:user) }
|
||||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||||
|
|
||||||
context 'when it is an unauthenticated user' do
|
context 'when it is an unauthenticated user' do
|
||||||
@@ -119,9 +120,9 @@ RSpec.describe 'Accounts API', type: :request do
|
|||||||
context 'when it is an unauthorized user' do
|
context 'when it is an unauthorized user' do
|
||||||
it 'returns unauthorized' do
|
it 'returns unauthorized' do
|
||||||
get "/api/v1/accounts/#{account.id}",
|
get "/api/v1/accounts/#{account.id}",
|
||||||
headers: agent.create_new_auth_token
|
headers: user_without_access.create_new_auth_token
|
||||||
|
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:not_found)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -185,4 +186,29 @@ RSpec.describe 'Accounts API', type: :request do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/accounts/{account.id}/update_active_at' do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post "/api/v1/accounts/#{account.id}/update_active_at"
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
it 'modifies an account' do
|
||||||
|
expect(agent.account_users.first.active_at).to eq(nil)
|
||||||
|
post "/api/v1/accounts/#{account.id}/update_active_at",
|
||||||
|
params: {},
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(agent.account_users.first.active_at).not_to eq(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ describe ::ConversationFinder do
|
|||||||
create(:conversation, account: account, inbox: inbox, assignee: user_1)
|
create(:conversation, account: account, inbox: inbox, assignee: user_1)
|
||||||
create(:conversation, account: account, inbox: inbox, assignee: user_1, status: 'resolved')
|
create(:conversation, account: account, inbox: inbox, assignee: user_1, status: 'resolved')
|
||||||
create(:conversation, account: account, inbox: inbox, assignee: user_2)
|
create(:conversation, account: account, inbox: inbox, assignee: user_2)
|
||||||
|
Current.account = account
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#perform' do
|
describe '#perform' do
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ describe ActionCableListener do
|
|||||||
expect(conversation.inbox.reload.inbox_members.count).to eq(1)
|
expect(conversation.inbox.reload.inbox_members.count).to eq(1)
|
||||||
|
|
||||||
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
||||||
[agent.pubsub_token, admin.pubsub_token, conversation.contact.pubsub_token], 'message.created', message.push_event_data
|
[agent.pubsub_token, admin.pubsub_token, conversation.contact.pubsub_token],
|
||||||
|
'message.created',
|
||||||
|
message.push_event_data.merge(account_id: account.id)
|
||||||
)
|
)
|
||||||
listener.message_created(event)
|
listener.message_created(event)
|
||||||
end
|
end
|
||||||
@@ -40,7 +42,8 @@ describe ActionCableListener do
|
|||||||
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
||||||
[admin.pubsub_token, conversation.contact.pubsub_token],
|
[admin.pubsub_token, conversation.contact.pubsub_token],
|
||||||
'conversation.typing_on', conversation: conversation.push_event_data,
|
'conversation.typing_on', conversation: conversation.push_event_data,
|
||||||
user: agent.push_event_data
|
user: agent.push_event_data,
|
||||||
|
account_id: account.id
|
||||||
)
|
)
|
||||||
listener.conversation_typing_on(event)
|
listener.conversation_typing_on(event)
|
||||||
end
|
end
|
||||||
@@ -56,7 +59,8 @@ describe ActionCableListener do
|
|||||||
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
|
||||||
[admin.pubsub_token, conversation.contact.pubsub_token],
|
[admin.pubsub_token, conversation.contact.pubsub_token],
|
||||||
'conversation.typing_off', conversation: conversation.push_event_data,
|
'conversation.typing_off', conversation: conversation.push_event_data,
|
||||||
user: agent.push_event_data
|
user: agent.push_event_data,
|
||||||
|
account_id: account.id
|
||||||
)
|
)
|
||||||
listener.conversation_typing_off(event)
|
listener.conversation_typing_off(event)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ RSpec.describe 'Confirmation Instructions', type: :mailer do
|
|||||||
let(:inviter_val) { create(:user, :administrator, skip_confirmation: true, account: account) }
|
let(:inviter_val) { create(:user, :administrator, skip_confirmation: true, account: account) }
|
||||||
|
|
||||||
it 'refers to the inviter and their account' do
|
it 'refers to the inviter and their account' do
|
||||||
|
Current.account = account
|
||||||
expect(mail.body).to match(
|
expect(mail.body).to match(
|
||||||
"#{CGI.escapeHTML(inviter_val.name)}, with #{CGI.escapeHTML(inviter_val.account.name)}, has invited you to try out Chatwoot!"
|
"#{CGI.escapeHTML(inviter_val.name)}, with #{CGI.escapeHTML(inviter_val.account.name)}, has invited you to try out Chatwoot!"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ RSpec.describe User do
|
|||||||
it { is_expected.to have_many(:assigned_conversations).class_name('Conversation').dependent(:nullify) }
|
it { is_expected.to have_many(:assigned_conversations).class_name('Conversation').dependent(:nullify) }
|
||||||
it { is_expected.to have_many(:inbox_members).dependent(:destroy) }
|
it { is_expected.to have_many(:inbox_members).dependent(:destroy) }
|
||||||
it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
|
it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
|
||||||
it { is_expected.to have_many(:assigned_inboxes).through(:inbox_members) }
|
|
||||||
it { is_expected.to have_many(:messages) }
|
it { is_expected.to have_many(:messages) }
|
||||||
it { is_expected.to have_many(:events) }
|
it { is_expected.to have_many(:events) }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,23 +11,26 @@ RSpec.describe ContactPolicy, type: :policy do
|
|||||||
let(:agent) { create(:user, account: account) }
|
let(:agent) { create(:user, account: account) }
|
||||||
let(:contact) { create(:contact) }
|
let(:contact) { create(:contact) }
|
||||||
|
|
||||||
|
let(:administrator_context) { { user: administrator, account: account, account_user: account.account_users.first } }
|
||||||
|
let(:agent_context) { { user: agent, account: account, account_user: account.account_users.first } }
|
||||||
|
|
||||||
permissions :index?, :show?, :update? do
|
permissions :index?, :show?, :update? do
|
||||||
context 'when administrator' do
|
context 'when administrator' do
|
||||||
it { expect(contact_policy).to permit(administrator, contact) }
|
it { expect(contact_policy).to permit(administrator_context, contact) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when agent' do
|
context 'when agent' do
|
||||||
it { expect(contact_policy).not_to permit(agent, contact) }
|
it { expect(contact_policy).to permit(agent_context, contact) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
permissions :create? do
|
permissions :create? do
|
||||||
context 'when administrator' do
|
context 'when administrator' do
|
||||||
it { expect(contact_policy).to permit(administrator, contact) }
|
it { expect(contact_policy).to permit(administrator_context, contact) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when agent' do
|
context 'when agent' do
|
||||||
it { expect(contact_policy).to permit(agent, contact) }
|
it { expect(contact_policy).to permit(agent_context, contact) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,24 +10,26 @@ RSpec.describe InboxPolicy, type: :policy do
|
|||||||
let(:administrator) { create(:user, :administrator, account: account) }
|
let(:administrator) { create(:user, :administrator, account: account) }
|
||||||
let(:agent) { create(:user, account: account) }
|
let(:agent) { create(:user, account: account) }
|
||||||
let(:inbox) { create(:inbox) }
|
let(:inbox) { create(:inbox) }
|
||||||
|
let(:administrator_context) { { user: administrator, account: account, account_user: account.account_users.first } }
|
||||||
|
let(:agent_context) { { user: agent, account: account, account_user: account.account_users.first } }
|
||||||
|
|
||||||
permissions :create?, :destroy?, :update?, :set_agent_bot? do
|
permissions :create?, :destroy?, :update?, :set_agent_bot? do
|
||||||
context 'when administrator' do
|
context 'when administrator' do
|
||||||
it { expect(inbox_policy).to permit(administrator, inbox) }
|
it { expect(inbox_policy).to permit(administrator_context, inbox) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when agent' do
|
context 'when agent' do
|
||||||
it { expect(inbox_policy).not_to permit(agent, inbox) }
|
it { expect(inbox_policy).not_to permit(agent_context, inbox) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
permissions :index? do
|
permissions :index? do
|
||||||
context 'when administrator' do
|
context 'when administrator' do
|
||||||
it { expect(inbox_policy).to permit(administrator, inbox) }
|
it { expect(inbox_policy).to permit(administrator_context, inbox) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when agent' do
|
context 'when agent' do
|
||||||
it { expect(inbox_policy).to permit(agent, inbox) }
|
it { expect(inbox_policy).to permit(agent_context, inbox) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,24 +10,26 @@ RSpec.describe UserPolicy, type: :policy do
|
|||||||
let(:administrator) { create(:user, :administrator, account: account) }
|
let(:administrator) { create(:user, :administrator, account: account) }
|
||||||
let(:agent) { create(:user, account: account) }
|
let(:agent) { create(:user, account: account) }
|
||||||
let(:user) { create(:user, account: account) }
|
let(:user) { create(:user, account: account) }
|
||||||
|
let(:administrator_context) { { user: administrator, account: account, account_user: account.account_users.first } }
|
||||||
|
let(:agent_context) { { user: agent, account: account, account_user: account.account_users.first } }
|
||||||
|
|
||||||
permissions :create?, :update?, :destroy? do
|
permissions :create?, :update?, :destroy? do
|
||||||
context 'when administrator' do
|
context 'when administrator' do
|
||||||
it { expect(user_policy).to permit(administrator, user) }
|
it { expect(user_policy).to permit(administrator_context, user) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when agent' do
|
context 'when agent' do
|
||||||
it { expect(user_policy).not_to permit(agent, user) }
|
it { expect(user_policy).not_to permit(agent_context, user) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
permissions :index? do
|
permissions :index? do
|
||||||
context 'when administrator' do
|
context 'when administrator' do
|
||||||
it { expect(user_policy).to permit(administrator, user) }
|
it { expect(user_policy).to permit(administrator_context, user) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when agent' do
|
context 'when agent' do
|
||||||
it { expect(user_policy).to permit(agent, user) }
|
it { expect(user_policy).to permit(agent_context, user) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user