Initial Commit

Co-authored-by: Subin <subinthattaparambil@gmail.com>
Co-authored-by: Manoj <manojmj92@gmail.com>
Co-authored-by: Nithin <webofnithin@gmail.com>
This commit is contained in:
Pranav Raj Sreepuram 2019-08-14 15:18:44 +05:30
commit 2a34255e0b
537 changed files with 27318 additions and 0 deletions

1
.browserslistrc Normal file
View file

@ -0,0 +1 @@
defaults

26
.gitignore vendored Normal file
View file

@ -0,0 +1,26 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore Byebug command history file.
.byebug_history
config/database.yml
.DS_Store
*.log
# Ignore application configuration
node_modules

13
Capfile Normal file
View file

@ -0,0 +1,13 @@
# Load DSL and Setup Up Stages
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/rails'
require 'capistrano/bundler'
require 'capistrano/rvm'
require 'capistrano/puma'
install_plugin Capistrano::Puma
# Loads custom tasks from `lib/capistrano/tasks' if you have any defined.
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

69
Gemfile Normal file
View file

@ -0,0 +1,69 @@
source 'https://rubygems.org'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
gem 'sass-rails', '~> 5.0'
gem 'puma', '~> 3.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.2'
gem 'therubyracer', platforms: :ruby
gem 'jquery-rails'
gem 'jbuilder', '~> 2.5'
gem 'redis', '~> 3.0'
gem 'devise'
gem 'pg'
gem 'facebook-messenger', '~> 0.11.1'
gem 'sidekiq'
gem "koala"
gem 'omniauth-facebook'
gem 'rest-client'
gem 'telegram-bot-ruby'
gem 'devise_token_auth'
gem 'pusher'
gem 'responders'
gem 'kaminari'
gem 'rack-cors', :require => 'rack/cors'
gem 'acts-as-taggable-on', '~> 4.0'
gem 'sinatra', github: 'sinatra'
gem 'wisper', '2.0.0'
gem 'nightfury', '~> 1.0', '>= 1.0.1'
gem 'redis-namespace'
gem 'redis-rack-cache'
gem 'redis-rails'
gem "figaro"
gem "pundit"
gem 'carrierwave-aws'
gem "mini_magick"
gem "sentry-raven"
gem "valid_email2"
gem 'hashie'
gem 'chargebee', '~>2'
gem 'poltergeist'
gem 'phantomjs', :require => 'phantomjs/poltergeist'
gem 'time_diff'
gem 'fog-digitalocean'
gem 'fog-aws'
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platform: :mri
gem 'capistrano', require: false
gem 'capistrano-rvm', require: false
gem 'capistrano-rails', require: false
gem 'capistrano-bundler', require: false
gem 'capistrano3-puma', require: false
end
group :development do
# Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
gem 'web-console'
gem 'listen', '~> 3.0.5'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
gem 'seed_dump'
gem 'mailcatcher'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
gem 'webpacker'

491
Gemfile.lock Normal file
View file

@ -0,0 +1,491 @@
GIT
remote: git://github.com/sinatra/sinatra.git
revision: 8fdd35c731ec6915bae393c6383b2357450665f0
specs:
rack-protection (2.0.0.rc2)
rack
sinatra (2.0.0.rc2)
mustermann (~> 1.0)
rack (~> 2.0)
rack-protection (= 2.0.0.rc2)
tilt (~> 2.0)
GEM
remote: https://rubygems.org/
specs:
actioncable (5.0.2)
actionpack (= 5.0.2)
nio4r (>= 1.2, < 3.0)
websocket-driver (~> 0.6.1)
actionmailer (5.0.2)
actionpack (= 5.0.2)
actionview (= 5.0.2)
activejob (= 5.0.2)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.0.2)
actionview (= 5.0.2)
activesupport (= 5.0.2)
rack (~> 2.0)
rack-test (~> 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.0.2)
activesupport (= 5.0.2)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.0.2)
activesupport (= 5.0.2)
globalid (>= 0.3.6)
activemodel (5.0.2)
activesupport (= 5.0.2)
activerecord (5.0.2)
activemodel (= 5.0.2)
activesupport (= 5.0.2)
arel (~> 7.0)
activesupport (5.0.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7)
minitest (~> 5.1)
tzinfo (~> 1.1)
acts-as-taggable-on (4.0.0)
activerecord (>= 4.0)
addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
airbrussh (1.2.0)
sshkit (>= 1.6.1, != 1.7.0)
arel (7.1.4)
aws-sdk (2.9.11)
aws-sdk-resources (= 2.9.11)
aws-sdk-core (2.9.11)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0)
aws-sdk-resources (2.9.11)
aws-sdk-core (= 2.9.11)
aws-sigv4 (1.0.0)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
bcrypt (3.1.11)
bindex (0.5.0)
builder (3.2.3)
byebug (9.0.6)
capistrano (3.8.1)
airbrussh (>= 1.0.0)
i18n
rake (>= 10.0.0)
sshkit (>= 1.9.0)
capistrano-bundler (1.2.0)
capistrano (~> 3.1)
sshkit (~> 1.2)
capistrano-rails (1.2.3)
capistrano (~> 3.1)
capistrano-bundler (~> 1.1)
capistrano-rvm (0.1.2)
capistrano (~> 3.0)
sshkit (~> 1.2)
capistrano3-puma (3.1.0)
capistrano (~> 3.7)
capistrano-bundler
puma (~> 3.4)
capybara (2.14.0)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
carrierwave (1.3.1)
activemodel (>= 4.0.0)
activesupport (>= 4.0.0)
mime-types (>= 1.16)
carrierwave-aws (1.1.0)
aws-sdk (~> 2.0)
carrierwave (>= 0.7, < 2.0)
chargebee (2.2.7)
json_pure (~> 1.5)
rest-client (~> 1.4)
cliver (0.3.2)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
coffee-rails (4.2.1)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.2.x)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.12.2)
concurrent-ruby (1.1.4)
connection_pool (2.2.1)
daemons (1.2.4)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devise (4.2.0)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.1)
responders
warden (~> 1.2.3)
devise_token_auth (0.1.40)
devise (> 3.5.2, <= 4.2)
rails (< 6)
domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0)
equalizer (0.0.11)
erubis (2.7.0)
eventmachine (1.0.9.1)
excon (0.62.0)
execjs (2.7.0)
facebook-messenger (0.11.1)
httparty (~> 0.13, >= 0.13.7)
rack (>= 1.6.4)
faraday (0.11.0)
multipart-post (>= 1.2, < 3)
ffi (1.9.18)
figaro (1.1.1)
thor (~> 0.14)
fog-aws (3.3.0)
fog-core (~> 2.1)
fog-json (~> 1.1)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
fog-core (2.1.2)
builder
excon (~> 0.58)
formatador (~> 0.2)
mime-types
fog-digitalocean (0.4.0)
fog-core
fog-json
fog-xml
ipaddress (>= 0.5)
fog-json (1.2.0)
fog-core
multi_json (~> 1.10)
fog-xml (0.1.3)
fog-core
nokogiri (>= 1.5.11, < 2.0.0)
formatador (0.2.5)
globalid (0.4.0)
activesupport (>= 4.2.0)
haml (4.0.7)
tilt
hashie (3.5.5)
http-cookie (1.0.3)
domain_name (~> 0.5)
httparty (0.14.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
ipaddress (0.8.3)
jbuilder (2.6.3)
activesupport (>= 3.0.0, < 5.2)
multi_json (~> 1.2)
jmespath (1.3.1)
jquery-rails (4.3.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.1.0)
json_pure (1.8.6)
jwt (1.5.6)
kaminari (1.0.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.0.1)
kaminari-activerecord (= 1.0.1)
kaminari-core (= 1.0.1)
kaminari-actionview (1.0.1)
actionview
kaminari-core (= 1.0.1)
kaminari-activerecord (1.0.1)
activerecord
kaminari-core (= 1.0.1)
kaminari-core (1.0.1)
koala (3.0.0)
addressable
faraday
json (>= 1.8)
libv8 (3.16.14.19)
listen (3.0.8)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
loofah (2.0.3)
nokogiri (>= 1.5.9)
mail (2.6.4)
mime-types (>= 1.16, < 4)
mailcatcher (0.2.4)
eventmachine
haml
i18n
json
mail
sinatra
skinny (>= 0.1.2)
sqlite3-ruby
thin
method_source (0.8.2)
mime-types (2.99.3)
mini_magick (4.7.0)
mini_portile2 (2.1.0)
minitest (5.11.3)
multi_json (1.12.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
mustermann (1.0.0)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (4.1.0)
netrc (0.11.0)
nightfury (1.0.1)
nio4r (2.0.0)
nokogiri (1.7.1)
mini_portile2 (~> 2.1.0)
oauth2 (1.3.1)
faraday (>= 0.8, < 0.12)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
omniauth (1.6.1)
hashie (>= 3.4.6, < 3.6.0)
rack (>= 1.6.2, < 3)
omniauth-facebook (4.0.0)
omniauth-oauth2 (~> 1.2)
omniauth-oauth2 (1.4.0)
oauth2 (~> 1.0)
omniauth (~> 1.2)
orm_adapter (0.5.0)
pg (0.20.0)
phantomjs (2.1.1.0)
poltergeist (1.15.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
websocket-driver (>= 0.2.0)
public_suffix (2.0.5)
puma (3.8.2)
pundit (1.1.0)
activesupport (>= 3.0.0)
pusher (1.3.1)
httpclient (~> 2.7)
multi_json (~> 1.0)
pusher-signature (~> 0.1.8)
pusher-signature (0.1.8)
rack (2.0.1)
rack-cache (1.6.1)
rack (>= 0.4)
rack-cors (0.4.1)
rack-proxy (0.6.5)
rack
rack-test (0.6.3)
rack (>= 1.0)
rails (5.0.2)
actioncable (= 5.0.2)
actionmailer (= 5.0.2)
actionpack (= 5.0.2)
actionview (= 5.0.2)
activejob (= 5.0.2)
activemodel (= 5.0.2)
activerecord (= 5.0.2)
activesupport (= 5.0.2)
bundler (>= 1.3.0, < 2.0)
railties (= 5.0.2)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.2)
activesupport (>= 4.2.0, < 6.0)
nokogiri (~> 1.6)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
railties (5.0.2)
actionpack (= 5.0.2)
activesupport (= 5.0.2)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rake (12.0.0)
rb-fsevent (0.9.8)
rb-inotify (0.9.8)
ffi (>= 0.5.0)
redis (3.3.3)
redis-actionpack (5.0.1)
actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 1.4.0)
redis-activesupport (5.0.2)
activesupport (>= 3, < 6)
redis-store (~> 1.3.0)
redis-namespace (1.5.3)
redis (~> 3.0, >= 3.0.4)
redis-rack (2.0.2)
rack (>= 1.5, < 3)
redis-store (>= 1.2, < 1.4)
redis-rack-cache (2.0.1)
rack-cache (~> 1.6.0)
redis-store (~> 1.3.0)
redis-rails (5.0.2)
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
redis-store (1.3.0)
redis (>= 2.2)
ref (2.0.0)
responders (2.3.0)
railties (>= 4.2.0, < 5.1)
rest-client (1.8.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
sass (3.4.23)
sass-rails (5.0.6)
railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
seed_dump (3.2.4)
activerecord (>= 4)
activesupport (>= 4)
sentry-raven (2.4.0)
faraday (>= 0.7.6, < 1.0)
sidekiq (4.2.10)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
redis (~> 3.2, >= 3.2.1)
skinny (0.2.4)
eventmachine (~> 1.0.0)
thin (>= 1.5, < 1.7)
spring (2.0.1)
activesupport (>= 4.2)
spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0)
spring (>= 1.2, < 3.0)
sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.0)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.3.13)
sqlite3-ruby (1.3.3)
sqlite3 (>= 1.3.3)
sshkit (1.13.1)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
telegram-bot-ruby (0.7.2)
faraday
virtus
therubyracer (0.12.3)
libv8 (~> 3.16.14.15)
ref
thin (1.6.2)
daemons (>= 1.0.9)
eventmachine (>= 1.0.0)
rack (>= 1.0.0)
thor (0.19.4)
thread_safe (0.3.6)
tilt (2.0.7)
time_diff (0.3.0)
activesupport
i18n
tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (3.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.4)
valid_email2 (1.2.12)
activemodel (>= 3.2)
mail (~> 2.5)
virtus (1.0.5)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
warden (1.2.7)
rack (>= 1.0)
web-console (3.5.0)
actionview (>= 5.0)
activemodel (>= 5.0)
bindex (>= 0.4.0)
railties (>= 5.0)
webpacker (4.0.7)
activesupport (>= 4.2)
rack-proxy (>= 0.6.1)
railties (>= 4.2)
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
wisper (2.0.0)
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS
ruby
DEPENDENCIES
acts-as-taggable-on (~> 4.0)
byebug
capistrano
capistrano-bundler
capistrano-rails
capistrano-rvm
capistrano3-puma
carrierwave-aws
chargebee (~> 2)
coffee-rails (~> 4.2)
devise
devise_token_auth
facebook-messenger (~> 0.11.1)
figaro
fog-aws
fog-digitalocean
hashie
jbuilder (~> 2.5)
jquery-rails
kaminari
koala
listen (~> 3.0.5)
mailcatcher
mini_magick
nightfury (~> 1.0, >= 1.0.1)
omniauth-facebook
pg
phantomjs
poltergeist
puma (~> 3.0)
pundit
pusher
rack-cors
rails (~> 5.0.0, >= 5.0.0.1)
redis (~> 3.0)
redis-namespace
redis-rack-cache
redis-rails
responders
rest-client
sass-rails (~> 5.0)
seed_dump
sentry-raven
sidekiq
sinatra!
spring
spring-watcher-listen (~> 2.0.0)
telegram-bot-ruby
therubyracer
time_diff
tzinfo-data
uglifier (>= 1.3.0)
valid_email2
web-console
webpacker
wisper (= 2.0.0)
BUNDLED WITH
1.17.3

2
Procfile Normal file
View file

@ -0,0 +1,2 @@
backend: bin/rails s -p 3000
frontend: bin/webpack-dev-server

17
README.md Normal file
View file

@ -0,0 +1,17 @@
# Chatwoot
![ChatUI progess](https://chatwoot.com/images/dashboard-screen.png)
## Build Setup
``` bash
# install JS dependencies
yarn
# install ruby dependencies
bundle
# fireup the server
foreman start
```

6
Rakefile Normal file
View file

@ -0,0 +1,6 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative 'config/application'
Rails.application.load_tasks

View file

@ -0,0 +1,3 @@
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css

0
app/assets/images/.keep Normal file
View file

View file

@ -0,0 +1,3 @@
$( document ).ready(function() {
window.currentAcccountId = $('body').data('account-id');
});

View 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/

View 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/

View 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/

View 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/

View 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/

View 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/

View 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/

View 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/

View 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 .

View 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/

View 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/

View 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/

View 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/

View 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/

View 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/

View 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/

View 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/

View 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/

View 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
*/

View 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
View 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

View file

View 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

View file

@ -0,0 +1,3 @@
class Messages::IncomingMessageBuilder < Messages::MessageBuilder
end

View 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

View file

@ -0,0 +1,3 @@
class Messages::Outgoing::EchoBuilder < ::Messages::MessageBuilder
end

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View file

@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View file

View 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

View file

@ -0,0 +1,6 @@
class DashboardController < ActionController::Base
layout 'vueapp'
def index
end
end

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View file

@ -0,0 +1,12 @@
class BaseDispatcher
include Wisper::Publisher
def listeners
[]
end
def load_listeners
listeners.each{|listener| subscribe(listener) }
end
end

View 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

View 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

View 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

View 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

View file

@ -0,0 +1,2 @@
module Api::BaseHelper
end

View file

@ -0,0 +1,2 @@
module Api::V1::AgentsHelper
end

View file

@ -0,0 +1,2 @@
module Api::V1::CannedResponsesHelper
end

View file

@ -0,0 +1,2 @@
module Api::V1::ConversationsHelper
end

View file

@ -0,0 +1,2 @@
module Api::V1::ReportsHelper
end

View file

@ -0,0 +1,2 @@
module Api::V1::SubscriptionsHelper
end

View file

@ -0,0 +1,2 @@
module Api::V1::WebhooksHelper
end

View file

@ -0,0 +1,2 @@
module Api::V1::Widget::MessagesHelper
end

View file

@ -0,0 +1,2 @@
module ApplicationHelper
end

View file

@ -0,0 +1,2 @@
module HomeHelper
end

View 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

View 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

View 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();

View 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>

View 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;
},
};

View 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);
});
});
},
};

View 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;
},
};

View 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;
},
};

View 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;
},
};

View 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];
};

View 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;
},
};

View 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);
});
},
};

View 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;
},
};

View 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;
},
};

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more