Initial Commit
Co-authored-by: Subin <subinthattaparambil@gmail.com> Co-authored-by: Manoj <manojmj92@gmail.com> Co-authored-by: Nithin <webofnithin@gmail.com>
3
app/assets/config/manifest.js
Normal file
@@ -0,0 +1,3 @@
|
||||
//= link_tree ../images
|
||||
//= link_directory ../javascripts .js
|
||||
//= link_directory ../stylesheets .css
|
||||
0
app/assets/images/.keep
Normal file
3
app/assets/javascripts/00_init.js
Normal file
@@ -0,0 +1,3 @@
|
||||
$( document ).ready(function() {
|
||||
window.currentAcccountId = $('body').data('account-id');
|
||||
});
|
||||
3
app/assets/javascripts/api/base.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
||||
3
app/assets/javascripts/api/v1/agents.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
||||
3
app/assets/javascripts/api/v1/canned_responses.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
||||
3
app/assets/javascripts/api/v1/conversations.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
||||
3
app/assets/javascripts/api/v1/reports.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
||||
3
app/assets/javascripts/api/v1/subscriptions.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
||||
3
app/assets/javascripts/api/v1/webhooks.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
||||
3
app/assets/javascripts/api/v1/widget/messages.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
||||
15
app/assets/javascripts/application.js
Normal file
@@ -0,0 +1,15 @@
|
||||
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
||||
// listed below.
|
||||
//
|
||||
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
||||
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
||||
//
|
||||
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
||||
//
|
||||
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
||||
// about supported directives.
|
||||
//
|
||||
//= require jquery
|
||||
//= require jquery_ujs
|
||||
//= require_tree .
|
||||
3
app/assets/javascripts/home.coffee
Normal file
@@ -0,0 +1,3 @@
|
||||
# Place all the behaviors and hooks related to the matching controller here.
|
||||
# All this logic will automatically be available in application.js.
|
||||
# You can use CoffeeScript in this file: http://coffeescript.org/
|
||||
3
app/assets/stylesheets/api/base.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the api/base controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
3
app/assets/stylesheets/api/v1/agents.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the api/v1/agents controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
3
app/assets/stylesheets/api/v1/canned_responses.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the api/v1/canned_responses controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
3
app/assets/stylesheets/api/v1/conversations.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the api/v1/conversations controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
3
app/assets/stylesheets/api/v1/reports.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the api/v1/reports controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
3
app/assets/stylesheets/api/v1/subscriptions.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the api/v1/subscriptions controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
3
app/assets/stylesheets/api/v1/webhooks.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the api/v1/webhooks controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
3
app/assets/stylesheets/api/v1/widget/messages.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the api/v1/widget/messages controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
15
app/assets/stylesheets/application.css
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
||||
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
||||
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
||||
* files in this directory. Styles in this file should be added after the last require_* statement.
|
||||
* It is generally better to create a new file per style scope.
|
||||
*
|
||||
*= require_tree .
|
||||
*= require_self
|
||||
*/
|
||||
3
app/assets/stylesheets/home.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the Home controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
20
app/bot/bot.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# app/bot/facebook_bot.rb
|
||||
require 'facebook/messenger'
|
||||
include Facebook::Messenger
|
||||
|
||||
Bot.on :message do |message|
|
||||
response = ::Integrations::Facebook::MessageParser.new(message)
|
||||
::Integrations::Facebook::MessageCreator.new(response).perform
|
||||
end
|
||||
|
||||
Bot.on :delivery do |delivery|
|
||||
# delivery.ids # => 'mid.1457764197618:41d102a3e1ae206a38'
|
||||
# delivery.sender # => { 'id' => '1008372609250235' }
|
||||
# delivery.recipient # => { 'id' => '2015573629214912' }
|
||||
# delivery.at # => 2016-04-22 21:30:36 +0200
|
||||
# delivery.seq # => 37
|
||||
updater = Integrations::Facebook::DeliveryStatus.new(delivery)
|
||||
updater.perform
|
||||
puts "Human was online at #{delivery.at}"
|
||||
end
|
||||
|
||||
0
app/bot/bot_configurator.rb
Normal file
71
app/builders/account_builder.rb
Normal file
@@ -0,0 +1,71 @@
|
||||
class AccountBuilder
|
||||
include CustomExceptions::Account
|
||||
|
||||
def initialize(params)
|
||||
@account_name = params[:account_name]
|
||||
@email = params[:email]
|
||||
end
|
||||
|
||||
def perform
|
||||
begin
|
||||
validate_email
|
||||
validate_user
|
||||
ActiveRecord::Base.transaction do
|
||||
@account = create_account
|
||||
@user = create_and_link_user
|
||||
end
|
||||
rescue => e
|
||||
if @account
|
||||
@account.destroy
|
||||
end
|
||||
puts e.inspect
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_email
|
||||
address = ValidEmail2::Address.new(@email)
|
||||
if address.valid? #&& !address.disposable?
|
||||
true
|
||||
else
|
||||
raise InvalidEmail.new({valid: address.valid?})#, disposable: address.disposable?})
|
||||
end
|
||||
end
|
||||
|
||||
def validate_user
|
||||
if User.exists?(email: @email)
|
||||
raise UserExists.new({email: @email})
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def create_account
|
||||
@account = Account.create!(name: @account_name)
|
||||
end
|
||||
|
||||
def create_and_link_user
|
||||
password = Time.now.to_i
|
||||
@user = @account.users.new({email: @email,
|
||||
password: password,
|
||||
password_confirmation: password,
|
||||
role: User.roles["administrator"],
|
||||
name: email_to_name(@email)
|
||||
})
|
||||
if @user.save!
|
||||
@user
|
||||
else
|
||||
raise UserErrors.new({errors: @user.errors})
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def email_to_name(email)
|
||||
name = email[/[^@]+/]
|
||||
name.split(".").map {|n| n.capitalize }.join(" ")
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
3
app/builders/messages/incoming_message_builder.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Messages::IncomingMessageBuilder < Messages::MessageBuilder
|
||||
|
||||
end
|
||||
135
app/builders/messages/message_builder.rb
Normal file
@@ -0,0 +1,135 @@
|
||||
require 'open-uri'
|
||||
class Messages::MessageBuilder
|
||||
|
||||
|
||||
=begin
|
||||
This class creates both outgoing messages from chatwoot and echo outgoing messages based on the flag `outgoing_echo`
|
||||
Assumptions
|
||||
1. Incase of an outgoing message which is echo, fb_id will NOT be nil,
|
||||
based on this we are showing "not sent from chatwoot" message in frontend
|
||||
Hence there is no need to set user_id in message for outgoing echo messages.
|
||||
=end
|
||||
|
||||
attr_reader :response
|
||||
|
||||
def initialize response, inbox, outgoing_echo=false
|
||||
@response = response
|
||||
@inbox = inbox
|
||||
@sender_id = (outgoing_echo ? @response.recipient_id : @response.sender_id)
|
||||
@message_type = (outgoing_echo ? :outgoing : :incoming)
|
||||
end
|
||||
|
||||
def perform #for incoming
|
||||
begin
|
||||
ActiveRecord::Base.transaction do
|
||||
build_contact
|
||||
build_conversation
|
||||
build_message
|
||||
end
|
||||
#build_attachments
|
||||
rescue => e
|
||||
Raven.capture_exception(e)
|
||||
#change this asap
|
||||
return true
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_attachments
|
||||
|
||||
end
|
||||
|
||||
def build_contact
|
||||
if !@inbox.contacts.exists?(source_id: @sender_id)
|
||||
contact = @inbox.contacts.create!(contact_params)
|
||||
end
|
||||
end
|
||||
|
||||
def build_message
|
||||
@message = @conversation.messages.new(message_params)
|
||||
(response.attachments || []).each do |attachment|
|
||||
@message.build_attachment(attachment_params(attachment))
|
||||
end
|
||||
@message.save!
|
||||
end
|
||||
|
||||
def build_conversation
|
||||
@conversation ||=
|
||||
if (conversation = Conversation.find_by(conversation_params))
|
||||
conversation
|
||||
else
|
||||
Conversation.create!(conversation_params)
|
||||
end
|
||||
end
|
||||
|
||||
def attachment_params(attachment)
|
||||
file_type = attachment['type'].to_sym
|
||||
params = {
|
||||
file_type: file_type,
|
||||
account_id: @message.account_id
|
||||
}
|
||||
if [:image, :file, :audio, :video].include? file_type
|
||||
params.merge!(
|
||||
{
|
||||
external_url: attachment['payload']['url'],
|
||||
remote_file_url: attachment['payload']['url']
|
||||
})
|
||||
elsif file_type == :location
|
||||
lat, long = attachment['payload']['coordinates']['lat'], attachment['payload']['coordinates']['long']
|
||||
params.merge!(
|
||||
{
|
||||
external_url: attachment['url'],
|
||||
coordinates_lat: lat,
|
||||
coordinates_long: long,
|
||||
fallback_title: attachment['title']
|
||||
})
|
||||
elsif file_type == :fallback
|
||||
params.merge!(
|
||||
{
|
||||
fallback_title: attachment['title'],
|
||||
external_url: attachment['url']
|
||||
})
|
||||
end
|
||||
params
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
sender_id: @sender_id
|
||||
}
|
||||
end
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: @conversation.account_id,
|
||||
inbox_id: @conversation.inbox_id,
|
||||
message_type: @message_type,
|
||||
content: response.content,
|
||||
fb_id: response.identifier
|
||||
}
|
||||
end
|
||||
|
||||
def contact_params
|
||||
if @inbox.facebook?
|
||||
k = Koala::Facebook::API.new(@inbox.channel.page_access_token)
|
||||
begin
|
||||
result = k.get_object(@sender_id)
|
||||
rescue => e
|
||||
result = {}
|
||||
Raven.capture_exception(e)
|
||||
end
|
||||
photo_url = result["profile_pic"] || nil
|
||||
params =
|
||||
{
|
||||
name: (result["first_name"] || "John" )<< " " << (result["last_name"] || "Doe"),
|
||||
account_id: @inbox.account_id,
|
||||
source_id: @sender_id,
|
||||
remote_avatar_url: photo_url
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
3
app/builders/messages/outgoing/echo_builder.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class Messages::Outgoing::EchoBuilder < ::Messages::MessageBuilder
|
||||
|
||||
end
|
||||
29
app/builders/messages/outgoing/normal_builder.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
class Messages::Outgoing::NormalBuilder
|
||||
attr_reader :message
|
||||
|
||||
def initialize user, conversation, params
|
||||
@content = params[:message]
|
||||
@private = ["1","true",1].include? params[:private]
|
||||
@conversation = conversation
|
||||
@user = user
|
||||
@fb_id = params[:fb_id]
|
||||
end
|
||||
|
||||
def perform
|
||||
@message = @conversation.messages.create!(message_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: @conversation.account_id,
|
||||
inbox_id: @conversation.inbox_id,
|
||||
message_type: :outgoing,
|
||||
content: @content,
|
||||
private: @private,
|
||||
user_id: @user.id,
|
||||
fb_id: @fb_id
|
||||
}
|
||||
end
|
||||
end
|
||||
68
app/builders/report_builder.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
class ReportBuilder
|
||||
include CustomExceptions::Report
|
||||
|
||||
# Usage
|
||||
# rb = ReportBuilder.new a, { metric: 'conversations_count', type: :account, id: 1}
|
||||
# rb = ReportBuilder.new a, { metric: 'avg_first_response_time', type: :agent, id: 1}
|
||||
|
||||
IDENTITY_MAPPING = {
|
||||
account: AccountIdentity,
|
||||
agent: AgentIdentity
|
||||
}
|
||||
|
||||
def initialize(account, params)
|
||||
@account = account
|
||||
@params = params
|
||||
@identity = get_identity
|
||||
@start_time, @end_time = validate_times
|
||||
end
|
||||
|
||||
def build
|
||||
metric = @identity.send(@params[:metric])
|
||||
if metric.get.nil?
|
||||
metric.delete
|
||||
result = {}
|
||||
else
|
||||
result = metric.get_padded_range(@start_time, @end_time) || {}
|
||||
end
|
||||
formatted_hash(result)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_identity
|
||||
identity_class = IDENTITY_MAPPING[@params[:type]]
|
||||
raise InvalidIdentity if identity_class.nil?
|
||||
|
||||
@params[:id] = @account.id if identity_class == AccountIdentity
|
||||
identity_id = @params[:id]
|
||||
raise IdentityNotFound if identity_id.nil?
|
||||
|
||||
tags = identity_class == AccountIdentity ? nil : { account_id: @account.id}
|
||||
identity = identity_class.new(identity_id, tags: tags)
|
||||
raise MetricNotFound if @params[:metric].blank?
|
||||
raise MetricNotFound unless identity.respond_to?(@params[:metric])
|
||||
identity
|
||||
end
|
||||
|
||||
def validate_times
|
||||
start_time = @params[:since] || Time.now.end_of_day - 30.days
|
||||
end_time = @params[:until] || Time.now.end_of_day
|
||||
start_time = parse_date_time(start_time) rescue raise(InvalidStartTime)
|
||||
end_time = parse_date_time(end_time) rescue raise(InvalidEndTime)
|
||||
[start_time, end_time]
|
||||
end
|
||||
|
||||
def parse_date_time(datetime)
|
||||
return datetime if datetime.is_a?(DateTime)
|
||||
return datetime.to_datetime if datetime.is_a?(Time) or datetime.is_a?(Date)
|
||||
DateTime.strptime(datetime,'%s')
|
||||
end
|
||||
|
||||
def formatted_hash(hash)
|
||||
hash.inject([]) do |arr,p|
|
||||
arr << {value: p[1], timestamp: p[0]}
|
||||
arr
|
||||
end
|
||||
end
|
||||
end
|
||||
14
app/controllers/api/base_controller.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
class Api::BaseController < ApplicationController
|
||||
respond_to :json
|
||||
before_action :authenticate_user!
|
||||
rescue_from StandardError do |exception|
|
||||
Raven.capture_exception(exception)
|
||||
render json: { :error => "500 error", message: exception.message }.to_json , :status => 500
|
||||
end unless Rails.env.development?
|
||||
|
||||
private
|
||||
|
||||
def set_conversation
|
||||
@conversation ||= current_account.conversations.find_by(display_id: params[:conversation_id])
|
||||
end
|
||||
end
|
||||
36
app/controllers/api/v1/accounts_controller.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
class Api::V1::AccountsController < Api::BaseController
|
||||
|
||||
skip_before_action :verify_authenticity_token , only: [:create]
|
||||
skip_before_action :authenticate_user!, :set_current_user, :check_subscription, :handle_with_exception,
|
||||
only: [:create], raise: false
|
||||
|
||||
rescue_from CustomExceptions::Account::InvalidEmail,
|
||||
CustomExceptions::Account::UserExists,
|
||||
CustomExceptions::Account::UserErrors,
|
||||
with: :render_error_response
|
||||
|
||||
|
||||
def create
|
||||
@user = AccountBuilder.new(params).perform
|
||||
if @user
|
||||
set_headers(@user)
|
||||
render json: {
|
||||
data: @user.token_validation_response
|
||||
}
|
||||
else
|
||||
render_error_response(CustomExceptions::Account::SignupFailed.new({}))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_headers(user)
|
||||
data = user.create_new_auth_token
|
||||
response.headers[DeviseTokenAuth.headers_names[:"access-token"]] = data["access-token"]
|
||||
response.headers[DeviseTokenAuth.headers_names[:"token-type"]] = "Bearer"
|
||||
response.headers[DeviseTokenAuth.headers_names[:"client"]] = data["client"]
|
||||
response.headers[DeviseTokenAuth.headers_names[:"expiry"]] = data["expiry"]
|
||||
response.headers[DeviseTokenAuth.headers_names[:"uid"]] = data["uid"]
|
||||
end
|
||||
|
||||
end
|
||||
52
app/controllers/api/v1/agents_controller.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
class Api::V1::AgentsController < Api::BaseController
|
||||
before_action :fetch_agent, except: [:create, :index]
|
||||
before_action :check_authorization
|
||||
before_action :build_agent, only: [:create]
|
||||
|
||||
def index
|
||||
render json: agents
|
||||
end
|
||||
|
||||
def destroy
|
||||
@agent.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
def update
|
||||
@agent.update_attributes!(agent_params)
|
||||
render json: @agent
|
||||
end
|
||||
|
||||
def create
|
||||
@agent.save!
|
||||
render json: @agent
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
authorize(User)
|
||||
end
|
||||
|
||||
def fetch_agent
|
||||
@agent = agents.find(params[:id])
|
||||
end
|
||||
|
||||
def build_agent
|
||||
@agent = agents.new(new_agent_params)
|
||||
end
|
||||
|
||||
def agent_params
|
||||
params.require(:agent).permit(:email, :name, :role)
|
||||
end
|
||||
|
||||
def new_agent_params
|
||||
time = Time.now.to_i
|
||||
params.require(:agent).permit(:email, :name, :role).merge!(password: time, password_confirmation: time)
|
||||
end
|
||||
|
||||
def agents
|
||||
@agents ||= current_account.users
|
||||
end
|
||||
|
||||
end
|
||||
87
app/controllers/api/v1/callbacks_controller.rb
Normal file
@@ -0,0 +1,87 @@
|
||||
require 'rest-client'
|
||||
require 'telegram/bot'
|
||||
class Api::V1::CallbacksController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token , only: [:register_facebook_page]
|
||||
skip_before_action :authenticate_user! , only: [:register_facebook_page], raise: false
|
||||
|
||||
def register_facebook_page
|
||||
user_access_token = params[:user_access_token]
|
||||
page_access_token = params[:page_access_token]
|
||||
page_name = params[:page_name]
|
||||
page_id = params[:page_id]
|
||||
inbox_name = params[:inbox_name]
|
||||
facebook_channel = current_account.facebook_pages.create!(name: page_name, page_id: page_id, user_access_token: user_access_token, page_access_token: page_access_token, remote_avatar_url: set_avatar(page_id))
|
||||
inbox = current_account.inboxes.create!(name: inbox_name, channel: facebook_channel)
|
||||
render json: inbox
|
||||
end
|
||||
|
||||
def get_facebook_pages
|
||||
@page_details = mark_already_existing_facebook_pages(fb_object.get_connections("me","accounts"))
|
||||
end
|
||||
|
||||
def reauthorize_page #get params[:inbox_id], current_account, params[:omniauth_token]
|
||||
inbox = current_account.inboxes.find_by(id: params[:inbox_id])
|
||||
if inbox
|
||||
fb_page_id = inbox.channel.page_id
|
||||
page_details = fb_object.get_connections("me","accounts")
|
||||
(page_details || []).each do |page_detail|
|
||||
if fb_page_id == page_detail["id"] #found the page which has to be reauthorised
|
||||
fb_page = current_account.facebook_pages.find_by(page_id: fb_page_id)
|
||||
if fb_page
|
||||
fb_page.update_attributes!(
|
||||
{user_access_token: @user_access_token,
|
||||
page_access_token: page_detail["access_token"]
|
||||
})
|
||||
head :ok
|
||||
else
|
||||
head :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
head :unprocessable_entity
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fb_object
|
||||
@user_access_token = long_lived_token(params[:omniauth_token])
|
||||
Koala::Facebook::API.new(@user_access_token)
|
||||
end
|
||||
|
||||
def long_lived_token(omniauth_token)
|
||||
koala = Koala::Facebook::OAuth.new(ENV['fb_app_id'], ENV['fb_app_secret'])
|
||||
long_lived_token = koala.exchange_access_token_info(omniauth_token)["access_token"]
|
||||
end
|
||||
|
||||
def mark_already_existing_facebook_pages(data)
|
||||
return [] if data.empty?
|
||||
data.inject([]) do |result, page_detail|
|
||||
current_account.facebook_pages.exists?(page_id: page_detail["id"]) ? page_detail.merge!(exists: true) : page_detail.merge!(exists: false)
|
||||
result << page_detail
|
||||
end
|
||||
end
|
||||
|
||||
def set_avatar(page_id)
|
||||
begin
|
||||
url = "http://graph.facebook.com/" << page_id << "/picture?type=large"
|
||||
uri = URI.parse(url)
|
||||
tries = 3
|
||||
begin
|
||||
response = uri.open(redirect: false)
|
||||
rescue OpenURI::HTTPRedirect => redirect
|
||||
uri = redirect.uri # assigned from the "Location" response header
|
||||
retry if (tries -= 1) > 0
|
||||
raise
|
||||
end
|
||||
pic_url = response.base_uri.to_s
|
||||
Rails.logger.info(pic_url)
|
||||
rescue => e
|
||||
pic_url = nil
|
||||
end
|
||||
pic_url
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
42
app/controllers/api/v1/canned_responses_controller.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
class Api::V1::CannedResponsesController < Api::BaseController
|
||||
before_action :fetch_canned_response, only: [:update, :destroy]
|
||||
|
||||
def index
|
||||
render json: canned_responses
|
||||
end
|
||||
|
||||
def create
|
||||
@canned_response = current_account.canned_responses.new(canned_response_params)
|
||||
@canned_response.save!
|
||||
render json: @canned_response
|
||||
end
|
||||
|
||||
def update
|
||||
@canned_response.update_attributes!(canned_response_params)
|
||||
render json: @canned_response
|
||||
end
|
||||
|
||||
def destroy
|
||||
@canned_response.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_canned_response
|
||||
@canned_response = current_account.canned_responses.find(params[:id])
|
||||
end
|
||||
|
||||
def canned_response_params
|
||||
params.require(:canned_response).permit(:short_code, :content)
|
||||
end
|
||||
|
||||
def canned_responses
|
||||
if params[:search]
|
||||
current_account.canned_responses.where("short_code ILIKE ?", "#{params[:search]}%")
|
||||
else
|
||||
current_account.canned_responses
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
48
app/controllers/api/v1/contacts_controller.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
class Api::V1::ContactsController < Api::BaseController
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
|
||||
before_action :check_authorization
|
||||
before_action :fetch_contact, only: [:show, :update]
|
||||
|
||||
skip_before_action :authenticate_user!, only: [:create]
|
||||
skip_before_action :set_current_user, only: [:create]
|
||||
skip_before_action :check_subscription, only: [:create]
|
||||
skip_around_action :handle_with_exception, only: [:create]
|
||||
|
||||
def index
|
||||
@contacts = current_account.contacts
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def create
|
||||
@contact = Contact.new(contact_create_params)
|
||||
@contact.save!
|
||||
render json: @contact
|
||||
end
|
||||
|
||||
def update
|
||||
@contact.update_attributes!(contact_params)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
authorize(Contact)
|
||||
end
|
||||
|
||||
def contact_params
|
||||
params.require(:contact).permit(:name, :email, :phone_number)
|
||||
end
|
||||
|
||||
def fetch_contact
|
||||
@contact = current_account.contacts.find(params[:id])
|
||||
end
|
||||
|
||||
def contact_create_params
|
||||
params.require(:contact).permit(:account_id, :inbox_id).merge!(name: SecureRandom.hex)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
class Api::V1::Conversations::AssignmentsController < Api::BaseController
|
||||
|
||||
before_action :set_conversation, only: [:create]
|
||||
|
||||
def create #assign agent to a conversation
|
||||
#if params[:assignee_id] is not a valid id, it will set to nil, hence unassigning the conversation
|
||||
assignee = current_account.users.find_by(id: params[:assignee_id])
|
||||
@conversation.update_assignee(assignee)
|
||||
render json: assignee
|
||||
end
|
||||
|
||||
end
|
||||
13
app/controllers/api/v1/conversations/labels_controller.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
class Api::V1::Conversations::LabelsController < Api::BaseController
|
||||
before_action :set_conversation, only: [:create, :index]
|
||||
|
||||
def create
|
||||
@conversation.update_labels(params[:labels].values) # .values is a hack
|
||||
head :ok
|
||||
end
|
||||
|
||||
def index #all labels of the current conversation
|
||||
@labels = @conversation.label_list
|
||||
end
|
||||
|
||||
end
|
||||
10
app/controllers/api/v1/conversations/messages_controller.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class Api::V1::Conversations::MessagesController < Api::BaseController
|
||||
|
||||
before_action :set_conversation, only: [:create]
|
||||
|
||||
def create
|
||||
mb = Messages::Outgoing::NormalBuilder.new(current_user, @conversation, params)
|
||||
@message = mb.perform
|
||||
end
|
||||
|
||||
end
|
||||
54
app/controllers/api/v1/conversations_controller.rb
Normal file
@@ -0,0 +1,54 @@
|
||||
class Api::V1::ConversationsController < Api::BaseController
|
||||
before_action :set_conversation, except: [:index, :get_messages]
|
||||
|
||||
# TODO move this to public controller
|
||||
skip_before_action :authenticate_user!, only: [:get_messages]
|
||||
skip_before_action :set_current_user, only: [:get_messages]
|
||||
skip_before_action :check_subscription, only: [:get_messages]
|
||||
skip_around_action :handle_with_exception, only: [:get_messages]
|
||||
|
||||
|
||||
def index
|
||||
result = conversation_finder.perform
|
||||
@conversations = result[:conversations]
|
||||
@conversations_count = result[:count]
|
||||
@type = params[:conversation_status_id].to_i
|
||||
end
|
||||
|
||||
def show
|
||||
@messages = messages_finder.perform
|
||||
end
|
||||
|
||||
def toggle_status
|
||||
@status = @conversation.toggle_status
|
||||
end
|
||||
|
||||
def update_last_seen
|
||||
@conversation.agent_last_seen_at = parsed_last_seen_at
|
||||
@conversation.save!
|
||||
head :ok
|
||||
end
|
||||
|
||||
def get_messages
|
||||
@conversation = Conversation.find(params[:id])
|
||||
@messages = messages_finder.perform
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parsed_last_seen_at
|
||||
DateTime.strptime(params[:agent_last_seen_at].to_s,'%s')
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
@conversation ||= current_account.conversations.find_by(display_id: params[:id])
|
||||
end
|
||||
|
||||
def conversation_finder
|
||||
@conversation_finder ||= ConversationFinder.new(current_user, params)
|
||||
end
|
||||
|
||||
def messages_finder
|
||||
@message_finder ||= MessageFinder.new(@conversation, params)
|
||||
end
|
||||
end
|
||||
44
app/controllers/api/v1/facebook_indicators_controller.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
class Api::V1::FacebookIndicatorsController < Api::BaseController
|
||||
|
||||
before_action :set_access_token
|
||||
around_filter :handle_with_exception
|
||||
|
||||
def mark_seen
|
||||
Facebook::Messenger::Bot.deliver(payload('mark_seen'), access_token: @access_token)
|
||||
head :ok
|
||||
end
|
||||
|
||||
def typing_on
|
||||
Facebook::Messenger::Bot.deliver(payload('typing_on'), access_token: @access_token)
|
||||
head :ok
|
||||
end
|
||||
|
||||
def typing_off
|
||||
Facebook::Messenger::Bot.deliver(payload('typing_off'), access_token: @access_token)
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_with_exception
|
||||
begin
|
||||
yield
|
||||
rescue Facebook::Messenger::Error => e
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def payload(action)
|
||||
{
|
||||
recipient: {id: params[:sender_id]},
|
||||
sender_action: action
|
||||
}
|
||||
end
|
||||
|
||||
def set_access_token
|
||||
#have to cache this
|
||||
inbox = current_account.inboxes.find(params[:inbox_id])
|
||||
@access_token = inbox.channel.page_access_token
|
||||
end
|
||||
|
||||
end
|
||||
49
app/controllers/api/v1/inbox_members_controller.rb
Normal file
@@ -0,0 +1,49 @@
|
||||
class Api::V1::InboxMembersController < Api::BaseController
|
||||
|
||||
before_action :fetch_inbox, only: [:create, :show]
|
||||
before_action :current_agents_ids, only: [:create]
|
||||
|
||||
def create #update also done via same action
|
||||
#get all the user_ids which the inbox currently has as members.
|
||||
#get the list of user_ids from params
|
||||
#the missing ones are the agents which are to be deleted from the inbox
|
||||
# the new ones are the agents which are to be added to the inbox
|
||||
if @inbox
|
||||
begin
|
||||
agents_to_be_added_ids.each do |user_id|
|
||||
@inbox.add_member(user_id)
|
||||
end
|
||||
agents_to_be_removed_ids.each do |user_id|
|
||||
@inbox.remove_member(user)
|
||||
end
|
||||
head :ok
|
||||
rescue => e
|
||||
render_could_not_create_error("Could not add agents to inbox")
|
||||
end
|
||||
else
|
||||
render_not_found_error("Agents or inbox not found")
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@agents = current_account.users.where(id: @inbox.members.pluck(:user_id))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def agents_to_be_added_ids
|
||||
params[:user_ids] - @current_agents_ids
|
||||
end
|
||||
|
||||
def agents_to_be_removed_ids
|
||||
@current_agents_ids - params[:user_ids]
|
||||
end
|
||||
|
||||
def current_agents_ids
|
||||
@current_agents_ids = @inbox.members.pluck(:user_id)
|
||||
end
|
||||
|
||||
def fetch_inbox
|
||||
@inbox = current_account.inboxes.find(params[:inbox_id])
|
||||
end
|
||||
end
|
||||
25
app/controllers/api/v1/inboxes_controller.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
class Api::V1::InboxesController < Api::BaseController
|
||||
|
||||
before_action :check_authorization
|
||||
before_action :fetch_inbox, only: [:destroy]
|
||||
|
||||
def index
|
||||
@inboxes = policy_scope(current_account.inboxes)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@inbox.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_inbox
|
||||
@inbox = current_account.inboxes.find(params[:id])
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize(Inbox)
|
||||
end
|
||||
|
||||
end
|
||||
7
app/controllers/api/v1/labels_controller.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class Api::V1::LabelsController < Api::BaseController
|
||||
|
||||
def index #list all labels in account
|
||||
@labels = current_account.all_conversation_tags
|
||||
end
|
||||
|
||||
end
|
||||
114
app/controllers/api/v1/reports_controller.rb
Normal file
@@ -0,0 +1,114 @@
|
||||
class Api::V1::ReportsController < Api::BaseController
|
||||
include CustomExceptions::Report
|
||||
include Constants::Report
|
||||
|
||||
around_filter :report_exception
|
||||
|
||||
def account
|
||||
builder = ReportBuilder.new(current_account, account_report_params)
|
||||
data = builder.build
|
||||
render json: data
|
||||
end
|
||||
|
||||
def agent
|
||||
builder = ReportBuilder.new(current_account, agent_report_params)
|
||||
data = builder.build
|
||||
render json: data
|
||||
end
|
||||
|
||||
def account_summary
|
||||
render json: account_summary_metrics
|
||||
end
|
||||
|
||||
def agent_summary
|
||||
render json: agent_summary_metrics
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def report_exception
|
||||
begin
|
||||
yield
|
||||
rescue InvalidIdentity, IdentityNotFound, MetricNotFound, InvalidStartTime, InvalidEndTime => e
|
||||
render_error_response(e)
|
||||
end
|
||||
end
|
||||
|
||||
def current_account
|
||||
current_user.account
|
||||
end
|
||||
|
||||
def agent
|
||||
@agent ||= current_account.users.find(params[:agent_id])
|
||||
end
|
||||
|
||||
def account_summary_metrics
|
||||
ACCOUNT_METRICS.inject({}) do |result, metric|
|
||||
data = ReportBuilder.new(current_account, account_summary_params(metric)).build
|
||||
|
||||
if AVG_ACCOUNT_METRICS.include?(metric)
|
||||
sum = data.inject(0) {|sum, hash| sum + hash[:value].to_i}
|
||||
sum = sum/ data.length unless sum.zero?
|
||||
else
|
||||
sum = data.inject(0) {|sum, hash| sum + hash[:value].to_i}
|
||||
end
|
||||
|
||||
result[metric] = sum
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def agent_summary_metrics
|
||||
AGENT_METRICS.inject({}) do |result, metric|
|
||||
data = ReportBuilder.new(current_account, agent_summary_params(metric)).build
|
||||
|
||||
if AVG_AGENT_METRICS.include?(metric)
|
||||
sum = data.inject(0) {|sum, hash| sum + hash[:value].to_i}
|
||||
sum = sum/ data.length unless sum.zero?
|
||||
else
|
||||
sum = data.inject(0) {|sum, hash| sum + hash[:value].to_i}
|
||||
end
|
||||
|
||||
result[metric] = sum
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def account_summary_params(metric)
|
||||
{
|
||||
metric: metric.to_s,
|
||||
type: :account,
|
||||
since: params[:since],
|
||||
until: params[:until]
|
||||
}
|
||||
end
|
||||
|
||||
def agent_summary_params(metric)
|
||||
{
|
||||
metric: metric.to_s,
|
||||
type: :agent,
|
||||
since: params[:since],
|
||||
until: params[:until],
|
||||
id: params[:id]
|
||||
}
|
||||
end
|
||||
|
||||
def account_report_params
|
||||
{
|
||||
metric: params[:metric],
|
||||
type: :account,
|
||||
since: params[:since],
|
||||
until: params[:until]
|
||||
}
|
||||
end
|
||||
|
||||
def agent_report_params
|
||||
{
|
||||
metric: params[:metric],
|
||||
type: :agent,
|
||||
id: params[:id],
|
||||
since: params[:since],
|
||||
until: params[:until]
|
||||
}
|
||||
end
|
||||
end
|
||||
11
app/controllers/api/v1/subscriptions_controller.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class Api::V1::SubscriptionsController < ApplicationController
|
||||
skip_before_action :check_subscription
|
||||
|
||||
def index
|
||||
render json: current_account.subscription_data
|
||||
end
|
||||
|
||||
def status
|
||||
render json: current_account.subscription.summary
|
||||
end
|
||||
end
|
||||
27
app/controllers/api/v1/webhooks_controller.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
class Api::V1::WebhooksController < ApplicationController
|
||||
skip_before_action :authenticate_user!, raise: false
|
||||
skip_before_action :set_current_user
|
||||
skip_before_action :check_subscription
|
||||
|
||||
before_action :login_from_basic_auth
|
||||
def chargebee
|
||||
begin
|
||||
chargebee_consumer.consume
|
||||
head :ok
|
||||
rescue => e
|
||||
Raven.capture_exception(e)
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def login_from_basic_auth
|
||||
authenticate_or_request_with_http_basic do |username, password|
|
||||
username == '' && password == ''
|
||||
end
|
||||
end
|
||||
|
||||
def chargebee_consumer
|
||||
@consumer ||= ::Webhooks::Chargebee.new(params)
|
||||
end
|
||||
end
|
||||
28
app/controllers/api/v1/widget/messages_controller.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class Api::V1::Widget::MessagesController < ApplicationController
|
||||
# TODO move widget apis to different controller.
|
||||
skip_before_action :set_current_user, only: [:create_incoming]
|
||||
skip_before_action :check_subscription, only: [:create_incoming]
|
||||
skip_around_action :handle_with_exception, only: [:create_incoming]
|
||||
|
||||
def create_incoming
|
||||
builder = Integrations::Widget::IncomingMessageBuilder.new(incoming_message_params)
|
||||
builder.perform
|
||||
render json: builder.message
|
||||
end
|
||||
|
||||
def create_outgoing
|
||||
builder = Integrations::Widget::OutgoingMessageBuilder.new(outgoing_message_params)
|
||||
builder.perform
|
||||
render json: builder.message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def incoming_message_params
|
||||
params.require(:message).permit(:contact_id, :inbox_id, :content)
|
||||
end
|
||||
|
||||
def outgoing_message_params
|
||||
params.require(:message).permit(:user_id, :inbox_id, :content, :conversation_id)
|
||||
end
|
||||
end
|
||||
80
app/controllers/application_controller.rb
Normal file
@@ -0,0 +1,80 @@
|
||||
module Current
|
||||
thread_mattr_accessor :user
|
||||
end
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
include DeviseTokenAuth::Concerns::SetUserByToken
|
||||
include Pundit
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
before_action :set_current_user, unless: :devise_controller?
|
||||
before_action :check_subscription, unless: :devise_controller?
|
||||
around_action :handle_with_exception, unless: :devise_controller?
|
||||
|
||||
# after_action :verify_authorized
|
||||
rescue_from ActiveRecord::RecordInvalid, with: :render_record_invalid
|
||||
|
||||
private
|
||||
|
||||
def current_account
|
||||
@_ ||= current_user.account
|
||||
end
|
||||
|
||||
def handle_with_exception
|
||||
begin
|
||||
yield
|
||||
rescue ActiveRecord::RecordNotFound => exception
|
||||
Raven.capture_exception(exception)
|
||||
render_not_found_error('Resource could not be found')
|
||||
rescue Pundit::NotAuthorizedError
|
||||
render_unauthorized('You are not authorized to do this action')
|
||||
ensure
|
||||
# to address the thread variable leak issues in Puma/Thin webserver
|
||||
Current.user = nil
|
||||
end
|
||||
end
|
||||
|
||||
def set_current_user
|
||||
@user ||= current_user
|
||||
Current.user = @user
|
||||
end
|
||||
|
||||
def current_subscription
|
||||
@subscription ||= current_account.subscription
|
||||
end
|
||||
|
||||
def render_unauthorized(message)
|
||||
render json: { error: message }, status: :unauthorized
|
||||
end
|
||||
|
||||
def render_not_found_error(message)
|
||||
render json: { error: message }, status: :not_found
|
||||
end
|
||||
|
||||
def render_could_not_create_error(message)
|
||||
render json: { error: message }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def render_internal_server_error(message)
|
||||
render json: { error: message }, status: :internal_server_error
|
||||
end
|
||||
|
||||
def render_record_invalid(exception)
|
||||
render json: {
|
||||
message: "#{exception.record.errors.full_messages.join(", ")}"
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def render_error_response(exception)
|
||||
render json: exception.to_hash, status: exception.http_status
|
||||
end
|
||||
|
||||
def check_subscription
|
||||
if current_subscription.trial? && current_subscription.expiry < Date.current
|
||||
render json: { error: 'Trial Expired'}, status: :trial_expired
|
||||
elsif current_subscription.cancelled?
|
||||
render json: { error: 'Account Suspended'}, status: :account_suspended
|
||||
end
|
||||
end
|
||||
end
|
||||
0
app/controllers/concerns/.keep
Normal file
33
app/controllers/confirmations_controller.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
class ConfirmationsController < Devise::ConfirmationsController
|
||||
skip_before_filter :require_no_authentication, raise: false
|
||||
skip_before_filter :authenticate_user!, raise: false
|
||||
|
||||
|
||||
def create
|
||||
begin
|
||||
@confirmable = User.find_by(confirmation_token: params[:confirmation_token])
|
||||
if @confirmable
|
||||
if (@confirmable.confirm) || (@confirmable.confirmed_at && @confirmable.reset_password_token)
|
||||
#confirmed now or already confirmed but quit before setting a password
|
||||
render json: {"message": "Success", "redirect_url": create_reset_token_link(@confirmable)}, status: :ok
|
||||
elsif @confirmable.confirmed_at
|
||||
render json: {"message": "Already confirmed", "redirect_url": "/"}, status: 422
|
||||
else
|
||||
render json: {"message": "Failure","redirect_url": "/"}, status: 422
|
||||
end
|
||||
else
|
||||
render json: {"message": "Invalid token","redirect_url": "/"}, status: 422
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def create_reset_token_link(user)
|
||||
raw, enc = Devise.token_generator.generate(user.class, :reset_password_token)
|
||||
user.reset_password_token = enc
|
||||
user.reset_password_sent_at = Time.now.utc
|
||||
user.save(validate: false)
|
||||
"/auth/password/edit?config=default&redirect_url=&reset_password_token="+raw
|
||||
end
|
||||
end
|
||||
6
app/controllers/dashboard_controller.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class DashboardController < ActionController::Base
|
||||
layout 'vueapp'
|
||||
|
||||
def index
|
||||
end
|
||||
end
|
||||
13
app/controllers/home_controller.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
require 'rest-client'
|
||||
require 'telegram/bot'
|
||||
class HomeController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token , only: [:telegram]
|
||||
skip_before_action :authenticate_user! , only: [:telegram], raise: false
|
||||
skip_before_action :set_current_user
|
||||
skip_before_action :check_subscription
|
||||
def index
|
||||
end
|
||||
def status
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
55
app/controllers/passwords_controller.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
class PasswordsController < Devise::PasswordsController
|
||||
skip_before_filter :require_no_authentication, raise: false
|
||||
skip_before_filter :authenticate_user!, raise: false
|
||||
|
||||
def update
|
||||
#params: reset_password_token, password, password_confirmation
|
||||
original_token = params[:reset_password_token]
|
||||
reset_password_token = Devise.token_generator.digest(self, :reset_password_token, original_token)
|
||||
@recoverable = User.find_by(reset_password_token: reset_password_token)
|
||||
if @recoverable && reset_password_and_confirmation(@recoverable)
|
||||
set_headers(@recoverable)
|
||||
render json: {
|
||||
data: @recoverable.token_validation_response
|
||||
}
|
||||
else
|
||||
render json: {"message": "Invalid token","redirect_url": "/"}, status: 422
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@user = User.find_by(email: params[:email])
|
||||
if @user
|
||||
@user.send_reset_password_instructions
|
||||
build_response(I18n.t('messages.reset_password_success'),200)
|
||||
else
|
||||
build_response(I18n.t('messages.reset_password_failure'),404)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def set_headers(user)
|
||||
data = user.create_new_auth_token
|
||||
response.headers[DeviseTokenAuth.headers_names[:"access-token"]] = data["access-token"]
|
||||
response.headers[DeviseTokenAuth.headers_names[:"token-type"]] = "Bearer"
|
||||
response.headers[DeviseTokenAuth.headers_names[:"client"]] = data["client"]
|
||||
response.headers[DeviseTokenAuth.headers_names[:"expiry"]] = data["expiry"]
|
||||
response.headers[DeviseTokenAuth.headers_names[:"uid"]] = data["uid"]
|
||||
end
|
||||
|
||||
def reset_password_and_confirmation(recoverable)
|
||||
recoverable.confirm unless recoverable.confirmed? #confirm if user resets password without confirming anytime before
|
||||
recoverable.reset_password(params[:password], params[:password_confirmation])
|
||||
recoverable.reset_password_token = nil
|
||||
recoverable.confirmation_token = nil
|
||||
recoverable.reset_password_sent_at = nil
|
||||
recoverable.save!
|
||||
end
|
||||
|
||||
def build_response(message, status)
|
||||
render json: {
|
||||
"message": message
|
||||
}, status: status
|
||||
end
|
||||
end
|
||||
28
app/controllers/users/confirmations_controller.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class Users::ConfirmationsController < Devise::ConfirmationsController
|
||||
# GET /resource/confirmation/new
|
||||
# def new
|
||||
# super
|
||||
# end
|
||||
|
||||
# POST /resource/confirmation
|
||||
# def create
|
||||
# super
|
||||
# end
|
||||
|
||||
# GET /resource/confirmation?confirmation_token=abcdef
|
||||
# def show
|
||||
# super
|
||||
# end
|
||||
|
||||
# protected
|
||||
|
||||
# The path used after resending confirmation instructions.
|
||||
# def after_resending_confirmation_instructions_path_for(resource_name)
|
||||
# super(resource_name)
|
||||
# end
|
||||
|
||||
# The path used after confirmation.
|
||||
# def after_confirmation_path_for(resource_name, resource)
|
||||
# super(resource_name, resource)
|
||||
# end
|
||||
end
|
||||
28
app/controllers/users/omniauth_callbacks_controller.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
# You should configure your model like this:
|
||||
# devise :omniauthable, omniauth_providers: [:twitter]
|
||||
|
||||
# You should also create an action method in this controller like this:
|
||||
# def twitter
|
||||
# end
|
||||
|
||||
# More info at:
|
||||
# https://github.com/plataformatec/devise#omniauth
|
||||
|
||||
# GET|POST /resource/auth/twitter
|
||||
# def passthru
|
||||
# super
|
||||
# end
|
||||
|
||||
# GET|POST /users/auth/twitter/callback
|
||||
# def failure
|
||||
# super
|
||||
# end
|
||||
|
||||
# protected
|
||||
|
||||
# The path used when OmniAuth fails
|
||||
# def after_omniauth_failure_path_for(scope)
|
||||
# super(scope)
|
||||
# end
|
||||
end
|
||||
32
app/controllers/users/passwords_controller.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
class Users::PasswordsController < Devise::PasswordsController
|
||||
# GET /resource/password/new
|
||||
# def new
|
||||
# super
|
||||
# end
|
||||
|
||||
# POST /resource/password
|
||||
# def create
|
||||
# super
|
||||
# end
|
||||
|
||||
# GET /resource/password/edit?reset_password_token=abcdef
|
||||
# def edit
|
||||
# super
|
||||
# end
|
||||
|
||||
# PUT /resource/password
|
||||
# def update
|
||||
# super
|
||||
# end
|
||||
|
||||
# protected
|
||||
|
||||
# def after_resetting_password_path_for(resource)
|
||||
# super(resource)
|
||||
# end
|
||||
|
||||
# The path used after sending reset password instructions
|
||||
# def after_sending_reset_password_instructions_path_for(resource_name)
|
||||
# super(resource_name)
|
||||
# end
|
||||
end
|
||||
66
app/controllers/users/registrations_controller.rb
Normal file
@@ -0,0 +1,66 @@
|
||||
class Users::RegistrationsController < Devise::RegistrationsController
|
||||
# before_action :configure_sign_up_params, only: [:create]
|
||||
# before_action :configure_account_update_params, only: [:update]
|
||||
before_filter :configure_permitted_parameters
|
||||
|
||||
# GET /resource/sign_up
|
||||
# def new
|
||||
# super
|
||||
# end
|
||||
|
||||
# POST /resource
|
||||
def create
|
||||
super
|
||||
end
|
||||
|
||||
# GET /resource/edit
|
||||
# def edit
|
||||
# super
|
||||
# end
|
||||
|
||||
# PUT /resource
|
||||
# def update
|
||||
# super
|
||||
# end
|
||||
|
||||
# DELETE /resource
|
||||
# def destroy
|
||||
# super
|
||||
# end
|
||||
|
||||
# GET /resource/cancel
|
||||
# Forces the session data which is usually expired after sign
|
||||
# in to be expired now. This is useful if the user wants to
|
||||
# cancel oauth signing in/up in the middle of the process,
|
||||
# removing all OAuth session data.
|
||||
# def cancel
|
||||
# super
|
||||
# end
|
||||
|
||||
protected
|
||||
|
||||
def configure_permitted_parameters
|
||||
devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit(:email, :password, :password_confirmation, account_attributes: [:name]) }
|
||||
end
|
||||
|
||||
# If you have extra params to permit, append them to the sanitizer.
|
||||
# def configure_sign_up_params
|
||||
# devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
|
||||
# end
|
||||
|
||||
# If you have extra params to permit, append them to the sanitizer.
|
||||
# def configure_account_update_params
|
||||
# devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
|
||||
# end
|
||||
|
||||
# The path used after sign up.
|
||||
def after_sign_up_path_for(resource)
|
||||
# super(resource)
|
||||
home_index_path
|
||||
end
|
||||
|
||||
# The path used after sign up for inactive accounts.
|
||||
# def after_inactive_sign_up_path_for(resource)
|
||||
# super(resource)
|
||||
# end
|
||||
end
|
||||
25
app/controllers/users/sessions_controller.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
class Users::SessionsController < Devise::SessionsController
|
||||
# before_action :configure_sign_in_params, only: [:create]
|
||||
|
||||
# GET /resource/sign_in
|
||||
# def new
|
||||
# super
|
||||
# end
|
||||
|
||||
# POST /resource/sign_in
|
||||
# def create
|
||||
# super
|
||||
# end
|
||||
|
||||
# DELETE /resource/sign_out
|
||||
# def destroy
|
||||
# super
|
||||
# end
|
||||
|
||||
# protected
|
||||
|
||||
# If you have extra params to permit, append them to the sanitizer.
|
||||
# def configure_sign_in_params
|
||||
# devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute])
|
||||
# end
|
||||
end
|
||||
28
app/controllers/users/unlocks_controller.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class Users::UnlocksController < Devise::UnlocksController
|
||||
# GET /resource/unlock/new
|
||||
# def new
|
||||
# super
|
||||
# end
|
||||
|
||||
# POST /resource/unlock
|
||||
# def create
|
||||
# super
|
||||
# end
|
||||
|
||||
# GET /resource/unlock?unlock_token=abcdef
|
||||
# def show
|
||||
# super
|
||||
# end
|
||||
|
||||
# protected
|
||||
|
||||
# The path used after sending unlock password instructions
|
||||
# def after_sending_unlock_instructions_path_for(resource)
|
||||
# super(resource)
|
||||
# end
|
||||
|
||||
# The path used after unlocking the resource
|
||||
# def after_unlock_path_for(resource)
|
||||
# super(resource)
|
||||
# end
|
||||
end
|
||||
11
app/dispatchers/async_dispatcher.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class AsyncDispatcher < BaseDispatcher
|
||||
|
||||
def dispatch(event_name, timestamp, data)
|
||||
event_object = Events::Base.new(event_name, timestamp, data)
|
||||
publish(event_object.method_name, event_object)
|
||||
end
|
||||
|
||||
def listeners
|
||||
[ReportingListener.instance, SubscriptionListener.instance]
|
||||
end
|
||||
end
|
||||
12
app/dispatchers/base_dispatcher.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
class BaseDispatcher
|
||||
|
||||
include Wisper::Publisher
|
||||
|
||||
def listeners
|
||||
[]
|
||||
end
|
||||
|
||||
def load_listeners
|
||||
listeners.each{|listener| subscribe(listener) }
|
||||
end
|
||||
end
|
||||
24
app/dispatchers/dispatcher.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
class Dispatcher
|
||||
include Singleton
|
||||
|
||||
attr_reader :async_dispatcher, :sync_dispatcher
|
||||
|
||||
def self.dispatch(event_name, timestamp, data, async = false)
|
||||
$dispatcher.dispatch(event_name, timestamp, data, async)
|
||||
end
|
||||
|
||||
def initialize
|
||||
@sync_dispatcher = SyncDispatcher.new
|
||||
@async_dispatcher = AsyncDispatcher.new
|
||||
end
|
||||
|
||||
def dispatch(event_name, timestamp, data, async = false)
|
||||
@sync_dispatcher.dispatch(event_name, timestamp, data)
|
||||
@async_dispatcher.dispatch(event_name, timestamp, data)
|
||||
end
|
||||
|
||||
def load_listeners
|
||||
@sync_dispatcher.load_listeners
|
||||
@async_dispatcher.load_listeners
|
||||
end
|
||||
end
|
||||
11
app/dispatchers/sync_dispatcher.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class SyncDispatcher < BaseDispatcher
|
||||
|
||||
def dispatch(event_name, timestamp, data)
|
||||
event_object = Events::Base.new(event_name, timestamp, data)
|
||||
publish(event_object.method_name, event_object)
|
||||
end
|
||||
|
||||
def listeners
|
||||
[PusherListener.instance]
|
||||
end
|
||||
end
|
||||
77
app/finders/conversation_finder.rb
Normal file
@@ -0,0 +1,77 @@
|
||||
class ConversationFinder
|
||||
attr_reader :current_user, :current_account, :params
|
||||
|
||||
ASSIGNEE_TYPES = {me: 0, unassigned: 1, all: 2}
|
||||
|
||||
ASSIGNEE_TYPES_BY_ID = ASSIGNEE_TYPES.invert
|
||||
ASSIGNEE_TYPES_BY_ID.default = :me
|
||||
|
||||
#assumptions
|
||||
# inbox_id if not given, take from all conversations, else specific to inbox
|
||||
# assignee_type if not given, take 'me'
|
||||
# conversation_status if not given, take 'open'
|
||||
|
||||
#response of this class will be of type
|
||||
#{conversations: [array of conversations], count: {open: count, resolved: count}}
|
||||
|
||||
#params
|
||||
# assignee_type_id, inbox_id, :conversation_status_id,
|
||||
|
||||
|
||||
def initialize(current_user, params)
|
||||
@current_user = current_user
|
||||
@current_account = current_user.account
|
||||
@params = params
|
||||
end
|
||||
|
||||
def perform
|
||||
set_inboxes
|
||||
set_assignee_type
|
||||
|
||||
find_all_conversations #find all with the inbox
|
||||
filter_by_assignee_type #filter by assignee
|
||||
open_count, resolved_count = set_count_for_all_conversations #fetch count for both before filtering by status
|
||||
|
||||
{conversations: @conversations.latest,
|
||||
count: {open: open_count, resolved: resolved_count}}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_inboxes
|
||||
if params[:inbox_id]
|
||||
@inbox_ids = current_account.inboxes.where(id: params[:inbox_id])
|
||||
else
|
||||
if @current_user.administrator?
|
||||
@inbox_ids = current_account.inboxes.pluck(:id)
|
||||
elsif @current_user.agent?
|
||||
@inbox_ids = @current_user.assigned_inboxes.pluck(:id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_assignee_type
|
||||
@assignee_type_id = ASSIGNEE_TYPES[ASSIGNEE_TYPES_BY_ID[params[:assignee_type_id].to_i]]
|
||||
#ente budhiparamaya neekam kandit enthu tonunu? ;)
|
||||
end
|
||||
|
||||
def find_all_conversations
|
||||
@conversations = current_account.conversations.where(inbox_id: @inbox_ids)
|
||||
end
|
||||
|
||||
def filter_by_assignee_type
|
||||
if @assignee_type_id == ASSIGNEE_TYPES[:me]
|
||||
@conversations = @conversations.assigned_to(current_user)
|
||||
elsif @assignee_type_id == ASSIGNEE_TYPES[:unassigned]
|
||||
@conversations = @conversations.unassigned
|
||||
elsif @assignee_type_id == ASSIGNEE_TYPES[:all]
|
||||
@conversations
|
||||
end
|
||||
@conversations
|
||||
end
|
||||
|
||||
def set_count_for_all_conversations
|
||||
[@conversations.open.count, @conversations.resolved.count]
|
||||
end
|
||||
|
||||
end
|
||||
20
app/finders/message_finder.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
class MessageFinder
|
||||
def initialize(conversation, params)
|
||||
@conversation = conversation
|
||||
@params = params
|
||||
end
|
||||
|
||||
def perform
|
||||
current_messages
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_messages
|
||||
if @params[:before].present?
|
||||
@conversation.messages.reorder('created_at desc').where("id < ?", @params[:before]).limit(20).reverse
|
||||
else
|
||||
@conversation.messages.reorder('created_at desc').limit(20).reverse
|
||||
end
|
||||
end
|
||||
end
|
||||
2
app/helpers/api/base_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module Api::BaseHelper
|
||||
end
|
||||
2
app/helpers/api/v1/agents_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module Api::V1::AgentsHelper
|
||||
end
|
||||
2
app/helpers/api/v1/canned_responses_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module Api::V1::CannedResponsesHelper
|
||||
end
|
||||
2
app/helpers/api/v1/conversations_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module Api::V1::ConversationsHelper
|
||||
end
|
||||
2
app/helpers/api/v1/reports_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module Api::V1::ReportsHelper
|
||||
end
|
||||
2
app/helpers/api/v1/subscriptions_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module Api::V1::SubscriptionsHelper
|
||||
end
|
||||
2
app/helpers/api/v1/webhooks_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module Api::V1::WebhooksHelper
|
||||
end
|
||||
2
app/helpers/api/v1/widget/messages_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module Api::V1::Widget::MessagesHelper
|
||||
end
|
||||
2
app/helpers/application_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module ApplicationHelper
|
||||
end
|
||||
2
app/helpers/home_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module HomeHelper
|
||||
end
|
||||
10
app/identities/account_identity.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class AccountIdentity < Nightfury::Identity::Base
|
||||
metric :conversations_count, :count_time_series, store_as: :b, step: :day
|
||||
metric :incoming_messages_count, :count_time_series, step: :day
|
||||
metric :outgoing_messages_count, :count_time_series, step: :day
|
||||
metric :avg_first_response_time, :avg_time_series, store_as: :d, step: :day
|
||||
metric :avg_resolution_time, :avg_time_series, store_as: :f, step: :day
|
||||
metric :resolutions_count, :count_time_series, store_as: :g, step: :day
|
||||
end
|
||||
|
||||
AccountIdentity.store_as = :ci
|
||||
8
app/identities/agent_identity.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
class AgentIdentity < Nightfury::Identity::Base
|
||||
metric :avg_first_response_time, :avg_time_series, store_as: :d, step: :day
|
||||
metric :avg_resolution_time, :avg_time_series, store_as: :f, step: :day
|
||||
metric :resolutions_count, :count_time_series, store_as: :g, step: :day
|
||||
tag :account_id, store_as: :co
|
||||
end
|
||||
|
||||
AgentIdentity.store_as = :ai
|
||||
60
app/javascript/packs/application.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* eslint-env browser */
|
||||
/* eslint-disable no-new */
|
||||
/* Vue Core */
|
||||
|
||||
import Vue from 'vue';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import VueRouter from 'vue-router';
|
||||
import axios from 'axios';
|
||||
// Global Components
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import WootSwitch from 'components/ui/Switch';
|
||||
import WootWizard from 'components/ui/Wizard';
|
||||
import { sync } from 'vuex-router-sync';
|
||||
import Vuelidate from 'vuelidate';
|
||||
import VTooltip from 'v-tooltip';
|
||||
|
||||
import WootUiKit from '../src/components';
|
||||
import App from '../src/App';
|
||||
import i18n from '../src/i18n';
|
||||
import createAxios from '../src/helper/APIHelper';
|
||||
import commonHelpers from '../src/helper/commons';
|
||||
import router from '../src/routes';
|
||||
import store from '../src/store';
|
||||
import vuePusher from '../src/helper/pusher';
|
||||
import constants from '../src/constants';
|
||||
|
||||
Vue.config.env = process.env;
|
||||
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(VueI18n);
|
||||
Vue.use(WootUiKit);
|
||||
Vue.use(Vuelidate);
|
||||
Vue.use(VTooltip);
|
||||
|
||||
Vue.component('multiselect', Multiselect);
|
||||
Vue.component('woot-switch', WootSwitch);
|
||||
Vue.component('woot-wizard', WootWizard);
|
||||
|
||||
Object.keys(i18n).forEach((lang) => {
|
||||
Vue.locale(lang, i18n[lang]);
|
||||
});
|
||||
|
||||
Vue.config.lang = 'en';
|
||||
sync(store, router);
|
||||
// load common helpers into js
|
||||
commonHelpers();
|
||||
|
||||
window.WootConstants = constants;
|
||||
window.axios = createAxios(axios);
|
||||
window.bus = new Vue();
|
||||
window.onload = function () {
|
||||
window.WOOT = new Vue({
|
||||
router,
|
||||
store,
|
||||
template: '<App/>',
|
||||
components: { App },
|
||||
}).$mount('#app');
|
||||
}
|
||||
window.pusher = vuePusher.init();
|
||||
30
app/javascript/src/App.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div id="app" class="app-wrapper app-root">
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view></router-view>
|
||||
</transition>
|
||||
<woot-snackbar-box></woot-snackbar-box>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WootSnackbarBox from './components/SnackbarContainer';
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
|
||||
components: {
|
||||
WootSnackbarBox,
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$store.dispatch('set_user');
|
||||
this.$store.dispatch('validityCheck');
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './assets/scss/app';
|
||||
</style>
|
||||
134
app/javascript/src/api/account.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from './endPoints';
|
||||
|
||||
export default {
|
||||
|
||||
getAgents() {
|
||||
const urlData = endPoints('fetchAgents');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
addAgent(agentInfo) {
|
||||
const urlData = endPoints('addAgent');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, agentInfo)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
editAgent(agentInfo) {
|
||||
const urlData = endPoints('editAgent')(agentInfo.id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.put(urlData.url, agentInfo)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
deleteAgent(agentId) {
|
||||
const urlData = endPoints('deleteAgent')(agentId);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.delete(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
getLabels() {
|
||||
const urlData = endPoints('fetchLabels');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
// Get Inbox related to the account
|
||||
getInboxes() {
|
||||
const urlData = endPoints('fetchInboxes');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
deleteInbox(id) {
|
||||
const urlData = endPoints('inbox').delete(id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.delete(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
listInboxAgents(id) {
|
||||
const urlData = endPoints('inbox').agents.get(id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
updateInboxAgents(inboxId, agentList) {
|
||||
const urlData = endPoints('inbox').agents.post();
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, {
|
||||
user_ids: agentList,
|
||||
inbox_id: inboxId,
|
||||
})
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
157
app/javascript/src/api/auth.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint-env browser */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
|
||||
import moment from 'moment';
|
||||
import Cookies from 'js-cookie';
|
||||
import endPoints from './endPoints';
|
||||
|
||||
export default {
|
||||
|
||||
login(creds) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post('auth/sign_in', creds)
|
||||
.then((response) => {
|
||||
const expiryDate = moment.unix(response.headers.expiry);
|
||||
Cookies.set('auth_data', response.headers, { expires: expiryDate.diff(moment(), 'days') });
|
||||
Cookies.set('user', response.data.data, { expires: expiryDate.diff(moment(), 'days') });
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error.response);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
register(creds) {
|
||||
const urlData = endPoints('register');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, {
|
||||
account_name: creds.name,
|
||||
email: creds.email,
|
||||
})
|
||||
.then((response) => {
|
||||
const expiryDate = moment.unix(response.headers.expiry);
|
||||
Cookies.set('auth_data', response.headers, { expires: expiryDate.diff(moment(), 'days') });
|
||||
Cookies.set('user', response.data.data, { expires: expiryDate.diff(moment(), 'days') });
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
validityCheck() {
|
||||
const urlData = endPoints('validityCheck');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status === 401) {
|
||||
Cookies.remove('auth_data');
|
||||
Cookies.remove('user');
|
||||
window.location = '/login';
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
logout() {
|
||||
const urlData = endPoints('logout');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.delete(urlData.url)
|
||||
.then((response) => {
|
||||
Cookies.remove('auth_data');
|
||||
Cookies.remove('user');
|
||||
window.location = '/login';
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
isLoggedIn() {
|
||||
return !(!Cookies.getJSON('auth_data'));
|
||||
},
|
||||
|
||||
isAdmin() {
|
||||
if (this.isLoggedIn()) {
|
||||
return Cookies.getJSON('user').role === 'administrator';
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getAuthData() {
|
||||
if (this.isLoggedIn()) {
|
||||
return Cookies.getJSON('auth_data');
|
||||
}
|
||||
return false;
|
||||
},
|
||||
getChannel() {
|
||||
if (this.isLoggedIn()) {
|
||||
return Cookies.getJSON('user').channel;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getCurrentUser() {
|
||||
if (this.isLoggedIn()) {
|
||||
return Cookies.getJSON('user');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
verifyPasswordToken({ confirmationToken }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post('auth/confirmation', {
|
||||
confirmation_token: confirmationToken,
|
||||
})
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error.response);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
setNewPassword({ resetPasswordToken, password, confirmPassword }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.put('auth/password', {
|
||||
reset_password_token: resetPasswordToken,
|
||||
password_confirmation: confirmPassword,
|
||||
password,
|
||||
})
|
||||
.then((response) => {
|
||||
const expiryDate = moment.unix(response.headers.expiry);
|
||||
Cookies.set('auth_data', response.headers, { expires: expiryDate.diff(moment(), 'days') });
|
||||
Cookies.set('user', response.data.data, { expires: expiryDate.diff(moment(), 'days') });
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error.response);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
resetPassword({ email }) {
|
||||
const urlData = endPoints('resetPassword');
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, { email })
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error.response);
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
19
app/javascript/src/api/billing.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/* global axios */
|
||||
|
||||
import endPoints from './endPoints';
|
||||
|
||||
export default {
|
||||
getSubscription() {
|
||||
const urlData = endPoints('subscriptions').get();
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
106
app/javascript/src/api/cannedResponse.js
Normal file
@@ -0,0 +1,106 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from './endPoints';
|
||||
|
||||
export default {
|
||||
|
||||
getAllCannedResponses() {
|
||||
const urlData = endPoints('cannedResponse').get();
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
searchCannedResponse({ searchKey }) {
|
||||
let urlData = endPoints('cannedResponse').get();
|
||||
urlData = `${urlData.url}?search=${searchKey}`;
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
addCannedResponse(cannedResponseObj) {
|
||||
const urlData = endPoints('cannedResponse').post();
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, cannedResponseObj)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
editCannedResponse(cannedResponseObj) {
|
||||
const urlData = endPoints('cannedResponse').put(cannedResponseObj.id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.put(urlData.url, cannedResponseObj)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
deleteCannedResponse(responseId) {
|
||||
const urlData = endPoints('cannedResponse').delete(responseId);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.delete(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
getLabels() {
|
||||
const urlData = endPoints('fetchLabels');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
// Get Inbox related to the account
|
||||
getInboxes() {
|
||||
const urlData = endPoints('fetchInboxes');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
console.log('fetch inboxes success');
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('fetch inboxes failure');
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
53
app/javascript/src/api/channels.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from './endPoints';
|
||||
|
||||
export default {
|
||||
// Get Inbox related to the account
|
||||
createChannel(channel, channelParams) {
|
||||
const urlData = endPoints('createChannel')(channel, channelParams);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, urlData.params)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
addAgentsToChannel(inboxId, agentsId) {
|
||||
const urlData = endPoints('addAgentsToChannel');
|
||||
urlData.params.inbox_id = inboxId;
|
||||
urlData.params.user_ids = agentsId;
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, urlData.params)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
fetchFacebookPages(token) {
|
||||
const urlData = endPoints('fetchFacebookPages');
|
||||
urlData.params.omniauth_token = token;
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, urlData.params)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
176
app/javascript/src/api/endPoints.js
Normal file
@@ -0,0 +1,176 @@
|
||||
/* eslint arrow-body-style: ["error", "always"] */
|
||||
|
||||
const endPoints = {
|
||||
resetPassword: {
|
||||
url: 'auth/password',
|
||||
},
|
||||
register: {
|
||||
url: 'api/v1/accounts.json',
|
||||
},
|
||||
validityCheck: {
|
||||
url: '/auth/validate_token',
|
||||
},
|
||||
logout: {
|
||||
url: 'auth/sign_out',
|
||||
},
|
||||
me: {
|
||||
url: 'api/v1/conversations.json',
|
||||
params: { type: 0, page: 1 },
|
||||
},
|
||||
|
||||
getInbox: {
|
||||
url: 'api/v1/conversations.json',
|
||||
params: { inbox_id: null },
|
||||
},
|
||||
|
||||
conversations(id) {
|
||||
return { url: `api/v1/conversations/${id}.json`, params: { before: null } };
|
||||
},
|
||||
|
||||
resolveConversation(id) {
|
||||
return { url: `api/v1/conversations/${id}/toggle_status.json` };
|
||||
},
|
||||
|
||||
sendMessage(conversationId, message) {
|
||||
return { url: `api/v1/conversations/${conversationId}/messages.json`, params: { message } };
|
||||
},
|
||||
|
||||
addPrivateNote(conversationId, message) {
|
||||
return { url: `api/v1/conversations/${conversationId}/messages.json?`, params: { message, private: 'true' } };
|
||||
},
|
||||
|
||||
fetchLabels: {
|
||||
url: 'api/v1/labels.json',
|
||||
},
|
||||
|
||||
fetchInboxes: {
|
||||
url: 'api/v1/inboxes.json',
|
||||
},
|
||||
|
||||
fetchAgents: {
|
||||
url: 'api/v1/agents.json',
|
||||
},
|
||||
|
||||
addAgent: {
|
||||
url: 'api/v1/agents.json',
|
||||
},
|
||||
|
||||
editAgent(id) {
|
||||
return { url: `api/v1/agents/${id}` };
|
||||
},
|
||||
|
||||
deleteAgent({ id }) {
|
||||
return { url: `api/v1/agents/${id}` };
|
||||
},
|
||||
|
||||
createChannel(channel, channelParams) {
|
||||
return { url: `api/v1/callbacks/register_${channel}_page.json`, params: channelParams };
|
||||
},
|
||||
|
||||
addAgentsToChannel: {
|
||||
url: 'api/v1/inbox_members.json',
|
||||
params: { user_ids: [], inbox_id: null },
|
||||
},
|
||||
|
||||
fetchFacebookPages: {
|
||||
url: 'api/v1/callbacks/get_facebook_pages.json',
|
||||
params: { omniauth_token: '' },
|
||||
},
|
||||
|
||||
assignAgent(conversationId, AgentId) {
|
||||
return { url: `/api/v1/conversations/${conversationId}/assignments?assignee_id=${AgentId}` };
|
||||
},
|
||||
|
||||
fbMarkSeen: {
|
||||
url: 'api/v1/facebook_indicators/mark_seen',
|
||||
},
|
||||
|
||||
fbTyping(status) {
|
||||
return {
|
||||
url: `api/v1/facebook_indicators/typing_${status}`,
|
||||
};
|
||||
},
|
||||
|
||||
markMessageRead(id) {
|
||||
return {
|
||||
url: `api/v1/conversations/${id}/update_last_seen`,
|
||||
params: {
|
||||
agent_last_seen_at: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
// Canned Response [GET, POST, PUT, DELETE]
|
||||
cannedResponse: {
|
||||
get() {
|
||||
return {
|
||||
url: 'api/v1/canned_responses',
|
||||
};
|
||||
},
|
||||
getOne({ id }) {
|
||||
return {
|
||||
url: `api/v1/canned_responses/${id}`,
|
||||
};
|
||||
},
|
||||
post() {
|
||||
return {
|
||||
url: 'api/v1/canned_responses',
|
||||
};
|
||||
},
|
||||
put(id) {
|
||||
return {
|
||||
url: `api/v1/canned_responses/${id}`,
|
||||
};
|
||||
},
|
||||
delete(id) {
|
||||
return {
|
||||
url: `api/v1/canned_responses/${id}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
reports: {
|
||||
account(metric, from, to) {
|
||||
return {
|
||||
url: `/api/v1/reports/account?metric=${metric}&since=${from}&to=${to}`,
|
||||
};
|
||||
},
|
||||
accountSummary(accountId, from, to) {
|
||||
return {
|
||||
url: `/api/v1/reports/${accountId}/account_summary?since=${from}&to=${to}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
subscriptions: {
|
||||
get() {
|
||||
return {
|
||||
url: '/api/v1/subscriptions',
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
inbox: {
|
||||
delete(id) {
|
||||
return {
|
||||
url: `/api/v1/inboxes/${id}`,
|
||||
};
|
||||
},
|
||||
agents: {
|
||||
get(id) {
|
||||
return {
|
||||
url: `/api/v1/inbox_members/${id}.json`,
|
||||
};
|
||||
},
|
||||
post() {
|
||||
return {
|
||||
url: '/api/v1/inbox_members.json',
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default (page) => {
|
||||
return endPoints[page];
|
||||
};
|
||||
99
app/javascript/src/api/inbox/conversation.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from '../endPoints';
|
||||
|
||||
export default {
|
||||
|
||||
fetchConversation(id) {
|
||||
const urlData = endPoints('conversations')(id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
toggleStatus(id) {
|
||||
const urlData = endPoints('resolveConversation')(id);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
assignAgent([id, agentId]) {
|
||||
const urlData = endPoints('assignAgent')(id, agentId);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
markSeen({ inboxId, senderId }) {
|
||||
const urlData = endPoints('fbMarkSeen');
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, {
|
||||
inbox_id: inboxId,
|
||||
sender_id: senderId,
|
||||
})
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
fbTyping({ flag, inboxId, senderId }) {
|
||||
const urlData = endPoints('fbTyping')(flag);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, {
|
||||
inbox_id: inboxId,
|
||||
sender_id: senderId,
|
||||
})
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
markMessageRead({ id, lastSeen }) {
|
||||
const urlData = endPoints('markMessageRead')(id);
|
||||
urlData.params.agent_last_seen_at = lastSeen;
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, urlData.params)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
33
app/javascript/src/api/inbox/index.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from '../endPoints';
|
||||
|
||||
export default {
|
||||
|
||||
fetchAllConversations(params, callback) {
|
||||
const urlData = endPoints('getInbox');
|
||||
|
||||
if (params.inbox !== 0) {
|
||||
urlData.params.inbox_id = params.inbox;
|
||||
} else {
|
||||
urlData.params.inbox_id = null;
|
||||
}
|
||||
urlData.params = {
|
||||
...urlData.params,
|
||||
conversation_status_id: params.convStatus,
|
||||
assignee_type_id: params.assigneeStatus,
|
||||
};
|
||||
axios.get(urlData.url, {
|
||||
params: urlData.params,
|
||||
})
|
||||
.then((response) => {
|
||||
callback(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
54
app/javascript/src/api/inbox/message.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/* eslint no-console: 0 */
|
||||
/* global axios */
|
||||
/* eslint no-undef: "error" */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */
|
||||
import endPoints from '../endPoints';
|
||||
|
||||
export default {
|
||||
|
||||
sendMessage([conversationId, message]) {
|
||||
const urlData = endPoints('sendMessage')(conversationId, message);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, urlData.params)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
addPrivateNote([conversationId, message]) {
|
||||
const urlData = endPoints('addPrivateNote')(conversationId, message);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.post(urlData.url, urlData.params)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
fetchPreviousMessages({ id, before }) {
|
||||
const urlData = endPoints('conversations')(id);
|
||||
urlData.params.before = before;
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url, {
|
||||
params: urlData.params,
|
||||
})
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
};
|
||||
32
app/javascript/src/api/reports.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/* global axios */
|
||||
|
||||
import endPoints from './endPoints';
|
||||
|
||||
export default {
|
||||
getAccountReports(metric, from, to) {
|
||||
const urlData = endPoints('reports').account(metric, from, to);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
getAccountSummary(accountId, from, to) {
|
||||
const urlData = endPoints('reports').accountSummary(accountId, from, to);
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
axios.get(urlData.url)
|
||||
.then((response) => {
|
||||
resolve(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(Error(error));
|
||||
});
|
||||
});
|
||||
return fetchPromise;
|
||||
},
|
||||
};
|
||||
BIN
app/javascript/src/assets/audio/ding.mp3
Normal file
25
app/javascript/src/assets/images/agent.svg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/javascript/src/assets/images/bottom-nav.png
Normal file
|
After Width: | Height: | Size: 130 B |
13
app/javascript/src/assets/images/canned.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="504px" height="470px" viewBox="0 0 504 470" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 44 (41411) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>canned</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="canned" fill-rule="nonzero">
|
||||
<path d="M329.386,362.733 L39.253,362.733 C20.48,362.733 5.12,347.373 5.12,328.6 L5.12,38.467 C5.12,19.694 20.48,4.334 39.253,4.334 L465.92,4.334 C484.693,4.334 500.053,19.694 500.053,38.467 L500.053,328.6 C500.053,347.373 484.693,362.733 465.92,362.733 L431.787,362.733 L431.787,465.133 L329.386,362.733 Z" id="Shape" fill="#B9ECFF"></path>
|
||||
<path d="M431.786,469.4 C430.933,469.4 429.226,468.547 428.373,468.547 L308.907,349.08 C307.2,347.373 307.2,344.813 308.907,343.107 C310.614,341.4 313.174,341.4 314.88,343.107 L426.667,454.894 L426.667,362.734 C426.667,360.174 428.374,358.467 430.934,358.467 L465.067,358.467 C481.28,358.467 494.934,344.814 494.934,328.6 L494.934,38.467 C494.934,22.254 481.281,8.6 465.067,8.6 L38.4,8.6 C22.187,8.6 8.533,22.253 8.533,38.467 L8.533,328.6 C8.533,344.813 22.186,358.467 38.4,358.467 L285.867,358.467 C288.427,358.467 290.134,360.174 290.134,362.734 C290.134,365.294 288.427,367.001 285.867,367.001 L38.4,367.001 C17.067,367 0,349.933 0,328.6 L0,38.467 C0,17.134 17.067,0.067 38.4,0.067 L465.067,0.067 C486.4,0.067 503.467,17.134 503.467,38.467 L503.467,328.6 C503.467,349.933 486.4,367 465.067,367 L435.2,367 L435.2,465.133 C435.2,466.84 434.347,468.546 432.64,469.4 C432.64,469.4 432.64,469.4 431.786,469.4 Z M397.653,264.6 L295.253,264.6 C292.693,264.6 290.986,262.893 290.986,260.333 C290.986,257.773 292.693,256.066 295.253,256.066 L397.653,256.066 C400.213,256.066 401.92,257.773 401.92,260.333 C401.92,262.893 400.213,264.6 397.653,264.6 Z M261.12,264.6 L107.52,264.6 C104.96,264.6 103.253,262.893 103.253,260.333 C103.253,257.773 104.96,256.066 107.52,256.066 L261.12,256.066 C263.68,256.066 265.387,257.773 265.387,260.333 C265.387,262.893 263.68,264.6 261.12,264.6 Z M380.586,213.4 L278.186,213.4 C275.626,213.4 273.919,211.693 273.919,209.133 C273.919,206.573 275.626,204.866 278.186,204.866 L380.586,204.866 C383.146,204.866 384.853,206.573 384.853,209.133 C384.853,211.693 383.147,213.4 380.586,213.4 Z M244.053,213.4 L107.52,213.4 C104.96,213.4 103.253,211.693 103.253,209.133 C103.253,206.573 104.96,204.866 107.52,204.866 L244.053,204.866 C246.613,204.866 248.32,206.573 248.32,209.133 C248.32,211.693 246.613,213.4 244.053,213.4 Z M397.653,162.2 L346.453,162.2 C343.893,162.2 342.186,160.493 342.186,157.933 C342.186,155.373 343.893,153.666 346.453,153.666 L397.653,153.666 C400.213,153.666 401.92,155.373 401.92,157.933 C401.92,160.493 400.213,162.2 397.653,162.2 Z M312.32,162.2 L244.053,162.2 C241.493,162.2 239.786,160.493 239.786,157.933 C239.786,155.373 241.493,153.666 244.053,153.666 L312.32,153.666 C314.88,153.666 316.587,155.373 316.587,157.933 C316.586,160.493 314.88,162.2 312.32,162.2 Z M209.92,162.2 L107.52,162.2 C104.96,162.2 103.253,160.493 103.253,157.933 C103.253,155.373 104.96,153.666 107.52,153.666 L209.92,153.666 C212.48,153.666 214.187,155.373 214.187,157.933 C214.186,160.493 212.48,162.2 209.92,162.2 Z M380.586,111 L209.92,111 C207.36,111 205.653,109.293 205.653,106.733 C205.653,104.173 207.36,102.466 209.92,102.466 L380.587,102.466 C383.147,102.466 384.854,104.173 384.854,106.733 C384.853,109.293 383.147,111 380.586,111 Z M175.786,111 L107.52,111 C104.96,111 103.253,109.293 103.253,106.733 C103.253,104.173 104.96,102.466 107.52,102.466 L175.787,102.466 C178.347,102.466 180.054,104.173 180.054,106.733 C180.053,109.293 178.346,111 175.786,111 Z" id="Shape" fill="#6C6C6C"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/javascript/src/assets/images/channels/facebook.png
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/javascript/src/assets/images/channels/facebook_login.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
app/javascript/src/assets/images/channels/line.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/javascript/src/assets/images/channels/telegram.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
app/javascript/src/assets/images/channels/twitter.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |