Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9a56f1f02 | ||
|
|
49b4c2ae07 | ||
|
|
1312d07aa2 | ||
|
|
f3a357d832 | ||
|
|
739c062676 | ||
|
|
afc062537c | ||
|
|
fe3cb76a19 | ||
|
|
31c07771e8 | ||
|
|
3b92c744d6 | ||
|
|
ac5d755545 | ||
|
|
3ffb29bf4e | ||
|
|
3544514470 | ||
|
|
c668cccb53 | ||
|
|
d18c8fc08a | ||
|
|
715bd368f4 | ||
|
|
afb5694ed6 | ||
|
|
e4336ffcb3 | ||
|
|
3ac32a3fbd | ||
|
|
9b23a11c5b | ||
|
|
dfb533db8d | ||
|
|
17968e6d3f | ||
|
|
cb6fc9fb2e | ||
|
|
29d13fbb4f | ||
|
|
5ac093fca2 | ||
|
|
85ae6d92b9 | ||
|
|
45cd949c40 | ||
|
|
ce13efd273 | ||
|
|
2193de9853 | ||
|
|
124e43b477 | ||
|
|
fe1259a29f | ||
|
|
d605619eba | ||
|
|
e1b7165bec | ||
|
|
31fd673f71 | ||
|
|
b38952d6ca | ||
|
|
37335c413a | ||
|
|
62e70ddcc8 | ||
|
|
aea5ea3a06 | ||
|
|
8cf05f1d9f | ||
|
|
ec6cd4bbba | ||
|
|
5917cd5d98 | ||
|
|
83eb92e7ff | ||
|
|
cdd385b269 | ||
|
|
507b40a51d | ||
|
|
e8912655a5 | ||
|
|
662bb882f7 | ||
|
|
dfe4b70d91 | ||
|
|
22880df429 | ||
|
|
4b70e4e3d6 | ||
|
|
104f1fb1cc | ||
|
|
f5374e0cd9 | ||
|
|
be91b322a4 | ||
|
|
0117997fb1 | ||
|
|
04bce69ded | ||
|
|
2063e248a9 | ||
|
|
4216d63311 | ||
|
|
a6a62d92bf | ||
|
|
ec3c2ed4bb | ||
|
|
0adbc346df | ||
|
|
a04ca24def | ||
|
|
db877453a4 | ||
|
|
3b23aa7913 | ||
|
|
941272cccd | ||
|
|
a5262a6ff3 | ||
|
|
5e5f34bedc | ||
|
|
6d4cfcceba | ||
|
|
1dd3573c39 | ||
|
|
4b773f2a0e | ||
|
|
a16d2c653d | ||
|
|
2b1d445003 | ||
|
|
f30c8943d9 | ||
|
|
703027b834 | ||
|
|
89ed0b425b | ||
|
|
858b72a404 | ||
|
|
0f2d3418f9 | ||
|
|
12ee7e5d82 | ||
|
|
80b083c7fc | ||
|
|
bb83d6ee4d | ||
|
|
c7d962b20f | ||
|
|
e72f29a26e | ||
|
|
d6f309ce22 | ||
|
|
fcb7625616 | ||
|
|
8079bf50a0 | ||
|
|
fa04098c20 | ||
|
|
d800b55ac6 | ||
|
|
29838f9424 | ||
|
|
440f449e24 | ||
|
|
b803ae3300 | ||
|
|
96efc44b82 | ||
|
|
7607e8edb4 | ||
|
|
7ca192615d | ||
|
|
7ef45e5844 | ||
|
|
642efe8d40 | ||
|
|
9e50d22472 | ||
|
|
a18d54b706 | ||
|
|
196741d975 | ||
|
|
d5a6f5e352 | ||
|
|
a8ce9ae59c | ||
|
|
196ffe3ea2 | ||
|
|
80dcc8339e | ||
|
|
ba5539d9bf | ||
|
|
a260495f3d | ||
|
|
aac3c27483 | ||
|
|
a3d14c1884 | ||
|
|
7483796a14 | ||
|
|
5dcc8a9e81 | ||
|
|
6f47c07362 | ||
|
|
24101eb117 | ||
|
|
e8994165ea | ||
|
|
929575f11a | ||
|
|
3593a84f26 | ||
|
|
6ede64cb6b | ||
|
|
acbb79a7c2 | ||
|
|
abc335cc8e | ||
|
|
d03fad3e1d | ||
|
|
a77cc713c2 | ||
|
|
0fc0dc1683 | ||
|
|
49db9c5d8a | ||
|
|
6a7d810c95 | ||
|
|
f8a3f2df27 | ||
|
|
e41bd56f41 | ||
|
|
b633126b35 | ||
|
|
43147b3163 | ||
|
|
4612494923 | ||
|
|
36661ea45d | ||
|
|
c98907db49 | ||
|
|
bd87927576 | ||
|
|
1b1deb9626 | ||
|
|
c0cd747f29 | ||
|
|
a699d7bdfe | ||
|
|
83be2e1edb | ||
|
|
534409d25a | ||
|
|
cc02611007 | ||
|
|
4f83d5451e | ||
|
|
0aab717bb3 | ||
|
|
97ad39713b | ||
|
|
8b61452d56 | ||
|
|
1ef8d03e18 | ||
|
|
aa8a85b8bd | ||
|
|
77c16932f8 | ||
|
|
d4214cd78f | ||
|
|
963f173730 | ||
|
|
04f6460afb | ||
|
|
b0bbd757b5 | ||
|
|
667e3abbe1 | ||
|
|
7c6ea3a05a | ||
|
|
5cb88237f5 | ||
|
|
5ec9af9325 | ||
|
|
ecd5e20133 | ||
|
|
944fbf27b7 | ||
|
|
f9e8bb8e10 | ||
|
|
ed1c871633 | ||
|
|
4f3b483066 | ||
|
|
ca7554f88c | ||
|
|
e769282e7f | ||
|
|
3823ad8fef | ||
|
|
04a0aa5f1b | ||
|
|
991648edcf | ||
|
|
bfe10618fb | ||
|
|
eadc33d230 | ||
|
|
432dad203b | ||
|
|
8b022311c0 | ||
|
|
40481f6462 | ||
|
|
602132119b | ||
|
|
52d28105e4 | ||
|
|
051871a3cd | ||
|
|
3d84568a37 | ||
|
|
915eefdc36 | ||
|
|
ede4dd9cb0 | ||
|
|
b7f7b54f9b | ||
|
|
a13a474c23 | ||
|
|
e783c3af5d | ||
|
|
68be7e3ea8 | ||
|
|
72a25ffdc5 |
+16
-14
@@ -16,7 +16,7 @@ defaults: &defaults
|
||||
- image: circleci/redis:alpine
|
||||
environment:
|
||||
- CC_TEST_REPORTER_ID: b1b5c4447bf93f6f0b06a64756e35afd0810ea83649f03971cbf303b4449456f
|
||||
|
||||
- RAILS_LOG_TO_STDOUT: false
|
||||
jobs:
|
||||
build:
|
||||
<<: *defaults
|
||||
@@ -69,11 +69,11 @@ jobs:
|
||||
- run:
|
||||
name: Download cc-test-reporter
|
||||
command: |
|
||||
mkdir -p tmp/
|
||||
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./tmp/cc-test-reporter
|
||||
chmod +x ./tmp/cc-test-reporter
|
||||
mkdir -p ~/tmp
|
||||
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ~/tmp/cc-test-reporter
|
||||
chmod +x ~/tmp/cc-test-reporter
|
||||
- persist_to_workspace:
|
||||
root: tmp
|
||||
root: ~/tmp
|
||||
paths:
|
||||
- cc-test-reporter
|
||||
|
||||
@@ -98,10 +98,10 @@ jobs:
|
||||
- run:
|
||||
name: Run backend tests
|
||||
command: |
|
||||
bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
|
||||
./tmp/cc-test-reporter format-coverage -t simplecov -o tmp/codeclimate.backend.json coverage/backend/.resultset.json
|
||||
bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) --profile=10
|
||||
~/tmp/cc-test-reporter format-coverage -t simplecov -o ~/tmp/codeclimate.backend.json coverage/backend/.resultset.json
|
||||
- persist_to_workspace:
|
||||
root: tmp
|
||||
root: ~/tmp
|
||||
paths:
|
||||
- codeclimate.backend.json
|
||||
|
||||
@@ -109,21 +109,23 @@ jobs:
|
||||
name: Run frontend tests
|
||||
command: |
|
||||
yarn test:coverage
|
||||
./tmp/cc-test-reporter format-coverage -t lcov -o tmp/codeclimate.frontend.json buildreports/lcov.info
|
||||
~/tmp/cc-test-reporter format-coverage -t lcov -o ~/tmp/codeclimate.frontend.json buildreports/lcov.info
|
||||
- persist_to_workspace:
|
||||
root: tmp
|
||||
root: ~/tmp
|
||||
paths:
|
||||
- codeclimate.frontend.json
|
||||
|
||||
# collect reports
|
||||
- store_test_results:
|
||||
path: /tmp/test-results
|
||||
path: ~/tmp/test-results
|
||||
- store_artifacts:
|
||||
path: /tmp/test-results
|
||||
path: ~/tmp/test-results
|
||||
destination: test-results
|
||||
- store_artifacts:
|
||||
path: log
|
||||
|
||||
- run:
|
||||
name: Upload coverage results to Code Climate
|
||||
command: |
|
||||
./tmp/cc-test-reporter sum-coverage tmp/codeclimate.*.json -p 2 -o tmp/codeclimate.total.json
|
||||
./tmp/cc-test-reporter upload-coverage -i tmp/codeclimate.total.json
|
||||
~/tmp/cc-test-reporter sum-coverage ~/tmp/codeclimate.*.json -p 2 -o ~/tmp/codeclimate.total.json
|
||||
~/tmp/cc-test-reporter upload-coverage -i ~/tmp/codeclimate.total.json
|
||||
|
||||
+17
-11
@@ -4,6 +4,9 @@ SECRET_KEY_BASE=replace_with_lengthy_secure_hex
|
||||
# Replace with the URL you are planning to use for your app
|
||||
FRONTEND_URL=http://0.0.0.0:3000
|
||||
|
||||
# If you plan to use CDN for your assets, set Asset CDN Host
|
||||
ASSET_CDN_HOST=
|
||||
|
||||
# Force all access to the app over SSL, default is set to false
|
||||
FORCE_SSL=false
|
||||
|
||||
@@ -40,7 +43,8 @@ SMTP_AUTHENTICATION=
|
||||
SMTP_ENABLE_STARTTLS_AUTO=
|
||||
|
||||
# Mail Incoming
|
||||
|
||||
# This is the domain set for the reply emails when conversation continuity is enabled
|
||||
MAILER_INBOUND_EMAIL_DOMAIN=
|
||||
# Set this to appropriate ingress channel with regards to incoming emails
|
||||
# Possible values are :
|
||||
# :relay for Exim, Postfix, Qmail
|
||||
@@ -59,6 +63,7 @@ MANDRILL_INGRESS_API_KEY=
|
||||
ACTIVE_STORAGE_SERVICE=local
|
||||
|
||||
# Amazon S3
|
||||
# documentation: https://www.chatwoot.com/docs/configuring-s3-bucket-as-cloud-storage
|
||||
S3_BUCKET_NAME=
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
@@ -74,34 +79,35 @@ LOG_LEVEL=info
|
||||
LOG_SIZE=500
|
||||
|
||||
### This environment variables are only required if you are setting up social media channels
|
||||
#facebook
|
||||
|
||||
# Facebook
|
||||
# documentation: https://www.chatwoot.com/docs/facebook-setup
|
||||
FB_VERIFY_TOKEN=
|
||||
FB_APP_SECRET=
|
||||
FB_APP_ID=
|
||||
|
||||
# Twitter
|
||||
# documentation: https://www.chatwoot.com/docs/twitter-app-setup
|
||||
TWITTER_APP_ID=
|
||||
TWITTER_CONSUMER_KEY=
|
||||
TWITTER_CONSUMER_SECRET=
|
||||
TWITTER_ENVIRONMENT=
|
||||
|
||||
#slack integration
|
||||
SLACK_CLIENT_ID=
|
||||
SLACK_CLIENT_SECRET=
|
||||
|
||||
### Change this env variable only if you are using a custom build mobile app
|
||||
## Mobile app env variables
|
||||
IOS_APP_ID=6C953F3RX2.com.chatwoot.app
|
||||
|
||||
#### This environment variables are only required in hosted version which has billing
|
||||
ENABLE_BILLING=
|
||||
|
||||
## chargebee settings
|
||||
CHARGEBEE_API_KEY=
|
||||
CHARGEBEE_SITE=
|
||||
CHARGEBEE_WEBHOOK_USERNAME=
|
||||
CHARGEBEE_WEBHOOK_PASSWORD=
|
||||
|
||||
## Push Notification
|
||||
## generate a new key value here : https://d3v.one/vapid-key-generator/
|
||||
# VAPID_PUBLIC_KEY=
|
||||
# VAPID_PRIVATE_KEY=
|
||||
#
|
||||
# for mobile apps
|
||||
# FCM_SERVER_KEY=
|
||||
|
||||
## Bot Customizations
|
||||
USE_INBOX_AVATAR_FOR_BOT=true
|
||||
|
||||
@@ -46,5 +46,6 @@ module.exports = {
|
||||
},
|
||||
globals: {
|
||||
__WEBPACK_ENV__: true,
|
||||
bus: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
open_collective: chatwoot
|
||||
github: chatwoot
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: 'Bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
@@ -18,11 +19,17 @@ Steps to reproduce the behavior:
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment**
|
||||
|
||||
Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self hosted installation of Chatwoot. If you are using a self hosted installation of Chatwoot describe the type of deployment (Docker/Linux VM installation/Heroku)
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
@@ -35,4 +42,5 @@ If applicable, add screenshots to help explain your problem.
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
|
||||
Add any other context about the problem here.
|
||||
|
||||
@@ -55,3 +55,7 @@ node_modules
|
||||
package-lock.json
|
||||
|
||||
*.dump
|
||||
|
||||
|
||||
# cypress
|
||||
test/cypress/videos/*
|
||||
+25
-12
@@ -4,12 +4,9 @@ require:
|
||||
- rubocop-rspec
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
Lint/RaiseException:
|
||||
Enabled: true
|
||||
Lint/StructNewOverride:
|
||||
Enabled: true
|
||||
Layout/LineLength:
|
||||
Max: 150
|
||||
|
||||
Metrics/ClassLength:
|
||||
Max: 125
|
||||
Exclude:
|
||||
@@ -18,21 +15,27 @@ RSpec/ExampleLength:
|
||||
Max: 25
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
Style/ExponentialNotation:
|
||||
Enabled: false
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: false
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
Style/HashEachMethods:
|
||||
Enabled: true
|
||||
Style/HashTransformKeys:
|
||||
Enabled: true
|
||||
Style/HashTransformValues:
|
||||
Enabled: true
|
||||
Style/OptionalBooleanParameter:
|
||||
Exclude:
|
||||
- 'app/services/email_templates/db_resolver_service.rb'
|
||||
- 'app/dispatchers/dispatcher.rb'
|
||||
Style/GlobalVars:
|
||||
Exclude:
|
||||
- 'config/initializers/redis.rb'
|
||||
- 'lib/redis/alfred.rb'
|
||||
- 'lib/global_config.rb'
|
||||
Style/ClassVars:
|
||||
Exclude:
|
||||
- 'app/services/email_templates/db_resolver_service.rb'
|
||||
Lint/MissingSuper:
|
||||
Exclude:
|
||||
- 'app/drops/base_drop.rb'
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
- spec/**/*
|
||||
@@ -65,12 +68,14 @@ Style/GuardClause:
|
||||
- 'app/builders/account_builder.rb'
|
||||
- 'app/models/attachment.rb'
|
||||
- 'app/models/message.rb'
|
||||
- 'lib/webhooks/chargebee.rb'
|
||||
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
||||
Metrics/AbcSize:
|
||||
Exclude:
|
||||
- 'app/controllers/concerns/auth_helper.rb'
|
||||
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
||||
- 'db/migrate/20161123131628_devise_token_auth_create_users.rb'
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 7
|
||||
Exclude:
|
||||
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
||||
Rails/ReversibleMigration:
|
||||
@@ -97,17 +102,25 @@ Rails/UniqueValidationWithoutIndex:
|
||||
Exclude:
|
||||
- 'app/models/channel/twitter_profile.rb'
|
||||
- 'app/models/webhook.rb'
|
||||
Rails/RenderInline:
|
||||
Exclude:
|
||||
- 'app/controllers/swagger_controller.rb'
|
||||
RSpec/NamedSubject:
|
||||
Enabled: false
|
||||
# we should bring this down
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 12
|
||||
AllCops:
|
||||
NewCops: enable
|
||||
Exclude:
|
||||
- 'bin/**/*'
|
||||
- 'db/schema.rb'
|
||||
- 'config/**/*'
|
||||
- 'public/**/*'
|
||||
- 'config/initializers/bot.rb'
|
||||
- 'vendor/**/*'
|
||||
- 'node_modules/**/*'
|
||||
- 'lib/tasks/auto_annotate_models.rake'
|
||||
- 'config/environments/**/*'
|
||||
- 'tmp/**/*'
|
||||
- 'storage/**/*'
|
||||
- 'db/migrate/20200225162150_init_schema.rb'
|
||||
|
||||
@@ -88,7 +88,6 @@ Naming/MemoizedInstanceVariableName:
|
||||
- 'app/controllers/application_controller.rb'
|
||||
- 'app/models/message.rb'
|
||||
- 'lib/integrations/widget/outgoing_message_builder.rb'
|
||||
- 'lib/webhooks/chargebee.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# Cop supports --auto-correct.
|
||||
@@ -187,7 +186,6 @@ Rails/EnumHash:
|
||||
- 'app/models/attachment.rb'
|
||||
- 'app/models/conversation.rb'
|
||||
- 'app/models/message.rb'
|
||||
- 'app/models/subscription.rb'
|
||||
- 'app/models/user.rb'
|
||||
|
||||
# Offense count: 1
|
||||
@@ -226,7 +224,6 @@ Rails/Output:
|
||||
Rails/TimeZone:
|
||||
Exclude:
|
||||
- 'app/builders/report_builder.rb'
|
||||
- 'app/models/subscription.rb'
|
||||
- 'lib/reports/update_account_identity.rb'
|
||||
- 'lib/reports/update_agent_identity.rb'
|
||||
- 'lib/reports/update_identity.rb'
|
||||
@@ -269,24 +266,6 @@ Style/CommentedKeyword:
|
||||
- 'app/controllers/api/v1/conversations/labels_controller.rb'
|
||||
- 'app/controllers/api/v1/labels_controller.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: annotated, template, unannotated
|
||||
Style/FormatStringToken:
|
||||
Exclude:
|
||||
- 'lib/constants/redis_keys.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# Configuration parameters: AllowedVariables.
|
||||
Style/GlobalVars:
|
||||
Exclude:
|
||||
- 'lib/redis/alfred.rb'
|
||||
|
||||
# Offense count: 4
|
||||
Style/IdenticalConditionalBranches:
|
||||
Exclude:
|
||||
- 'app/controllers/api/v1/reports_controller.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: AllowIfModifier.
|
||||
Style/IfInsideElse:
|
||||
|
||||
+2
-1
@@ -51,7 +51,7 @@ linters:
|
||||
|
||||
ElsePlacement:
|
||||
enabled: true
|
||||
style: same_line # or 'new_line'
|
||||
style: new_line
|
||||
|
||||
EmptyLineBetweenBlocks:
|
||||
enabled: true
|
||||
@@ -282,3 +282,4 @@ exclude:
|
||||
- 'app/javascript/widget/assets/scss/_reset.scss'
|
||||
- 'app/javascript/widget/assets/scss/sdk.css'
|
||||
- 'app/assets/stylesheets/administrate/reset/_normalize.scss'
|
||||
- 'app/javascript/shared/assets/stylesheets/*.scss'
|
||||
|
||||
@@ -8,7 +8,7 @@ gem 'rails'
|
||||
# Reduces boot times through caching; required in config/boot.rb
|
||||
gem 'bootsnap', require: false
|
||||
|
||||
##-- rails helper gems --##
|
||||
##-- rails application helper gems --##
|
||||
gem 'acts-as-taggable-on'
|
||||
gem 'attr_extras'
|
||||
gem 'browser'
|
||||
@@ -23,6 +23,12 @@ gem 'tzinfo-data'
|
||||
gem 'valid_email2'
|
||||
# compress javascript config.assets.js_compressor
|
||||
gem 'uglifier'
|
||||
##-- used for single column multiple binary flags in notification settings/feature flagging --##
|
||||
gem 'flag_shih_tzu'
|
||||
# Random name generator for user names
|
||||
gem 'haikunator'
|
||||
# Template parsing safetly
|
||||
gem 'liquid'
|
||||
|
||||
##-- for active storage --##
|
||||
gem 'aws-sdk-s3', require: false
|
||||
@@ -41,7 +47,9 @@ gem 'redis-rack-cache'
|
||||
gem 'dotenv-rails'
|
||||
gem 'foreman'
|
||||
gem 'puma'
|
||||
gem 'webpacker'
|
||||
gem 'webpacker', '~> 5.x'
|
||||
# metrics on heroku
|
||||
gem 'barnes'
|
||||
|
||||
##--- gems for authentication & authorization ---##
|
||||
gem 'devise'
|
||||
@@ -56,11 +64,9 @@ gem 'administrate'
|
||||
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
||||
gem 'wisper', '2.0.0'
|
||||
|
||||
##--- gems for billing ---##
|
||||
gem 'chargebee'
|
||||
|
||||
##--- gems for channels ---##
|
||||
gem 'facebook-messenger'
|
||||
# TODO: bump up gem to 2.0
|
||||
gem 'facebook-messenger', '1.5.0'
|
||||
gem 'telegram-bot-ruby'
|
||||
gem 'twilio-ruby', '~> 5.32.0'
|
||||
# twitty will handle subscription of twitter account events
|
||||
@@ -68,8 +74,8 @@ gem 'twilio-ruby', '~> 5.32.0'
|
||||
gem 'twitty'
|
||||
# facebook client
|
||||
gem 'koala'
|
||||
# Random name generator
|
||||
gem 'haikunator'
|
||||
# slack client
|
||||
gem 'slack-ruby-client'
|
||||
|
||||
##--- gems for debugging and error reporting ---##
|
||||
# static analysis
|
||||
@@ -80,10 +86,8 @@ gem 'sentry-raven'
|
||||
##-- background job processing --##
|
||||
gem 'sidekiq'
|
||||
|
||||
##-- used for single column multiple binary flags in notification settings/feature flagging --##
|
||||
gem 'flag_shih_tzu'
|
||||
|
||||
##-- Push notification service --##
|
||||
gem 'fcm'
|
||||
gem 'webpush'
|
||||
|
||||
group :development do
|
||||
@@ -94,6 +98,16 @@ group :development do
|
||||
|
||||
# used in swagger build
|
||||
gem 'json_refs', git: 'https://github.com/tzmfreedom/json_refs', ref: 'e32deb0'
|
||||
|
||||
# When we want to squash migrations
|
||||
gem 'squasher'
|
||||
end
|
||||
|
||||
group :test do
|
||||
# Cypress in rails.
|
||||
gem 'cypress-on-rails', '~> 1.0'
|
||||
# fast cleaning of database
|
||||
gem 'database_cleaner'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
@@ -117,4 +131,5 @@ group :development, :test do
|
||||
gem 'simplecov', '0.17.1', require: false
|
||||
gem 'spring'
|
||||
gem 'spring-watcher-listen'
|
||||
gem 'webmock'
|
||||
end
|
||||
|
||||
+181
-148
@@ -18,56 +18,56 @@ GEM
|
||||
specs:
|
||||
action-cable-testing (0.6.1)
|
||||
actioncable (>= 5.0)
|
||||
actioncable (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
actioncable (6.0.3.3)
|
||||
actionpack (= 6.0.3.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
activejob (= 6.0.3.1)
|
||||
activerecord (= 6.0.3.1)
|
||||
activestorage (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
actionmailbox (6.0.3.3)
|
||||
actionpack (= 6.0.3.3)
|
||||
activejob (= 6.0.3.3)
|
||||
activerecord (= 6.0.3.3)
|
||||
activestorage (= 6.0.3.3)
|
||||
activesupport (= 6.0.3.3)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
actionview (= 6.0.3.1)
|
||||
activejob (= 6.0.3.1)
|
||||
actionmailer (6.0.3.3)
|
||||
actionpack (= 6.0.3.3)
|
||||
actionview (= 6.0.3.3)
|
||||
activejob (= 6.0.3.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.0.3.1)
|
||||
actionview (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
actionpack (6.0.3.3)
|
||||
actionview (= 6.0.3.3)
|
||||
activesupport (= 6.0.3.3)
|
||||
rack (~> 2.0, >= 2.0.8)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
activerecord (= 6.0.3.1)
|
||||
activestorage (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
actiontext (6.0.3.3)
|
||||
actionpack (= 6.0.3.3)
|
||||
activerecord (= 6.0.3.3)
|
||||
activestorage (= 6.0.3.3)
|
||||
activesupport (= 6.0.3.3)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
actionview (6.0.3.3)
|
||||
activesupport (= 6.0.3.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
activejob (6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
activejob (6.0.3.3)
|
||||
activesupport (= 6.0.3.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
activerecord (6.0.3.1)
|
||||
activemodel (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
activestorage (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
activejob (= 6.0.3.1)
|
||||
activerecord (= 6.0.3.1)
|
||||
activemodel (6.0.3.3)
|
||||
activesupport (= 6.0.3.3)
|
||||
activerecord (6.0.3.3)
|
||||
activemodel (= 6.0.3.3)
|
||||
activesupport (= 6.0.3.3)
|
||||
activestorage (6.0.3.3)
|
||||
actionpack (= 6.0.3.3)
|
||||
activejob (= 6.0.3.3)
|
||||
activerecord (= 6.0.3.3)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (6.0.3.1)
|
||||
activesupport (6.0.3.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
@@ -77,7 +77,7 @@ GEM
|
||||
activerecord (>= 5.0, < 6.1)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
administrate (0.13.0)
|
||||
administrate (0.14.0)
|
||||
actionpack (>= 4.2)
|
||||
actionview (>= 4.2)
|
||||
activerecord (>= 4.2)
|
||||
@@ -91,26 +91,26 @@ GEM
|
||||
annotate (3.1.1)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.0)
|
||||
attr_extras (6.2.3)
|
||||
autoprefixer-rails (9.7.6)
|
||||
ast (2.4.1)
|
||||
attr_extras (6.2.4)
|
||||
autoprefixer-rails (9.8.6.3)
|
||||
execjs
|
||||
aws-eventstream (1.1.0)
|
||||
aws-partitions (1.317.0)
|
||||
aws-sdk-core (3.96.1)
|
||||
aws-partitions (1.360.0)
|
||||
aws-sdk-core (3.105.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.31.0)
|
||||
aws-sdk-core (~> 3, >= 3.71.0)
|
||||
aws-sdk-kms (1.37.0)
|
||||
aws-sdk-core (~> 3, >= 3.99.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.65.0)
|
||||
aws-sdk-core (~> 3, >= 3.96.1)
|
||||
aws-sdk-s3 (1.79.1)
|
||||
aws-sdk-core (~> 3, >= 3.104.3)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.1.3)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
aws-sigv4 (1.2.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
axiom-types (0.1.1)
|
||||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
@@ -122,79 +122,89 @@ GEM
|
||||
faraday (~> 1.0)
|
||||
faraday_middleware (~> 1.0.0.rc1)
|
||||
nokogiri (~> 1.10.4)
|
||||
bcrypt (3.1.13)
|
||||
barnes (0.0.8)
|
||||
multi_json (~> 1)
|
||||
statsd-ruby (~> 1.1)
|
||||
bcrypt (3.1.15)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.4.6)
|
||||
bootsnap (1.4.8)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (4.8.2)
|
||||
browser (4.1.0)
|
||||
brakeman (4.9.0)
|
||||
browser (4.2.0)
|
||||
builder (3.2.4)
|
||||
bullet (6.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
bundle-audit (0.1.0)
|
||||
bundler-audit
|
||||
bundler-audit (0.6.1)
|
||||
bundler-audit (0.7.0.1)
|
||||
bundler (>= 1.2.0, < 3)
|
||||
thor (~> 0.18)
|
||||
thor (>= 0.18, < 2)
|
||||
byebug (11.1.3)
|
||||
chargebee (2.7.5)
|
||||
json_pure (~> 2.1)
|
||||
rest-client (>= 1.8, < 3.0)
|
||||
coderay (1.1.2)
|
||||
coderay (1.1.3)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
concurrent-ruby (1.1.6)
|
||||
connection_pool (2.2.2)
|
||||
concurrent-ruby (1.1.7)
|
||||
connection_pool (2.2.3)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.6)
|
||||
cypress-on-rails (1.8.0)
|
||||
rack
|
||||
database_cleaner (1.8.5)
|
||||
datetime_picker_rails (0.0.7)
|
||||
momentjs-rails (>= 2.8.1)
|
||||
declarative (0.0.10)
|
||||
declarative (0.0.20)
|
||||
declarative-option (0.1.0)
|
||||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
devise (4.7.1)
|
||||
devise (4.7.2)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise_token_auth (1.1.3)
|
||||
devise_token_auth (1.1.4)
|
||||
bcrypt (~> 3.0)
|
||||
devise (> 3.5.2, < 5)
|
||||
rails (>= 4.2.0, < 6.1)
|
||||
diff-lcs (1.3)
|
||||
digest-crc (0.5.1)
|
||||
sprockets (= 3.7.2)
|
||||
diff-lcs (1.4.4)
|
||||
digest-crc (0.6.1)
|
||||
rake (~> 13.0)
|
||||
docile (1.3.2)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.5)
|
||||
dotenv-rails (2.7.5)
|
||||
dotenv (= 2.7.5)
|
||||
railties (>= 3.2, < 6.1)
|
||||
dotenv (2.7.6)
|
||||
dotenv-rails (2.7.6)
|
||||
dotenv (= 2.7.6)
|
||||
railties (>= 3.2)
|
||||
equalizer (0.0.11)
|
||||
erubi (1.9.0)
|
||||
execjs (2.7.0)
|
||||
facebook-messenger (1.5.0)
|
||||
httparty (~> 0.13, >= 0.13.7)
|
||||
rack (>= 1.4.5)
|
||||
factory_bot (5.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
factory_bot_rails (5.2.0)
|
||||
factory_bot (~> 5.2.0)
|
||||
railties (>= 4.2.0)
|
||||
faker (2.11.0)
|
||||
factory_bot (6.1.0)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.1.0)
|
||||
factory_bot (~> 6.1.0)
|
||||
railties (>= 5.0.0)
|
||||
faker (2.13.0)
|
||||
i18n (>= 1.6, < 2)
|
||||
faraday (1.0.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday_middleware (1.0.0)
|
||||
faraday (~> 1.0)
|
||||
ffi (1.12.2)
|
||||
fcm (1.0.2)
|
||||
faraday (~> 1.0.0)
|
||||
ffi (1.13.1)
|
||||
flag_shih_tzu (0.3.23)
|
||||
foreman (0.87.1)
|
||||
foreman (0.87.2)
|
||||
gli (2.19.2)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
google-api-client (0.39.4)
|
||||
google-api-client (0.43.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
@@ -205,37 +215,38 @@ GEM
|
||||
google-cloud-core (1.5.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.3.1)
|
||||
google-cloud-env (1.3.3)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.0.0)
|
||||
google-cloud-storage (1.26.1)
|
||||
google-cloud-errors (1.0.1)
|
||||
google-cloud-storage (1.28.0)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.33)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.12.0)
|
||||
googleauth (0.13.1)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.14)
|
||||
groupdate (5.0.0)
|
||||
groupdate (5.1.0)
|
||||
activesupport (>= 5)
|
||||
haikunator (1.1.0)
|
||||
hana (1.3.6)
|
||||
hashdiff (1.0.1)
|
||||
hashie (4.1.0)
|
||||
hkdf (0.3.0)
|
||||
http-accept (1.7.0)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httparty (0.18.0)
|
||||
httparty (0.18.1)
|
||||
mime-types (~> 3.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.2)
|
||||
i18n (1.8.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
ice_nine (0.11.2)
|
||||
inflecto (0.0.2)
|
||||
@@ -246,9 +257,8 @@ GEM
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.3.0)
|
||||
json_pure (2.3.0)
|
||||
jwt (2.2.1)
|
||||
json (2.3.1)
|
||||
jwt (2.2.2)
|
||||
kaminari (1.2.1)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.1)
|
||||
@@ -269,10 +279,11 @@ GEM
|
||||
addressable (~> 2.7)
|
||||
letter_opener (1.7.0)
|
||||
launchy (~> 2.2)
|
||||
liquid (4.0.3)
|
||||
listen (3.2.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
loofah (2.5.0)
|
||||
loofah (2.7.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
@@ -288,23 +299,23 @@ GEM
|
||||
mini_magick (4.10.1)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.14.1)
|
||||
minitest (5.14.2)
|
||||
momentjs-rails (2.20.1)
|
||||
railties (>= 3.1)
|
||||
msgpack (1.3.3)
|
||||
multi_json (1.14.1)
|
||||
multi_json (1.15.0)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.1.1)
|
||||
netrc (0.11.0)
|
||||
nio4r (2.5.2)
|
||||
nokogiri (1.10.9)
|
||||
nio4r (2.5.3)
|
||||
nokogiri (1.10.10)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
oauth (0.5.4)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.1.0)
|
||||
parallel (1.19.1)
|
||||
parser (2.7.1.2)
|
||||
ast (~> 2.4.0)
|
||||
os (1.1.1)
|
||||
parallel (1.19.2)
|
||||
parser (2.7.1.4)
|
||||
ast (~> 2.4.1)
|
||||
pg (1.2.3)
|
||||
pry (0.13.1)
|
||||
coderay (~> 1.1)
|
||||
@@ -316,40 +327,38 @@ GEM
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
rack (2.2.2)
|
||||
rack-cache (1.11.1)
|
||||
rack (2.2.3)
|
||||
rack-cache (1.12.0)
|
||||
rack (>= 0.4)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-protection (2.0.8.1)
|
||||
rack
|
||||
rack-proxy (0.6.5)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.0.3.1)
|
||||
actioncable (= 6.0.3.1)
|
||||
actionmailbox (= 6.0.3.1)
|
||||
actionmailer (= 6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
actiontext (= 6.0.3.1)
|
||||
actionview (= 6.0.3.1)
|
||||
activejob (= 6.0.3.1)
|
||||
activemodel (= 6.0.3.1)
|
||||
activerecord (= 6.0.3.1)
|
||||
activestorage (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
rails (6.0.3.3)
|
||||
actioncable (= 6.0.3.3)
|
||||
actionmailbox (= 6.0.3.3)
|
||||
actionmailer (= 6.0.3.3)
|
||||
actionpack (= 6.0.3.3)
|
||||
actiontext (= 6.0.3.3)
|
||||
actionview (= 6.0.3.3)
|
||||
activejob (= 6.0.3.3)
|
||||
activemodel (= 6.0.3.3)
|
||||
activerecord (= 6.0.3.3)
|
||||
activestorage (= 6.0.3.3)
|
||||
activesupport (= 6.0.3.3)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 6.0.3.1)
|
||||
railties (= 6.0.3.3)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.3.0)
|
||||
loofah (~> 2.3)
|
||||
railties (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
railties (6.0.3.3)
|
||||
actionpack (= 6.0.3.3)
|
||||
activesupport (= 6.0.3.3)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
@@ -358,19 +367,20 @@ GEM
|
||||
rb-fsevent (0.10.4)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
redis (4.1.4)
|
||||
redis-namespace (1.7.0)
|
||||
redis (4.2.1)
|
||||
redis-namespace (1.8.0)
|
||||
redis (>= 3.0.4)
|
||||
redis-rack-cache (2.2.1)
|
||||
rack-cache (>= 1.10, < 2)
|
||||
redis-store (>= 1.6, < 2)
|
||||
redis-store (1.8.2)
|
||||
redis-store (1.9.0)
|
||||
redis (>= 4, < 5)
|
||||
regexp_parser (1.7.1)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
responders (3.0.0)
|
||||
responders (3.0.1)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rest-client (2.1.0)
|
||||
@@ -397,28 +407,33 @@ GEM
|
||||
rspec-mocks (~> 3.9)
|
||||
rspec-support (~> 3.9)
|
||||
rspec-support (3.9.3)
|
||||
rubocop (0.83.0)
|
||||
rubocop (0.89.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.7.0.1)
|
||||
parser (>= 2.7.1.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.7)
|
||||
rexml
|
||||
rubocop-ast (>= 0.3.0, < 1.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-performance (1.5.2)
|
||||
rubocop (>= 0.71.0)
|
||||
rubocop-rails (2.5.2)
|
||||
activesupport
|
||||
rubocop-ast (0.3.0)
|
||||
parser (>= 2.7.1.4)
|
||||
rubocop-performance (1.7.1)
|
||||
rubocop (>= 0.82.0)
|
||||
rubocop-rails (2.7.1)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
rubocop-rspec (1.39.0)
|
||||
rubocop (>= 0.68.1)
|
||||
rubocop (>= 0.87.0)
|
||||
rubocop-rspec (1.43.2)
|
||||
rubocop (~> 0.87)
|
||||
ruby-progressbar (1.10.1)
|
||||
safe_yaml (1.0.5)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sassc (2.3.0)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
@@ -426,7 +441,7 @@ GEM
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
scout_apm (2.6.7)
|
||||
scout_apm (2.6.9)
|
||||
parser
|
||||
scss_lint (0.59.0)
|
||||
sass (~> 3.5, >= 3.5.5)
|
||||
@@ -435,15 +450,14 @@ GEM
|
||||
activesupport (>= 4)
|
||||
selectize-rails (0.12.6)
|
||||
semantic_range (2.3.0)
|
||||
sentry-raven (3.0.0)
|
||||
sentry-raven (3.0.3)
|
||||
faraday (>= 1.0)
|
||||
shoulda-matchers (4.3.0)
|
||||
shoulda-matchers (4.4.1)
|
||||
activesupport (>= 4.2.0)
|
||||
sidekiq (6.0.7)
|
||||
sidekiq (6.1.1)
|
||||
connection_pool (>= 2.2.2)
|
||||
rack (~> 2.0)
|
||||
rack-protection (>= 2.0.0)
|
||||
redis (>= 4.1.0)
|
||||
redis (>= 4.2.0)
|
||||
signet (0.14.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
@@ -454,23 +468,31 @@ GEM
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
spring (2.1.0)
|
||||
slack-ruby-client (0.15.0)
|
||||
faraday (>= 1.0)
|
||||
faraday_middleware
|
||||
gli
|
||||
hashie
|
||||
websocket-driver
|
||||
spring (2.1.1)
|
||||
spring-watcher-listen (2.0.1)
|
||||
listen (>= 2.7, < 4.0)
|
||||
spring (>= 1.2, < 3.0)
|
||||
sprockets (4.0.0)
|
||||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.1)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
squasher (0.6.2)
|
||||
statsd-ruby (1.4.0)
|
||||
telegram-bot-ruby (0.12.0)
|
||||
faraday
|
||||
inflecto
|
||||
virtus
|
||||
telephone_number (1.4.7)
|
||||
thor (0.20.3)
|
||||
telephone_number (1.4.9)
|
||||
thor (1.0.1)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.10)
|
||||
time_diff (0.3.0)
|
||||
@@ -494,7 +516,7 @@ GEM
|
||||
unf_ext (0.0.7.7)
|
||||
unicode-display_width (1.7.0)
|
||||
uniform_notifier (1.13.0)
|
||||
valid_email2 (3.2.2)
|
||||
valid_email2 (3.3.1)
|
||||
activemodel (>= 3.2)
|
||||
mail (~> 2.5)
|
||||
virtus (1.0.5)
|
||||
@@ -504,12 +526,16 @@ GEM
|
||||
equalizer (~> 0.0, >= 0.0.9)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
web-console (4.0.2)
|
||||
web-console (4.0.4)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webpacker (5.1.1)
|
||||
webmock (3.8.3)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webpacker (5.2.1)
|
||||
activesupport (>= 5.2)
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
@@ -517,11 +543,11 @@ GEM
|
||||
webpush (1.0.0)
|
||||
hkdf (~> 0.2)
|
||||
jwt (~> 2.0)
|
||||
websocket-driver (0.7.1)
|
||||
websocket-driver (0.7.3)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.4)
|
||||
websocket-extensions (0.1.5)
|
||||
wisper (2.0.0)
|
||||
zeitwerk (2.3.0)
|
||||
zeitwerk (2.4.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@@ -534,19 +560,22 @@ DEPENDENCIES
|
||||
attr_extras
|
||||
aws-sdk-s3
|
||||
azure-storage-blob
|
||||
barnes
|
||||
bootsnap
|
||||
brakeman
|
||||
browser
|
||||
bullet
|
||||
bundle-audit
|
||||
byebug
|
||||
chargebee
|
||||
cypress-on-rails (~> 1.0)
|
||||
database_cleaner
|
||||
devise
|
||||
devise_token_auth
|
||||
dotenv-rails
|
||||
facebook-messenger
|
||||
facebook-messenger (= 1.5.0)
|
||||
factory_bot_rails
|
||||
faker
|
||||
fcm
|
||||
flag_shih_tzu
|
||||
foreman
|
||||
google-cloud-storage
|
||||
@@ -559,6 +588,7 @@ DEPENDENCIES
|
||||
kaminari
|
||||
koala
|
||||
letter_opener
|
||||
liquid
|
||||
listen
|
||||
mini_magick
|
||||
mock_redis!
|
||||
@@ -585,8 +615,10 @@ DEPENDENCIES
|
||||
shoulda-matchers
|
||||
sidekiq
|
||||
simplecov (= 0.17.1)
|
||||
slack-ruby-client
|
||||
spring
|
||||
spring-watcher-listen
|
||||
squasher
|
||||
telegram-bot-ruby
|
||||
telephone_number
|
||||
time_diff
|
||||
@@ -596,7 +628,8 @@ DEPENDENCIES
|
||||
uglifier
|
||||
valid_email2
|
||||
web-console
|
||||
webpacker
|
||||
webmock
|
||||
webpacker (~> 5.x)
|
||||
webpush
|
||||
wisper (= 2.0.0)
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
release: bundle exec rake db:migrate
|
||||
release: bundle exec rails db:prepare
|
||||
web: bin/rails server -p $PORT -e $RAILS_ENV
|
||||
worker: bundle exec sidekiq -C config/sidekiq.yml
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
backend: RAILS_ENV=test bin/rails s -p 5050
|
||||
frontend: bin/webpack-dev-server
|
||||
worker: RAILS_ENV=test bundle exec sidekiq -C config/sidekiq.yml
|
||||
@@ -23,40 +23,71 @@ ___
|
||||
<a href="https://discord.gg/cJXdrwS"><img src="https://img.shields.io/discord/647412545203994635" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||

|
||||
<img src="https://s3.us-west-2.amazonaws.com/gh-assets.chatwoot.com/chatwoot-dashboard-assets.png" width="100%" alt="Chat dashboard"/>
|
||||
|
||||
## Background
|
||||
Chatwoot is an open-source omnichannel customer support software. The development of Chatwoot started in 2016. It failed to succeed as a business and eventually shut up shop in 2017. During 2019 #Hacktoberfest, the maintainers decided to make it open-source, instead of letting the code rust in a private repo. With a pleasant surprise, Chatwoot became a trending project on Hacker News and best of all, got lots of love from the community.
|
||||
Now, a failed project is back on track and the prospects are looking great. The team is back to working on the project and this time, we are building it in the open. Thanks to the ideas and contributions from the community.
|
||||
|
||||
Chatwoot is a customer support tool for instant messaging channels which can help businesses to provide exceptional customer support. The development of Chatwoot started in 2016 and it failed to succeed as a business and eventually shut the shop in 2017. During 2019 #Hacktoberfest, the maintainers decided to make it opensource instead of letting the code rust in a private repo. With a pleasant surprise, Chatwoot became a trending project on Hacker News and best of all, got lots of love from the community.
|
||||
---
|
||||
|
||||
Now, a failed project is back on track and the prospects are looking great. The team is back to working on the project and we are building it in the open. Thanks to the ideas and contributions from the community.
|
||||
### Features
|
||||
|
||||
## Documentation
|
||||
Chatwoot gives an integrated view of conversations happening in different communication channels.
|
||||
|
||||
Detailed documentation is available at [www.chatwoot.com/docs](https://www.chatwoot.com/docs).
|
||||
It supports the following conversation channels:
|
||||
|
||||
You can find the quick setup docs [here](https://www.chatwoot.com/docs/quick-setup).
|
||||
- **Website**: Talk to your customers using our live chat widget and make use of our SDK to identify a user and provide contextual support.
|
||||
- **Facebook**: Connect your Facebook pages and start replying to the direct messages to your page.
|
||||
- **Twitter**: Connect your Twitter profiles and reply to direct messages or the tweets where you are mentioned.
|
||||
- **Whatsapp**: Connect your Whatsapp business account and manage the conversation in Chatwoot
|
||||
- **SMS**: Connect your Twilio SMS account and reply to the SMS queries in Chatwoot
|
||||
- **API Channel**: Build custom communication channels using our API channel.
|
||||
- **Email (beta)**: Forward all your email queries to Chatwoot and view it in our integrated dashboard.
|
||||
|
||||
## Branching model
|
||||
Other features include:
|
||||
|
||||
We use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. The base branch is `develop`.
|
||||
- **Multi-brand inboxes**: Manage multiple brands or pages using a single dashboard.
|
||||
- **Private notes**: Inter team communication is possible using private notes in a conversation.
|
||||
- **Canned responses (Saved replies)**: Improve the response rate by adding saved replies for frequently asked questions.
|
||||
- **Conversation Labels**: Use conversation labelling to create custom workflows.
|
||||
- **Auto assignment**: Chatwoot intelligently assigns a ticket to the agents who have access to the inbox depending on their availability and load.
|
||||
- **Conversation continuity**: If the user has provided an email address through the chat widget, Chatwoot would send an email to the customer under the agent name so that the user can continue the conversation over the email.
|
||||
- **Multi-lingual support**: Chatwoot supports 10+ languages.
|
||||
- **Powerful API & Webhooks**: Extend the capability of the software using Chatwoot’s webhooks and APIs.
|
||||
- **Integrations**: Chatwoot natively integrates with Slack right now. Manage your conversations in Slack without logging into the dashboard.
|
||||
|
||||
---
|
||||
|
||||
### Documentation
|
||||
|
||||
Detailed documentation is available at [www.chatwoot.com/help-center](https://www.chatwoot.com/help-center).
|
||||
|
||||
---
|
||||
|
||||
### Branching model
|
||||
|
||||
We use the [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. The base branch is `develop`.
|
||||
If you are looking for a stable version, please use the `master` or tags labelled as `v1.x.x`.
|
||||
|
||||
## Heroku one-click deploy
|
||||
---
|
||||
|
||||
Deploying chatwoot to heroku, it's a breeze. It's as simple as clicking this button.
|
||||
### Deployment
|
||||
|
||||
#### Heroku one-click deploy
|
||||
|
||||
Deploying Chatwoot to Heroku is a breeze. It's as simple as clicking this button:
|
||||
|
||||
[](https://heroku.com/deploy?template=https://github.com/chatwoot/chatwoot/tree/master)
|
||||
|
||||
Follow this [link](https://www.chatwoot.com/docs/environment-variables) to understand setting the correct environment variables for the app to work with all the features. There might be breakages if you do not set the relevant environment variables. This applies to deploying the docker image as well.
|
||||
Follow this [link](https://www.chatwoot.com/docs/environment-variables) to understand setting the correct environment variables for the app to work with all the features. There might be breakages if you do not set the relevant environment variables.
|
||||
|
||||
## Docker
|
||||
#### Other deployment options
|
||||
|
||||
Follow our [docker development guide](https://www.chatwoot.com/docs/installation-guide-docker) to develop and debug the application using `docker-compose`.
|
||||
Please follow [deployment architecture guide](https://www.chatwoot.com/docs/deployment/architecture) to deploy with Docker or Caprover.
|
||||
|
||||
Follow our [environment variables](https://www.chatwoot.com/docs/environment-variables/) guide to setup environment for Docker.
|
||||
---
|
||||
|
||||
## Contributors ✨
|
||||
### Contributors ✨
|
||||
|
||||
Thanks goes to all these [wonderful people](https://www.chatwoot.com/docs/contributors):
|
||||
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
"vue"
|
||||
],
|
||||
"success_url": "/app/login",
|
||||
"scripts": {
|
||||
"postdeploy": "bundle exec rake db:schema:load && bundle exec rake db:seed"
|
||||
},
|
||||
"env": {
|
||||
"SECRET_TOKEN": {
|
||||
"description": "A secret key for verifying the integrity of signed cookies.",
|
||||
@@ -40,17 +37,7 @@
|
||||
}
|
||||
},
|
||||
"image": "heroku/ruby",
|
||||
"addons": [
|
||||
{
|
||||
"plan": "heroku-redis"
|
||||
},
|
||||
{
|
||||
"plan": "heroku-postgresql",
|
||||
"options": {
|
||||
"version": "9.5"
|
||||
}
|
||||
}
|
||||
],
|
||||
"addons": [ "heroku-redis", "heroku-postgresql"],
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "heroku/ruby"
|
||||
|
||||
@@ -33,7 +33,8 @@ class ContactIdentifyAction
|
||||
end
|
||||
|
||||
def update_contact
|
||||
@contact.update!(params.slice(:name, :email, :identifier))
|
||||
custom_attributes = params[:custom_attributes] ? @contact.custom_attributes.merge(params[:custom_attributes]) : @contact.custom_attributes
|
||||
@contact.update!(params.slice(:name, :email, :identifier).merge({ custom_attributes: custom_attributes }))
|
||||
ContactAvatarJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present?
|
||||
end
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class ContactMergeAction
|
||||
end
|
||||
|
||||
def merge_messages
|
||||
Message.where(contact_id: @mergee_contact.id).update(contact_id: @base_contact.id)
|
||||
Message.where(sender: @mergee_contact).update(sender: @base_contact)
|
||||
end
|
||||
|
||||
def merge_contact_inboxes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.logo-brand {
|
||||
margin-bottom: $space-normal;
|
||||
padding: $space-normal $space-smaller;
|
||||
padding: $space-normal $space-smaller $space-small;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -70,3 +70,9 @@
|
||||
left: $space-normal;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.app-version {
|
||||
color: $color-gray;
|
||||
font-size: $font-size-small;
|
||||
padding-top: $space-smaller;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Sections
|
||||
|
||||
@@ -19,4 +19,10 @@ class FacebookBot
|
||||
updater.perform
|
||||
Rails.logger.info "Human was online at #{delivery.at}"
|
||||
end
|
||||
|
||||
Bot.on :message_echo do |message|
|
||||
Rails.logger.info "MESSAGE_ECHO #{message}"
|
||||
response = ::Integrations::Facebook::MessageParser.new(message)
|
||||
::Integrations::Facebook::MessageCreator.new(response).perform
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
|
||||
class AccountBuilder
|
||||
include CustomExceptions::Account
|
||||
pattr_initialize [:account_name!, :email!, :confirmed!]
|
||||
pattr_initialize [:account_name!, :email!, :confirmed!, :user]
|
||||
|
||||
def perform
|
||||
validate_email
|
||||
validate_user
|
||||
if @user.nil?
|
||||
validate_email
|
||||
validate_user
|
||||
end
|
||||
ActiveRecord::Base.transaction do
|
||||
@account = create_account
|
||||
@user = create_and_link_user
|
||||
end
|
||||
[@user, @account]
|
||||
rescue StandardError => e
|
||||
@account&.destroy
|
||||
puts e.inspect
|
||||
@@ -42,13 +45,7 @@ class AccountBuilder
|
||||
end
|
||||
|
||||
def create_and_link_user
|
||||
password = Time.now.to_i
|
||||
@user = User.new(email: @email,
|
||||
password: password,
|
||||
password_confirmation: password,
|
||||
name: email_to_name(@email))
|
||||
@user.confirm if @confirmed
|
||||
if @user.save!
|
||||
if @user.present? || create_user
|
||||
link_user_to_account(@user, @account)
|
||||
@user
|
||||
else
|
||||
@@ -68,4 +65,14 @@ class AccountBuilder
|
||||
name = email[/[^@]+/]
|
||||
name.split('.').map(&:capitalize).join(' ')
|
||||
end
|
||||
|
||||
def create_user
|
||||
password = Time.now.to_i
|
||||
@user = User.new(email: @email,
|
||||
password: password,
|
||||
password_confirmation: password,
|
||||
name: email_to_name(@email))
|
||||
@user.confirm if @confirmed
|
||||
@user.save!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# 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, source_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.
|
||||
|
||||
class Messages::Facebook::MessageBuilder
|
||||
attr_reader :response
|
||||
|
||||
def initialize(response, inbox, outgoing_echo: false)
|
||||
@response = response
|
||||
@inbox = inbox
|
||||
@outgoing_echo = outgoing_echo
|
||||
@sender_id = (@outgoing_echo ? @response.recipient_id : @response.sender_id)
|
||||
@message_type = (@outgoing_echo ? :outgoing : :incoming)
|
||||
end
|
||||
|
||||
def perform
|
||||
ActiveRecord::Base.transaction do
|
||||
build_contact
|
||||
build_message
|
||||
end
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contact
|
||||
@contact ||= @inbox.contact_inboxes.find_by(source_id: @sender_id)&.contact
|
||||
end
|
||||
|
||||
def build_contact
|
||||
return if contact.present?
|
||||
|
||||
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
|
||||
ContactAvatarJob.perform_later(@contact, contact_params[:remote_avatar_url]) if contact_params[:remote_avatar_url]
|
||||
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
|
||||
end
|
||||
|
||||
def build_message
|
||||
@message = conversation.messages.create!(message_params)
|
||||
(response.attachments || []).each do |attachment|
|
||||
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
||||
attachment_obj.save!
|
||||
attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url]
|
||||
end
|
||||
end
|
||||
|
||||
def attach_file(attachment, file_url)
|
||||
file_resource = LocalResource.new(file_url)
|
||||
attachment.file.attach(io: file_resource.file, filename: file_resource.tmp_filename, content_type: file_resource.encoding)
|
||||
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, SocketError => e
|
||||
Rails.logger.info "invalid url #{file_url} : #{e.message}"
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= Conversation.find_by(conversation_params) || build_conversation
|
||||
end
|
||||
|
||||
def build_conversation
|
||||
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: @sender_id)
|
||||
Conversation.create!(conversation_params.merge(
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
))
|
||||
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!(file_type_params(attachment))
|
||||
elsif file_type == :location
|
||||
params.merge!(location_params(attachment))
|
||||
elsif file_type == :fallback
|
||||
params.merge!(fallback_params(attachment))
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
def file_type_params(attachment)
|
||||
{
|
||||
external_url: attachment['payload']['url'],
|
||||
remote_file_url: attachment['payload']['url']
|
||||
}
|
||||
end
|
||||
|
||||
def location_params(attachment)
|
||||
lat = attachment['payload']['coordinates']['lat']
|
||||
long = attachment['payload']['coordinates']['long']
|
||||
{
|
||||
external_url: attachment['url'],
|
||||
coordinates_lat: lat,
|
||||
coordinates_long: long,
|
||||
fallback_title: attachment['title']
|
||||
}
|
||||
end
|
||||
|
||||
def fallback_params(attachment)
|
||||
{
|
||||
fallback_title: attachment['title'],
|
||||
external_url: attachment['url']
|
||||
}
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
contact_id: contact.id
|
||||
}
|
||||
end
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: @message_type,
|
||||
content: response.content,
|
||||
source_id: response.identifier,
|
||||
sender: @outgoing_echo ? nil : contact
|
||||
}
|
||||
end
|
||||
|
||||
def contact_params
|
||||
begin
|
||||
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
||||
result = k.get_object(@sender_id) || {}
|
||||
rescue StandardError => e
|
||||
result = {}
|
||||
Raven.capture_exception(e)
|
||||
end
|
||||
{
|
||||
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
||||
account_id: @inbox.account_id,
|
||||
remote_avatar_url: result['profile_pic'] || ''
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -1,2 +0,0 @@
|
||||
class Messages::IncomingMessageBuilder < Messages::MessageBuilder
|
||||
end
|
||||
@@ -1,138 +1,59 @@
|
||||
# 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, source_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.
|
||||
|
||||
class Messages::MessageBuilder
|
||||
attr_reader :response
|
||||
include ::FileTypeHelper
|
||||
attr_reader :message
|
||||
|
||||
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)
|
||||
def initialize(user, conversation, params)
|
||||
@params = params
|
||||
@private = params[:private] || false
|
||||
@conversation = conversation
|
||||
@user = user
|
||||
@message_type = params[:message_type] || 'outgoing'
|
||||
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
|
||||
@attachments = params[:attachments]
|
||||
@in_reply_to = params.to_unsafe_h&.dig(:content_attributes, :in_reply_to)
|
||||
end
|
||||
|
||||
def perform
|
||||
ActiveRecord::Base.transaction do
|
||||
build_contact
|
||||
build_message
|
||||
@message = @conversation.messages.build(message_params)
|
||||
if @attachments.present?
|
||||
@attachments.each do |uploaded_attachment|
|
||||
attachment = @message.attachments.new(
|
||||
account_id: @message.account_id,
|
||||
file_type: file_type(uploaded_attachment&.content_type)
|
||||
)
|
||||
attachment.file.attach(uploaded_attachment)
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
true
|
||||
@message.save
|
||||
@message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contact
|
||||
@contact ||= @inbox.contact_inboxes.find_by(source_id: @sender_id)&.contact
|
||||
end
|
||||
|
||||
def build_contact
|
||||
return if contact.present?
|
||||
|
||||
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
|
||||
ContactAvatarJob.perform_later(@contact, contact_params[:remote_avatar_url]) if contact_params[:remote_avatar_url]
|
||||
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
|
||||
end
|
||||
|
||||
def build_message
|
||||
@message = conversation.messages.create!(message_params)
|
||||
(response.attachments || []).each do |attachment|
|
||||
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
||||
attachment_obj.save!
|
||||
attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url]
|
||||
end
|
||||
end
|
||||
|
||||
def attach_file(attachment, file_url)
|
||||
file_resource = LocalResource.new(file_url)
|
||||
attachment.file.attach(io: file_resource.file, filename: file_resource.tmp_filename, content_type: file_resource.encoding)
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= Conversation.find_by(conversation_params) || build_conversation
|
||||
end
|
||||
|
||||
def build_conversation
|
||||
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: @sender_id)
|
||||
Conversation.create!(conversation_params.merge(
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
))
|
||||
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!(file_type_params(attachment))
|
||||
elsif file_type == :location
|
||||
params.merge!(location_params(attachment))
|
||||
elsif file_type == :fallback
|
||||
params.merge!(fallback_params(attachment))
|
||||
def message_type
|
||||
if @conversation.inbox.channel_type != 'Channel::Api' && @message_type == 'incoming'
|
||||
raise StandardError, 'Incoming messages are only allowed in Api inboxes'
|
||||
end
|
||||
|
||||
params
|
||||
@message_type
|
||||
end
|
||||
|
||||
def file_type_params(attachment)
|
||||
{
|
||||
external_url: attachment['payload']['url'],
|
||||
remote_file_url: attachment['payload']['url']
|
||||
}
|
||||
end
|
||||
|
||||
def location_params(attachment)
|
||||
lat = attachment['payload']['coordinates']['lat']
|
||||
long = attachment['payload']['coordinates']['long']
|
||||
{
|
||||
external_url: attachment['url'],
|
||||
coordinates_lat: lat,
|
||||
coordinates_long: long,
|
||||
fallback_title: attachment['title']
|
||||
}
|
||||
end
|
||||
|
||||
def fallback_params(attachment)
|
||||
{
|
||||
fallback_title: attachment['title'],
|
||||
external_url: attachment['url']
|
||||
}
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
contact_id: contact.id
|
||||
}
|
||||
def sender
|
||||
message_type == 'outgoing' ? @user : @conversation.contact
|
||||
end
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: @message_type,
|
||||
content: response.content,
|
||||
source_id: response.identifier
|
||||
}
|
||||
end
|
||||
|
||||
def contact_params
|
||||
begin
|
||||
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
||||
result = k.get_object(@sender_id) || {}
|
||||
rescue Exception => e
|
||||
result = {}
|
||||
Raven.capture_exception(e)
|
||||
end
|
||||
{
|
||||
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
||||
account_id: @inbox.account_id,
|
||||
remote_avatar_url: result['profile_pic'] || ''
|
||||
account_id: @conversation.account_id,
|
||||
inbox_id: @conversation.inbox_id,
|
||||
message_type: message_type,
|
||||
content: @params[:content],
|
||||
private: @private,
|
||||
sender: sender,
|
||||
content_type: @params[:content_type],
|
||||
items: @items,
|
||||
in_reply_to: @in_reply_to,
|
||||
echo_id: @params[:echo_id]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
class Messages::Outgoing::EchoBuilder < ::Messages::MessageBuilder
|
||||
end
|
||||
@@ -1,46 +0,0 @@
|
||||
class Messages::Outgoing::NormalBuilder
|
||||
include ::FileTypeHelper
|
||||
attr_reader :message
|
||||
|
||||
def initialize(user, conversation, params)
|
||||
@content = params[:content]
|
||||
@private = params[:private] || false
|
||||
@conversation = conversation
|
||||
@user = user
|
||||
@fb_id = params[:fb_id]
|
||||
@content_type = params[:content_type]
|
||||
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
|
||||
@attachments = params[:attachments]
|
||||
end
|
||||
|
||||
def perform
|
||||
@message = @conversation.messages.build(message_params)
|
||||
if @attachments.present?
|
||||
@attachments.each do |uploaded_attachment|
|
||||
attachment = @message.attachments.new(
|
||||
account_id: @message.account_id,
|
||||
file_type: file_type(uploaded_attachment&.content_type)
|
||||
)
|
||||
attachment.file.attach(uploaded_attachment)
|
||||
end
|
||||
end
|
||||
@message.save
|
||||
@message
|
||||
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,
|
||||
source_id: @fb_id,
|
||||
content_type: @content_type,
|
||||
items: @items
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -12,6 +12,8 @@ class NotificationSubscriptionBuilder
|
||||
|
||||
def identifier
|
||||
@identifier ||= params[:subscription_attributes][:endpoint] if params[:subscription_type] == 'browser_push'
|
||||
@identifier ||= params[:subscription_attributes][:device_id] if params[:subscription_type] == 'fcm'
|
||||
@identifier
|
||||
end
|
||||
|
||||
def identifier_subscription
|
||||
|
||||
@@ -1,10 +1,47 @@
|
||||
class RoomChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_from params[:pubsub_token]
|
||||
::OnlineStatusTracker.add_subscription(params[:pubsub_token])
|
||||
ensure_stream
|
||||
current_user
|
||||
current_account
|
||||
update_subscription
|
||||
broadcast_presence
|
||||
end
|
||||
|
||||
def unsubscribed
|
||||
::OnlineStatusTracker.remove_subscription(params[:pubsub_token])
|
||||
def update_presence
|
||||
update_subscription
|
||||
broadcast_presence
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def broadcast_presence
|
||||
data = { account_id: @current_account.id, users: ::OnlineStatusTracker.get_available_users(@current_account.id) }
|
||||
data[:contacts] = ::OnlineStatusTracker.get_available_contacts(@current_account.id) if @current_user.is_a? User
|
||||
ActionCable.server.broadcast(@pubsub_token, { event: 'presence.update', data: data })
|
||||
end
|
||||
|
||||
def ensure_stream
|
||||
@pubsub_token = params[:pubsub_token]
|
||||
stream_from @pubsub_token
|
||||
end
|
||||
|
||||
def update_subscription
|
||||
::OnlineStatusTracker.update_presence(@current_account.id, @current_user.class.name, @current_user.id)
|
||||
end
|
||||
|
||||
def current_user
|
||||
@current_user ||= if params[:user_id].blank?
|
||||
Contact.find_by!(pubsub_token: @pubsub_token)
|
||||
else
|
||||
User.find_by!(pubsub_token: @pubsub_token, id: params[:user_id])
|
||||
end
|
||||
end
|
||||
|
||||
def current_account
|
||||
@current_account ||= if @current_user.is_a? Contact
|
||||
@current_user.account
|
||||
else
|
||||
@current_user.accounts.find(params[:account_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,12 +10,4 @@ class Api::BaseController < ApplicationController
|
||||
def authenticate_by_access_token?
|
||||
request.headers[:api_access_token].present? || request.headers[:HTTP_API_ACCESS_TOKEN].present?
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
@conversation ||= current_account.conversations.find_by(display_id: params[:conversation_id])
|
||||
end
|
||||
|
||||
def check_billing_enabled
|
||||
raise ActionController::RoutingError, 'Not Found' unless ENV['BILLING_ENABLED']
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
class Api::V1::Accounts::Actions::ContactMergesController < Api::BaseController
|
||||
class Api::V1::Accounts::Actions::ContactMergesController < Api::V1::Accounts::BaseController
|
||||
before_action :set_base_contact, only: [:create]
|
||||
before_action :set_mergee_contact, only: [:create]
|
||||
|
||||
def create
|
||||
contact_merge_action = ContactMergeAction.new(
|
||||
account: current_account,
|
||||
account: Current.account,
|
||||
base_contact: @base_contact,
|
||||
mergee_contact: @mergee_contact
|
||||
)
|
||||
@@ -23,6 +23,6 @@ class Api::V1::Accounts::Actions::ContactMergesController < Api::BaseController
|
||||
end
|
||||
|
||||
def contacts
|
||||
@contacts ||= current_account.contacts
|
||||
@contacts ||= Current.account.contacts
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::AgentsController < Api::BaseController
|
||||
class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_agent, except: [:create, :index]
|
||||
before_action :check_authorization
|
||||
before_action :find_user, only: [:create]
|
||||
@@ -46,7 +46,7 @@ class Api::V1::Accounts::AgentsController < Api::BaseController
|
||||
|
||||
def save_account_user
|
||||
AccountUser.create!(
|
||||
account_id: current_account.id,
|
||||
account_id: Current.account.id,
|
||||
user_id: @user.id,
|
||||
role: new_agent_params[:role],
|
||||
inviter_id: current_user.id
|
||||
@@ -64,6 +64,6 @@ class Api::V1::Accounts::AgentsController < Api::BaseController
|
||||
end
|
||||
|
||||
def agents
|
||||
@agents ||= current_account.users
|
||||
@agents ||= Current.account.users.order_by_full_name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
class Api::V1::Accounts::BaseController < Api::BaseController
|
||||
before_action :current_account
|
||||
|
||||
private
|
||||
|
||||
def current_account
|
||||
@current_account ||= ensure_current_account
|
||||
Current.account = @current_account
|
||||
end
|
||||
|
||||
def ensure_current_account
|
||||
account = Account.find(params[:account_id])
|
||||
if current_user
|
||||
account_accessible_for_user?(account)
|
||||
elsif @resource&.is_a?(AgentBot)
|
||||
account_accessible_for_bot?(account)
|
||||
end
|
||||
switch_locale account
|
||||
account
|
||||
end
|
||||
|
||||
def account_accessible_for_user?(account)
|
||||
@current_account_user = account.account_users.find_by(user_id: current_user.id)
|
||||
Current.account_user = @current_account_user
|
||||
render_unauthorized('You are not authorized to access this account') unless @current_account_user
|
||||
end
|
||||
|
||||
def account_accessible_for_bot?(account)
|
||||
render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
|
||||
before_action :inbox, only: [:reauthorize_page]
|
||||
|
||||
def register_facebook_page
|
||||
@@ -7,11 +7,11 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
page_id = params[:page_id]
|
||||
inbox_name = params[:inbox_name]
|
||||
ActiveRecord::Base.transaction do
|
||||
facebook_channel = current_account.facebook_pages.create!(
|
||||
facebook_channel = Current.account.facebook_pages.create!(
|
||||
page_id: page_id, user_access_token: user_access_token,
|
||||
page_access_token: page_access_token
|
||||
)
|
||||
@facebook_inbox = current_account.inboxes.create!(name: inbox_name, channel: facebook_channel)
|
||||
@facebook_inbox = Current.account.inboxes.create!(name: inbox_name, channel: facebook_channel)
|
||||
set_avatar(@facebook_inbox, page_id)
|
||||
rescue StandardError => e
|
||||
Rails.logger.info e
|
||||
@@ -22,7 +22,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
@page_details = mark_already_existing_facebook_pages(fb_object.get_connections('me', 'accounts'))
|
||||
end
|
||||
|
||||
# get params[:inbox_id], current_account, params[:omniauth_token]
|
||||
# get params[:inbox_id], current_account. params[:omniauth_token]
|
||||
def reauthorize_page
|
||||
if @inbox&.facebook?
|
||||
fb_page_id = @inbox.channel.page_id
|
||||
@@ -40,7 +40,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
private
|
||||
|
||||
def inbox
|
||||
@inbox = current_account.inboxes.find_by(id: params[:inbox_id])
|
||||
@inbox = Current.account.inboxes.find_by(id: params[:inbox_id])
|
||||
end
|
||||
|
||||
def update_fb_page(fb_page_id, access_token)
|
||||
@@ -50,7 +50,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
end
|
||||
|
||||
def get_fb_page(fb_page_id)
|
||||
current_account.facebook_pages.find_by(page_id: fb_page_id)
|
||||
Current.account.facebook_pages.find_by(page_id: fb_page_id)
|
||||
end
|
||||
|
||||
def fb_object
|
||||
@@ -69,7 +69,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
return [] if data.empty?
|
||||
|
||||
data.inject([]) do |result, page_detail|
|
||||
page_detail[:exists] = current_account.facebook_pages.exists?(page_id: page_detail['id']) ? true : false
|
||||
page_detail[:exists] = Current.account.facebook_pages.exists?(page_id: page_detail['id']) ? true : false
|
||||
result << page_detail
|
||||
end
|
||||
end
|
||||
@@ -81,6 +81,8 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
|
||||
avatar_resource = LocalResource.new(uri)
|
||||
facebook_inbox.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
|
||||
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, SocketError => e
|
||||
Rails.logger.info "invalid url #{file_url} : #{e.message}"
|
||||
end
|
||||
|
||||
def get_avatar_url(page_id)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
||||
class Api::V1::Accounts::CannedResponsesController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_canned_response, only: [:update, :destroy]
|
||||
|
||||
def index
|
||||
@@ -6,7 +6,7 @@ class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
||||
end
|
||||
|
||||
def create
|
||||
@canned_response = current_account.canned_responses.new(canned_response_params)
|
||||
@canned_response = Current.account.canned_responses.new(canned_response_params)
|
||||
@canned_response.save!
|
||||
render json: @canned_response
|
||||
end
|
||||
@@ -24,7 +24,7 @@ class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
||||
private
|
||||
|
||||
def fetch_canned_response
|
||||
@canned_response = current_account.canned_responses.find(params[:id])
|
||||
@canned_response = Current.account.canned_responses.find(params[:id])
|
||||
end
|
||||
|
||||
def canned_response_params
|
||||
@@ -33,9 +33,9 @@ class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
||||
|
||||
def canned_responses
|
||||
if params[:search]
|
||||
current_account.canned_responses.where('short_code ILIKE ?', "#{params[:search]}%")
|
||||
Current.account.canned_responses.where('short_code ILIKE ?', "#{params[:search]}%")
|
||||
else
|
||||
current_account.canned_responses
|
||||
Current.account.canned_responses
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseController
|
||||
before_action :current_account
|
||||
class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts::BaseController
|
||||
before_action :authorize_request
|
||||
|
||||
def create
|
||||
@@ -38,13 +37,13 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseControlle
|
||||
end
|
||||
|
||||
def build_inbox
|
||||
@twilio_channel = current_account.twilio_sms.create!(
|
||||
@twilio_channel = Current.account.twilio_sms.create!(
|
||||
account_sid: permitted_params[:account_sid],
|
||||
auth_token: permitted_params[:auth_token],
|
||||
phone_number: phone_number,
|
||||
medium: medium
|
||||
)
|
||||
@inbox = current_account.inboxes.create(
|
||||
@inbox = Current.account.inboxes.create(
|
||||
name: permitted_params[:name],
|
||||
channel: @twilio_channel
|
||||
)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
class Api::V1::Accounts::Contacts::ContactInboxesController < Api::V1::Accounts::BaseController
|
||||
before_action :ensure_contact
|
||||
before_action :ensure_inbox, only: [:create]
|
||||
before_action :validate_channel_type
|
||||
|
||||
def create
|
||||
source_id = params[:source_id] || SecureRandom.uuid
|
||||
@contact_inbox = ContactInbox.create(contact: @contact, inbox: @inbox, source_id: source_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_channel_type
|
||||
return if @inbox.channel_type == 'Channel::Api'
|
||||
|
||||
render json: { error: 'Contact Inbox creation is only allowed in API inboxes' }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def ensure_inbox
|
||||
@inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||
end
|
||||
|
||||
def ensure_contact
|
||||
@contact = Current.account.contacts.find(params[:contact_id])
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
class Api::V1::Accounts::Contacts::ConversationsController < Api::BaseController
|
||||
class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::BaseController
|
||||
def index
|
||||
@conversations = current_account.conversations.includes(
|
||||
@conversations = Current.account.conversations.includes(
|
||||
:assignee, :contact, :inbox
|
||||
).where(inbox_id: inbox_ids, contact_id: permitted_params[:contact_id])
|
||||
end
|
||||
@@ -9,7 +9,7 @@ class Api::V1::Accounts::Contacts::ConversationsController < Api::BaseController
|
||||
|
||||
def inbox_ids
|
||||
if current_user.administrator?
|
||||
current_account.inboxes.pluck(:id)
|
||||
Current.account.inboxes.pluck(:id)
|
||||
elsif current_user.agent?
|
||||
current_user.assigned_inboxes.pluck(:id)
|
||||
else
|
||||
|
||||
@@ -1,23 +1,36 @@
|
||||
class Api::V1::Accounts::ContactsController < Api::BaseController
|
||||
class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
before_action :check_authorization
|
||||
before_action :fetch_contact, only: [:show, :update]
|
||||
|
||||
def index
|
||||
@contacts = current_account.contacts
|
||||
@contacts = Current.account.contacts
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@contact = Contact.new(contact_create_params)
|
||||
@contact.save!
|
||||
render json: @contact
|
||||
ActiveRecord::Base.transaction do
|
||||
@contact = Current.account.contacts.new(contact_params)
|
||||
@contact.save!
|
||||
@contact_inbox = build_contact_inbox
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@contact.update!(contact_params)
|
||||
@contact.update!(contact_update_params)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
render json: {
|
||||
message: e.record.errors.full_messages.join(', '),
|
||||
contact: Contact.find_by(email: contact_params[:email])
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def search
|
||||
render json: { error: 'Specify search string with parameter q' }, status: :unprocessable_entity if params[:q].blank? && return
|
||||
|
||||
@contacts = Current.account.contacts.where('name LIKE :search OR email LIKE :search', search: "%#{params[:q]}%")
|
||||
end
|
||||
|
||||
private
|
||||
@@ -26,15 +39,30 @@ class Api::V1::Accounts::ContactsController < Api::BaseController
|
||||
authorize(Contact)
|
||||
end
|
||||
|
||||
def build_contact_inbox
|
||||
return if params[:inbox_id].blank?
|
||||
|
||||
inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||
source_id = params[:source_id] || SecureRandom.uuid
|
||||
ContactInbox.create(contact: @contact, inbox: inbox, source_id: source_id)
|
||||
end
|
||||
|
||||
def contact_params
|
||||
params.require(:contact).permit(:name, :email, :phone_number)
|
||||
params.require(:contact).permit(:name, :email, :phone_number, additional_attributes: {}, custom_attributes: {})
|
||||
end
|
||||
|
||||
def contact_custom_attributes
|
||||
return @contact.custom_attributes.merge(contact_params[:custom_attributes]) if contact_params[:custom_attributes]
|
||||
|
||||
@contact.custom_attributes
|
||||
end
|
||||
|
||||
def contact_update_params
|
||||
# we want the merged custom attributes not the original one
|
||||
contact_params.except(:custom_attributes).merge({ custom_attributes: contact_custom_attributes })
|
||||
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)
|
||||
@contact = Current.account.contacts.find(params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
class Api::V1::Accounts::Conversations::AssignmentsController < Api::BaseController
|
||||
before_action :set_conversation, only: [:create]
|
||||
|
||||
class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Accounts::Conversations::BaseController
|
||||
# assign agent to a conversation
|
||||
def create
|
||||
# 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])
|
||||
assignee = Current.account.users.find_by(id: params[:assignee_id])
|
||||
@conversation.update_assignee(assignee)
|
||||
render json: assignee
|
||||
end
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::BaseController
|
||||
before_action :conversation
|
||||
|
||||
private
|
||||
|
||||
def conversation
|
||||
@conversation ||= Current.account.conversations.find_by(display_id: params[:conversation_id])
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,4 @@
|
||||
class Api::V1::Accounts::Conversations::LabelsController < Api::BaseController
|
||||
before_action :set_conversation, only: [:create, :index]
|
||||
|
||||
class Api::V1::Accounts::Conversations::LabelsController < Api::V1::Accounts::Conversations::BaseController
|
||||
def create
|
||||
@conversation.update_labels(params[:labels])
|
||||
@labels = @conversation.label_list
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
class Api::V1::Accounts::Conversations::MessagesController < Api::BaseController
|
||||
before_action :set_conversation, only: [:index, :create]
|
||||
|
||||
class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::Conversations::BaseController
|
||||
def index
|
||||
@messages = message_finder.perform
|
||||
end
|
||||
|
||||
def create
|
||||
mb = Messages::Outgoing::NormalBuilder.new(current_user, @conversation, params)
|
||||
user = current_user || @resource
|
||||
mb = Messages::MessageBuilder.new(user, @conversation, params)
|
||||
@message = mb.perform
|
||||
rescue StandardError => e
|
||||
render_could_not_create_error(e.message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||
class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController
|
||||
include Events::Types
|
||||
before_action :current_account
|
||||
|
||||
before_action :conversation, except: [:index]
|
||||
before_action :contact_inbox, only: [:create]
|
||||
|
||||
@@ -26,6 +26,11 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def transcript
|
||||
ConversationReplyMailer.conversation_transcript(@conversation, params[:email])&.deliver_later if params[:email].present?
|
||||
head :ok
|
||||
end
|
||||
|
||||
def toggle_status
|
||||
if params[:status]
|
||||
@conversation.status = params[:status]
|
||||
@@ -36,18 +41,18 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||
end
|
||||
|
||||
def toggle_typing_status
|
||||
if params[:typing_status] == 'on'
|
||||
case params[:typing_status]
|
||||
when 'on'
|
||||
trigger_typing_event(CONVERSATION_TYPING_ON)
|
||||
elsif params[:typing_status] == 'off'
|
||||
when 'off'
|
||||
trigger_typing_event(CONVERSATION_TYPING_OFF)
|
||||
end
|
||||
head :ok
|
||||
end
|
||||
|
||||
def update_last_seen
|
||||
@conversation.agent_last_seen_at = parsed_last_seen_at
|
||||
@conversation.agent_last_seen_at = DateTime.now.utc
|
||||
@conversation.save!
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
@@ -57,12 +62,8 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: user)
|
||||
end
|
||||
|
||||
def parsed_last_seen_at
|
||||
DateTime.strptime(params[:agent_last_seen_at].to_s, '%s')
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= current_account.conversations.find_by(display_id: params[:id])
|
||||
@conversation ||= Current.account.conversations.find_by(display_id: params[:id])
|
||||
end
|
||||
|
||||
def contact_inbox
|
||||
@@ -71,7 +72,7 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: current_account.id,
|
||||
account_id: Current.account.id,
|
||||
inbox_id: @contact_inbox.inbox_id,
|
||||
contact_id: @contact_inbox.contact_id,
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::FacebookIndicatorsController < Api::BaseController
|
||||
class Api::V1::Accounts::FacebookIndicatorsController < Api::V1::Accounts::BaseController
|
||||
before_action :set_access_token
|
||||
around_action :handle_with_exception
|
||||
|
||||
@@ -38,7 +38,7 @@ class Api::V1::Accounts::FacebookIndicatorsController < Api::BaseController
|
||||
end
|
||||
|
||||
def inbox
|
||||
@inbox ||= current_account.inboxes.find(permitted_params[:inbox_id])
|
||||
@inbox ||= Current.account.inboxes.find(permitted_params[:inbox_id])
|
||||
end
|
||||
|
||||
def set_access_token
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::InboxMembersController < Api::BaseController
|
||||
class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_inbox, only: [:create, :show]
|
||||
before_action :current_agents_ids, only: [:create]
|
||||
|
||||
@@ -12,7 +12,7 @@ class Api::V1::Accounts::InboxMembersController < Api::BaseController
|
||||
end
|
||||
|
||||
def show
|
||||
@agents = current_account.users.where(id: @inbox.members.pluck(:user_id))
|
||||
@agents = Current.account.users.where(id: @inbox.members.select(:user_id))
|
||||
end
|
||||
|
||||
private
|
||||
@@ -40,6 +40,6 @@ class Api::V1::Accounts::InboxMembersController < Api::BaseController
|
||||
end
|
||||
|
||||
def fetch_inbox
|
||||
@inbox = current_account.inboxes.find(params[:inbox_id])
|
||||
@inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
class Api::V1::Accounts::InboxesController < Api::BaseController
|
||||
before_action :current_account
|
||||
class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_inbox, except: [:index, :create]
|
||||
before_action :fetch_agent_bot, only: [:set_agent_bot]
|
||||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
@inboxes = policy_scope(current_account.inboxes)
|
||||
@inboxes = policy_scope(Current.account.inboxes.order_by_name.includes(:channel, :avatar_attachment))
|
||||
end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
channel = web_widgets.create!(permitted_params[:channel].except(:type)) if permitted_params[:channel][:type] == 'web_widget'
|
||||
@inbox = current_account.inboxes.build(name: permitted_params[:name], channel: channel)
|
||||
channel = create_channel
|
||||
@inbox = Current.account.inboxes.build(
|
||||
name: permitted_params[:name],
|
||||
greeting_message: permitted_params[:greeting_message],
|
||||
greeting_enabled: permitted_params[:greeting_enabled],
|
||||
channel: channel
|
||||
)
|
||||
@inbox.avatar.attach(permitted_params[:avatar])
|
||||
@inbox.save!
|
||||
end
|
||||
@@ -19,7 +23,10 @@ class Api::V1::Accounts::InboxesController < Api::BaseController
|
||||
|
||||
def update
|
||||
@inbox.update(inbox_update_params.except(:channel))
|
||||
@inbox.channel.update!(inbox_update_params[:channel]) if @inbox.channel.is_a?(Channel::WebWidget) && inbox_update_params[:channel].present?
|
||||
return unless @inbox.channel.is_a?(Channel::WebWidget) && inbox_update_params[:channel].present?
|
||||
|
||||
@inbox.channel.update!(inbox_update_params[:channel])
|
||||
update_channel_feature_flags
|
||||
end
|
||||
|
||||
def set_agent_bot
|
||||
@@ -41,27 +48,50 @@ class Api::V1::Accounts::InboxesController < Api::BaseController
|
||||
private
|
||||
|
||||
def fetch_inbox
|
||||
@inbox = current_account.inboxes.find(params[:id])
|
||||
@inbox = Current.account.inboxes.find(params[:id])
|
||||
end
|
||||
|
||||
def fetch_agent_bot
|
||||
@agent_bot = AgentBot.find(params[:agent_bot]) if params[:agent_bot]
|
||||
end
|
||||
|
||||
def web_widgets
|
||||
current_account.web_widgets
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize(Inbox)
|
||||
end
|
||||
|
||||
def create_channel
|
||||
case permitted_params[:channel][:type]
|
||||
when 'web_widget'
|
||||
Current.account.web_widgets.create!(permitted_params[:channel].except(:type))
|
||||
when 'api'
|
||||
Current.account.api_channels.create!(permitted_params[:channel].except(:type))
|
||||
when 'email'
|
||||
Current.account.email_channels.create!(permitted_params[:channel].except(:type))
|
||||
end
|
||||
end
|
||||
|
||||
def update_channel_feature_flags
|
||||
return unless inbox_update_params[:channel].key? :selected_feature_flags
|
||||
|
||||
@inbox.channel.selected_feature_flags = inbox_update_params[:channel][:selected_feature_flags]
|
||||
@inbox.channel.save!
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :avatar, :name, channel: [:type, :website_url, :widget_color, :welcome_title, :welcome_tagline, :agent_away_message])
|
||||
params.permit(:id, :avatar, :name, :greeting_message, :greeting_enabled, channel:
|
||||
[:type, :website_url, :widget_color, :welcome_title, :welcome_tagline, :webhook_url, :email])
|
||||
end
|
||||
|
||||
def inbox_update_params
|
||||
params.permit(:enable_auto_assignment, :name, :avatar, channel: [:website_url, :widget_color, :welcome_title,
|
||||
:welcome_tagline, :agent_away_message])
|
||||
params.permit(:enable_auto_assignment, :name, :avatar, :greeting_message, :greeting_enabled,
|
||||
channel: [
|
||||
:website_url,
|
||||
:widget_color,
|
||||
:welcome_title,
|
||||
:welcome_tagline,
|
||||
:webhook_url,
|
||||
:email,
|
||||
{ selected_feature_flags: [] }
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
class Api::V1::Accounts::Integrations::AppsController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_apps, only: [:index]
|
||||
before_action :fetch_app, only: [:show]
|
||||
|
||||
def index; end
|
||||
|
||||
def show; end
|
||||
|
||||
private
|
||||
|
||||
def fetch_apps
|
||||
@apps = Integrations::App.all.select(&:active?)
|
||||
end
|
||||
|
||||
def fetch_app
|
||||
@app = Integrations::App.find(id: params[:id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_hook, only: [:update, :destroy]
|
||||
|
||||
def create
|
||||
builder = Integrations::Slack::HookBuilder.new(
|
||||
account: Current.account,
|
||||
code: params[:code],
|
||||
inbox_id: params[:inbox_id]
|
||||
)
|
||||
@hook = builder.perform
|
||||
create_chatwoot_slack_channel
|
||||
end
|
||||
|
||||
def update
|
||||
create_chatwoot_slack_channel
|
||||
render json: @hook
|
||||
end
|
||||
|
||||
def destroy
|
||||
@hook.destroy
|
||||
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_hook
|
||||
@hook = Integrations::Hook.where(account: Current.account).find_by(app_id: 'slack')
|
||||
end
|
||||
|
||||
def create_chatwoot_slack_channel
|
||||
channel = params[:channel] || 'customer-conversations'
|
||||
builder = Integrations::Slack::ChannelBuilder.new(
|
||||
hook: @hook, channel: channel
|
||||
)
|
||||
builder.perform
|
||||
end
|
||||
end
|
||||
@@ -1,10 +1,38 @@
|
||||
class Api::V1::Accounts::LabelsController < Api::BaseController
|
||||
# list all labels in account
|
||||
class Api::V1::Accounts::LabelsController < Api::V1::Accounts::BaseController
|
||||
before_action :current_account
|
||||
before_action :fetch_label, except: [:index, :create]
|
||||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
@labels = current_account.all_conversation_tags
|
||||
@labels = policy_scope(Current.account.labels)
|
||||
end
|
||||
|
||||
def most_used
|
||||
@labels = ActsAsTaggableOn::Tag.most_used(params[:count] || 10)
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@label = Current.account.labels.create!(permitted_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@label.update!(permitted_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@label.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_label
|
||||
@label = Current.account.labels.find(params[:id])
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize(Label)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.require(:label).permit(:title, :description, :color, :show_on_sidebar)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::NotificationSettingsController < Api::BaseController
|
||||
class Api::V1::Accounts::NotificationSettingsController < Api::V1::Accounts::BaseController
|
||||
before_action :set_user, :load_notification_setting
|
||||
|
||||
def show; end
|
||||
@@ -16,7 +16,7 @@ class Api::V1::Accounts::NotificationSettingsController < Api::BaseController
|
||||
end
|
||||
|
||||
def load_notification_setting
|
||||
@notification_setting = @user.notification_settings.find_by(account_id: current_account.id)
|
||||
@notification_setting = @user.notification_settings.find_by(account_id: Current.account.id)
|
||||
end
|
||||
|
||||
def notification_setting_params
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
class Api::V1::Accounts::NotificationsController < Api::BaseController
|
||||
class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseController
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
before_action :fetch_notification, only: [:update]
|
||||
before_action :set_primary_actor, only: [:read_all]
|
||||
|
||||
def index
|
||||
@notifications = current_user.notifications.where(account_id: current_account.id)
|
||||
render json: @notifications
|
||||
@unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count
|
||||
@notifications = current_user.notifications.where(account_id: current_account.id).page params[:page]
|
||||
end
|
||||
|
||||
def read_all
|
||||
if @primary_actor
|
||||
current_user.notifications.where(account_id: current_account.id, primary_actor: @primary_actor, read_at: nil)
|
||||
.update(read_at: DateTime.now.utc)
|
||||
else
|
||||
current_user.notifications.where(account_id: current_account.id, read_at: nil).update(read_at: DateTime.now.utc)
|
||||
end
|
||||
|
||||
head :ok
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -15,6 +27,13 @@ class Api::V1::Accounts::NotificationsController < Api::BaseController
|
||||
|
||||
private
|
||||
|
||||
def set_primary_actor
|
||||
return unless params[:primary_actor_type]
|
||||
return unless Notification::PRIMARY_ACTORS.include?(params[:primary_actor_type])
|
||||
|
||||
@primary_actor = params[:primary_actor_type].safe_constantize.find_by(id: params[:primary_actor_id])
|
||||
end
|
||||
|
||||
def fetch_notification
|
||||
@notification = current_user.notifications.find(params[:id])
|
||||
end
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
class Api::V1::Accounts::SubscriptionsController < Api::BaseController
|
||||
skip_before_action :check_subscription
|
||||
|
||||
before_action :check_billing_enabled
|
||||
|
||||
def index
|
||||
render json: current_account.subscription_data
|
||||
end
|
||||
|
||||
def status
|
||||
render json: current_account.subscription.summary
|
||||
end
|
||||
end
|
||||
@@ -1,14 +1,13 @@
|
||||
class Api::V1::Accounts::WebhooksController < Api::BaseController
|
||||
before_action :current_account
|
||||
class Api::V1::Accounts::WebhooksController < Api::V1::Accounts::BaseController
|
||||
before_action :check_authorization
|
||||
before_action :fetch_webhook, only: [:update, :destroy]
|
||||
|
||||
def index
|
||||
@webhooks = current_account.webhooks
|
||||
@webhooks = Current.account.webhooks
|
||||
end
|
||||
|
||||
def create
|
||||
@webhook = current_account.webhooks.new(webhook_params)
|
||||
@webhook = Current.account.webhooks.new(webhook_params)
|
||||
@webhook.save!
|
||||
end
|
||||
|
||||
@@ -28,7 +27,7 @@ class Api::V1::Accounts::WebhooksController < Api::BaseController
|
||||
end
|
||||
|
||||
def fetch_webhook
|
||||
@webhook = current_account.webhooks.find(params[:id])
|
||||
@webhook = Current.account.webhooks.find(params[:id])
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
|
||||
+8
-7
@@ -1,8 +1,8 @@
|
||||
class Api::V1::Accounts::AccountsController < Api::BaseController
|
||||
class Api::V1::AccountsController < Api::BaseController
|
||||
include AuthHelper
|
||||
|
||||
skip_before_action :verify_authenticity_token, only: [:create]
|
||||
skip_before_action :authenticate_user!, :set_current_user, :check_subscription, :handle_with_exception,
|
||||
skip_before_action :authenticate_user!, :set_current_user, :handle_with_exception,
|
||||
only: [:create], raise: false
|
||||
before_action :check_signup_enabled, only: [:create]
|
||||
before_action :fetch_account, except: [:create]
|
||||
@@ -14,14 +14,15 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
||||
with: :render_error_response
|
||||
|
||||
def create
|
||||
@user = AccountBuilder.new(
|
||||
@user, @account = AccountBuilder.new(
|
||||
account_name: account_params[:account_name],
|
||||
email: account_params[:email],
|
||||
confirmed: confirmed?
|
||||
confirmed: confirmed?,
|
||||
user: current_user
|
||||
).perform
|
||||
if @user
|
||||
send_auth_headers(@user)
|
||||
render partial: 'devise/auth.json', locals: { resource: @user }
|
||||
render 'api/v1/accounts/create.json', locals: { resource: @user }
|
||||
else
|
||||
render_error_response(CustomExceptions::Account::SignupFailed.new({}))
|
||||
end
|
||||
@@ -32,7 +33,7 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
||||
end
|
||||
|
||||
def update
|
||||
@account.update!(account_params.slice(:name, :locale, :domain, :support_email, :domain_emails_enabled))
|
||||
@account.update!(account_params.slice(:name, :locale, :domain, :support_email))
|
||||
end
|
||||
|
||||
def update_active_at
|
||||
@@ -57,7 +58,7 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
||||
end
|
||||
|
||||
def account_params
|
||||
params.permit(:account_name, :email, :name, :locale, :domain, :support_email, :domain_emails_enabled)
|
||||
params.permit(:account_name, :email, :name, :locale, :domain, :support_email)
|
||||
end
|
||||
|
||||
def check_signup_enabled
|
||||
@@ -1,6 +1,5 @@
|
||||
class Api::V1::AgentBotsController < Api::BaseController
|
||||
skip_before_action :authenticate_user!
|
||||
skip_before_action :check_subscription
|
||||
|
||||
def index
|
||||
render json: AgentBot.all
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
class Api::V1::Integrations::WebhooksController < ApplicationController
|
||||
def create
|
||||
builder = Integrations::Slack::IncomingMessageBuilder.new(params)
|
||||
response = builder.perform
|
||||
render json: response
|
||||
end
|
||||
end
|
||||
@@ -16,6 +16,14 @@ class Api::V1::ProfilesController < Api::BaseController
|
||||
end
|
||||
|
||||
def profile_params
|
||||
params.require(:profile).permit(:email, :name, :password, :password_confirmation, :avatar)
|
||||
params.require(:profile).permit(
|
||||
:email,
|
||||
:name,
|
||||
:display_name,
|
||||
:password,
|
||||
:password_confirmation,
|
||||
:avatar,
|
||||
:availability
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
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, only: [:chargebee]
|
||||
before_action :check_billing_enabled, only: [:chargebee]
|
||||
|
||||
def chargebee
|
||||
chargebee_consumer.consume
|
||||
head :ok
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
head :ok
|
||||
end
|
||||
|
||||
def twitter_crc
|
||||
render json: { response_token: "sha256=#{twitter_client.generate_crc(params[:crc_token])}" }
|
||||
@@ -34,16 +22,6 @@ class Api::V1::WebhooksController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def login_from_basic_auth
|
||||
authenticate_or_request_with_http_basic do |username, password|
|
||||
username == ENV['CHARGEBEE_WEBHOOK_USERNAME'] && password == ENV['CHARGEBEE_WEBHOOK_PASSWORD']
|
||||
end
|
||||
end
|
||||
|
||||
def chargebee_consumer
|
||||
@chargebee_consumer ||= ::Webhooks::Chargebee.new(params)
|
||||
end
|
||||
|
||||
def twitter_consumer
|
||||
@twitter_consumer ||= ::Webhooks::Twitter.new(params)
|
||||
end
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
class Api::V1::Widget::BaseController < ApplicationController
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
|
||||
private
|
||||
|
||||
def conversation
|
||||
@@ -27,4 +30,14 @@ class Api::V1::Widget::BaseController < ApplicationController
|
||||
)
|
||||
@contact = @contact_inbox.contact
|
||||
end
|
||||
|
||||
def browser_params
|
||||
{
|
||||
browser_name: browser.name,
|
||||
browser_version: browser.full_version,
|
||||
device_name: browser.device.name,
|
||||
platform_name: browser.platform.name,
|
||||
platform_version: browser.platform.version
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
|
||||
def update
|
||||
contact_identify_action = ContactIdentifyAction.new(
|
||||
contact: @contact,
|
||||
@@ -13,6 +10,6 @@ class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
|
||||
private
|
||||
|
||||
def permitted_params
|
||||
params.permit(:website_token, :identifier, :email, :name, :avatar_url)
|
||||
params.permit(:website_token, :identifier, :email, :name, :avatar_url, custom_attributes: {})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||
include Events::Types
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
|
||||
def index
|
||||
@conversation = conversation
|
||||
end
|
||||
|
||||
def update_last_seen
|
||||
head :ok && return if conversation.nil?
|
||||
|
||||
conversation.contact_last_seen_at = DateTime.now.utc
|
||||
conversation.save!
|
||||
head :ok
|
||||
end
|
||||
|
||||
def transcript
|
||||
if permitted_params[:email].present? && conversation.present?
|
||||
ConversationReplyMailer.conversation_transcript(
|
||||
conversation,
|
||||
permitted_params[:email]
|
||||
)&.deliver_later
|
||||
end
|
||||
head :ok
|
||||
end
|
||||
|
||||
def toggle_typing
|
||||
head :ok && return if conversation.nil?
|
||||
|
||||
if permitted_params[:typing_status] == 'on'
|
||||
case permitted_params[:typing_status]
|
||||
when 'on'
|
||||
trigger_typing_event(CONVERSATION_TYPING_ON)
|
||||
elsif permitted_params[:typing_status] == 'off'
|
||||
when 'off'
|
||||
trigger_typing_event(CONVERSATION_TYPING_OFF)
|
||||
end
|
||||
|
||||
@@ -26,6 +43,6 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :typing_status, :website_token)
|
||||
params.permit(:id, :typing_status, :website_token, :email)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
class Api::V1::Widget::EventsController < Api::V1::Widget::BaseController
|
||||
include Events::Types
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
|
||||
def create
|
||||
Rails.configuration.dispatcher.dispatch(permitted_params[:name], Time.zone.now, contact_inbox: @contact_inbox)
|
||||
Rails.configuration.dispatcher.dispatch(permitted_params[:name], Time.zone.now, contact_inbox: @contact_inbox, event_info: event_info)
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def event_info
|
||||
{
|
||||
widget_language: params[:locale],
|
||||
browser_language: browser.accept_language.first&.code,
|
||||
browser: browser_params
|
||||
}
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:name, :website_token)
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Api::V1::Widget::InboxMembersController < Api::V1::Widget::BaseController
|
||||
before_action :set_web_widget
|
||||
skip_before_action :set_contact
|
||||
|
||||
def index
|
||||
@inbox_members = @web_widget.inbox.inbox_members.includes(:user)
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
class Api::V1::Widget::LabelsController < Api::V1::Widget::BaseController
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
|
||||
def create
|
||||
conversation.label_list.add(permitted_params[:label])
|
||||
conversation.save!
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
before_action :set_conversation, only: [:create]
|
||||
before_action :set_message, only: [:update]
|
||||
|
||||
@@ -47,9 +45,10 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
contact_id: @contact.id,
|
||||
sender: @contact,
|
||||
content: permitted_params[:message][:content],
|
||||
inbox_id: conversation.inbox_id,
|
||||
echo_id: permitted_params[:message][:echo_id],
|
||||
message_type: :incoming
|
||||
}
|
||||
end
|
||||
@@ -68,16 +67,6 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
}
|
||||
end
|
||||
|
||||
def browser_params
|
||||
{
|
||||
browser_name: browser.name,
|
||||
browser_version: browser.full_version,
|
||||
device_name: browser.device.name,
|
||||
platform_name: browser.platform.name,
|
||||
platform_version: browser.platform.version
|
||||
}
|
||||
end
|
||||
|
||||
def timestamp_params
|
||||
{
|
||||
timestamp: permitted_params[:message][:timestamp]
|
||||
@@ -124,11 +113,11 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
end
|
||||
|
||||
def message_update_params
|
||||
params.permit(message: [submitted_values: [:name, :title, :value]])
|
||||
params.permit(message: [{ submitted_values: [:name, :title, :value] }])
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :before, :website_token, contact: [:email], message: [:content, :referer_url, :timestamp])
|
||||
params.permit(:id, :before, :website_token, contact: [:email], message: [:content, :referer_url, :timestamp, :echo_id])
|
||||
end
|
||||
|
||||
def set_message
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Api::V2::Accounts::ReportsController < Api::BaseController
|
||||
class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||
def account
|
||||
builder = V2::ReportBuilder.new(current_account, account_report_params)
|
||||
builder = V2::ReportBuilder.new(Current.account, account_report_params)
|
||||
data = builder.build
|
||||
render json: data
|
||||
end
|
||||
@@ -29,7 +29,7 @@ class Api::V2::Accounts::ReportsController < Api::BaseController
|
||||
end
|
||||
|
||||
def account_summary_metrics
|
||||
builder = V2::ReportBuilder.new(current_account, account_summary_params)
|
||||
builder = V2::ReportBuilder.new(Current.account, account_summary_params)
|
||||
builder.summary
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
class ApiController < ApplicationController
|
||||
skip_before_action :set_current_user, only: [:index]
|
||||
skip_before_action :check_subscription, only: [:index]
|
||||
|
||||
def index
|
||||
render json: { version: Chatwoot.config[:version], timestamp: Time.now.utc.to_formatted_s(:db) }
|
||||
|
||||
@@ -5,7 +5,6 @@ class ApplicationController < ActionController::Base
|
||||
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
|
||||
@@ -13,40 +12,6 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
private
|
||||
|
||||
def current_account
|
||||
@current_account ||= find_current_account
|
||||
Current.account = @current_account
|
||||
end
|
||||
|
||||
def find_current_account
|
||||
account = Account.find(params[:account_id])
|
||||
if current_user
|
||||
account_accessible_for_user?(account)
|
||||
elsif @resource&.is_a?(AgentBot)
|
||||
account_accessible_for_bot?(account)
|
||||
end
|
||||
switch_locale account
|
||||
account
|
||||
end
|
||||
|
||||
def switch_locale(account)
|
||||
# priority is for locale set in query string (mostly for widget/from js sdk)
|
||||
locale ||= (I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil)
|
||||
# if local is not set in param, lets try account
|
||||
locale ||= (I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil)
|
||||
I18n.locale = locale || I18n.default_locale
|
||||
end
|
||||
|
||||
def account_accessible_for_user?(account)
|
||||
@current_account_user = account.account_users.find_by(user_id: current_user.id)
|
||||
Current.account_user = @current_account_user
|
||||
render_unauthorized('You are not authorized to access this account') unless @current_account_user
|
||||
end
|
||||
|
||||
def account_accessible_for_bot?(account)
|
||||
render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
|
||||
end
|
||||
|
||||
def handle_with_exception
|
||||
yield
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
@@ -65,7 +30,7 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def current_subscription
|
||||
@subscription ||= current_account.subscription
|
||||
@subscription ||= Current.account.subscription
|
||||
end
|
||||
|
||||
def render_unauthorized(message)
|
||||
@@ -94,16 +59,20 @@ class ApplicationController < ActionController::Base
|
||||
render json: exception.to_hash, status: exception.http_status
|
||||
end
|
||||
|
||||
def check_subscription
|
||||
# This block is left over from the initial version of chatwoot
|
||||
# We might reuse this later in the hosted version of chatwoot.
|
||||
return if !ENV['BILLING_ENABLED'] || !current_user
|
||||
def locale_from_params
|
||||
I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil
|
||||
end
|
||||
|
||||
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
|
||||
def locale_from_account(account)
|
||||
I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil
|
||||
end
|
||||
|
||||
def switch_locale(account)
|
||||
# priority is for locale set in query string (mostly for widget/from js sdk)
|
||||
locale ||= locale_from_params
|
||||
# if local is not set in param, lets try account
|
||||
locale ||= locale_from_account(account)
|
||||
I18n.locale = locale || I18n.default_locale
|
||||
end
|
||||
|
||||
def pundit_user
|
||||
|
||||
@@ -15,7 +15,10 @@ class DashboardController < ActionController::Base
|
||||
'WIDGET_BRAND_URL',
|
||||
'TERMS_URL',
|
||||
'PRIVACY_URL',
|
||||
'DISPLAY_MANIFEST'
|
||||
'DISPLAY_MANIFEST',
|
||||
'CREATE_NEW_ACCOUNT_FROM_DASHBOARD'
|
||||
).merge(
|
||||
APP_VERSION: Chatwoot.config[:version]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,6 +25,6 @@ class DeviseOverrides::ConfirmationsController < Devise::ConfirmationsController
|
||||
user.reset_password_token = enc
|
||||
user.reset_password_sent_at = Time.now.utc
|
||||
user.save(validate: false)
|
||||
'/app/auth/password/edit?config=default&redirect_url=&reset_password_token=' + raw
|
||||
"/app/auth/password/edit?config=default&redirect_url=&reset_password_token=#{raw}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,7 +27,7 @@ class Twitter::CallbacksController < Twitter::BaseController
|
||||
end
|
||||
|
||||
def account
|
||||
@account ||= Account.find_by!(id: account_id)
|
||||
@account ||= Account.find(account_id)
|
||||
end
|
||||
|
||||
def twitter_app_redirect_url
|
||||
|
||||
@@ -4,13 +4,14 @@ class WidgetsController < ActionController::Base
|
||||
before_action :set_token
|
||||
before_action :set_contact
|
||||
before_action :build_contact
|
||||
after_action :allow_iframe_requests
|
||||
|
||||
def index; end
|
||||
|
||||
private
|
||||
|
||||
def set_global_config
|
||||
@global_config = GlobalConfig.get('LOGO_THUMBNAIL', 'INSTALLATION_NAME', 'WIDGET_BRAND_URL')
|
||||
@global_config = GlobalConfig.get('LOGO_THUMBNAIL', 'BRAND_NAME', 'WIDGET_BRAND_URL')
|
||||
end
|
||||
|
||||
def set_web_widget
|
||||
@@ -50,4 +51,8 @@ class WidgetsController < ActionController::Base
|
||||
def permitted_params
|
||||
params.permit(:website_token, :cw_conversation)
|
||||
end
|
||||
|
||||
def allow_iframe_requests
|
||||
response.headers.delete('X-Frame-Options')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,7 +24,7 @@ class UserDashboard < Administrate::BaseDashboard
|
||||
confirmation_sent_at: Field::DateTime,
|
||||
unconfirmed_email: Field::String,
|
||||
name: Field::String,
|
||||
nickname: Field::String,
|
||||
display_name: Field::String,
|
||||
email: Field::String,
|
||||
tokens: Field::String.with_options(searchable: false),
|
||||
created_at: Field::DateTime,
|
||||
@@ -53,7 +53,7 @@ class UserDashboard < Administrate::BaseDashboard
|
||||
avatar_url
|
||||
unconfirmed_email
|
||||
name
|
||||
nickname
|
||||
display_name
|
||||
email
|
||||
created_at
|
||||
updated_at
|
||||
@@ -65,7 +65,7 @@ class UserDashboard < Administrate::BaseDashboard
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
name
|
||||
nickname
|
||||
display_name
|
||||
email
|
||||
password
|
||||
].freeze
|
||||
|
||||
@@ -9,8 +9,10 @@ class AsyncDispatcher < BaseDispatcher
|
||||
end
|
||||
|
||||
def listeners
|
||||
listeners = [EventListener.instance, WebhookListener.instance]
|
||||
listeners << SubscriptionListener.instance if ENV['BILLING_ENABLED']
|
||||
listeners
|
||||
[
|
||||
EventListener.instance,
|
||||
WebhookListener.instance,
|
||||
InstallationWebhookListener.instance, HookListener.instance
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
class AccountDrop < BaseDrop
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
class BaseDrop < Liquid::Drop
|
||||
def initialize(obj)
|
||||
@obj = obj
|
||||
end
|
||||
|
||||
def id
|
||||
@obj.try(:id)
|
||||
end
|
||||
|
||||
def name
|
||||
@obj.try(:name)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class ConversationDrop < BaseDrop
|
||||
def display_id
|
||||
@obj.try(:display_id)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
class InboxDrop < BaseDrop
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class UserDrop < BaseDrop
|
||||
def available_name
|
||||
@obj.try(:available_name)
|
||||
end
|
||||
end
|
||||
@@ -62,14 +62,15 @@ class ConversationFinder
|
||||
|
||||
def find_all_conversations
|
||||
@conversations = current_account.conversations.includes(
|
||||
:assignee, :contact, :inbox
|
||||
:assignee, :inbox, contact: [:avatar_attachment]
|
||||
).where(inbox_id: @inbox_ids)
|
||||
end
|
||||
|
||||
def filter_by_assignee_type
|
||||
if @assignee_type == 'me'
|
||||
case @assignee_type
|
||||
when 'me'
|
||||
@conversations = @conversations.assigned_to(current_user)
|
||||
elsif @assignee_type == 'unassigned'
|
||||
when 'unassigned'
|
||||
@conversations = @conversations.unassigned
|
||||
end
|
||||
@conversations
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
module Api::V1::SubscriptionsHelper
|
||||
end
|
||||
@@ -10,6 +10,10 @@ class ApiClient {
|
||||
}
|
||||
|
||||
get url() {
|
||||
return `${this.baseUrl()}/${this.resource}`;
|
||||
}
|
||||
|
||||
baseUrl() {
|
||||
let url = this.apiVersion;
|
||||
if (this.options.accountScoped) {
|
||||
const isInsideAccountScopedURLs = window.location.pathname.includes(
|
||||
@@ -21,7 +25,8 @@ class ApiClient {
|
||||
url = `${url}/accounts/${accountId}`;
|
||||
}
|
||||
}
|
||||
return `${url}/${this.resource}`;
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
get() {
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
/* global axios */
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class AccountAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('', { accountScoped: true });
|
||||
}
|
||||
|
||||
createAccount(data) {
|
||||
return axios.post(`${this.apiVersion}/accounts`, data);
|
||||
}
|
||||
}
|
||||
|
||||
export default new AccountAPI();
|
||||
|
||||
@@ -118,21 +118,24 @@ export default {
|
||||
return axios.post(urlData.url, { email });
|
||||
},
|
||||
|
||||
profileUpdate({ name, email, password, password_confirmation, avatar }) {
|
||||
profileUpdate({
|
||||
password,
|
||||
password_confirmation,
|
||||
displayName,
|
||||
...profileAttributes
|
||||
}) {
|
||||
const formData = new FormData();
|
||||
if (name) {
|
||||
formData.append('profile[name]', name);
|
||||
}
|
||||
if (email) {
|
||||
formData.append('profile[email]', email);
|
||||
}
|
||||
Object.keys(profileAttributes).forEach(key => {
|
||||
const value = profileAttributes[key];
|
||||
if (value) {
|
||||
formData.append(`profile[${key}]`, value);
|
||||
}
|
||||
});
|
||||
formData.append('profile[display_name]', displayName || '');
|
||||
if (password && password_confirmation) {
|
||||
formData.append('profile[password]', password);
|
||||
formData.append('profile[password_confirmation]', password_confirmation);
|
||||
}
|
||||
if (avatar) {
|
||||
formData.append('profile[avatar]', avatar);
|
||||
}
|
||||
return axios.put(endPoints('profileUpdate').url, formData);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/* 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;
|
||||
},
|
||||
};
|
||||
@@ -6,20 +6,6 @@ class FBChannel extends ApiClient {
|
||||
super('facebook_indicators', { accountScoped: true });
|
||||
}
|
||||
|
||||
markSeen({ inboxId, contactId }) {
|
||||
return axios.post(`${this.url}/mark_seen`, {
|
||||
inbox_id: inboxId,
|
||||
contact_id: contactId,
|
||||
});
|
||||
}
|
||||
|
||||
toggleTyping({ status, inboxId, contactId }) {
|
||||
return axios.post(`${this.url}/typing_${status}`, {
|
||||
inbox_id: inboxId,
|
||||
contact_id: contactId,
|
||||
});
|
||||
}
|
||||
|
||||
create(params) {
|
||||
return axios.post(
|
||||
`${this.url.replace(this.resource, '')}callbacks/register_facebook_page`,
|
||||
|
||||
@@ -33,14 +33,6 @@ const endPoints = {
|
||||
},
|
||||
params: { omniauth_token: '' },
|
||||
},
|
||||
|
||||
subscriptions: {
|
||||
get() {
|
||||
return {
|
||||
url: '/api/v1/subscriptions',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default page => {
|
||||
|
||||
@@ -6,13 +6,14 @@ class ConversationApi extends ApiClient {
|
||||
super('conversations', { accountScoped: true });
|
||||
}
|
||||
|
||||
get({ inboxId, status, assigneeType, page }) {
|
||||
get({ inboxId, status, assigneeType, page, labels }) {
|
||||
return axios.get(this.url, {
|
||||
params: {
|
||||
inbox_id: inboxId,
|
||||
status,
|
||||
assignee_type: assigneeType,
|
||||
page,
|
||||
labels,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -28,10 +29,8 @@ class ConversationApi extends ApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
markMessageRead({ id, lastSeen }) {
|
||||
return axios.post(`${this.url}/${id}/update_last_seen`, {
|
||||
agent_last_seen_at: lastSeen,
|
||||
});
|
||||
markMessageRead({ id }) {
|
||||
return axios.post(`${this.url}/${id}/update_last_seen`);
|
||||
}
|
||||
|
||||
toggleTyping({ conversationId, status }) {
|
||||
@@ -43,6 +42,21 @@ class ConversationApi extends ApiClient {
|
||||
mute(conversationId) {
|
||||
return axios.post(`${this.url}/${conversationId}/mute`);
|
||||
}
|
||||
|
||||
meta({ inboxId, status, assigneeType, labels }) {
|
||||
return axios.get(`${this.url}/meta`, {
|
||||
params: {
|
||||
inbox_id: inboxId,
|
||||
status,
|
||||
assignee_type: assigneeType,
|
||||
labels,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
sendEmailTranscript({ conversationId, email }) {
|
||||
return axios.post(`${this.url}/${conversationId}/transcript`, { email });
|
||||
}
|
||||
}
|
||||
|
||||
export default new ConversationApi();
|
||||
|
||||
@@ -7,10 +7,11 @@ class MessageApi extends ApiClient {
|
||||
super('conversations', { accountScoped: true });
|
||||
}
|
||||
|
||||
create({ conversationId, message, private: isPrivate }) {
|
||||
create({ conversationId, message, private: isPrivate, contentAttributes }) {
|
||||
return axios.post(`${this.url}/${conversationId}/messages`, {
|
||||
content: message,
|
||||
private: isPrivate,
|
||||
content_attributes: contentAttributes,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,9 +21,10 @@ class MessageApi extends ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
sendAttachment([conversationId, { file }]) {
|
||||
sendAttachment([conversationId, { file, isPrivate = false }]) {
|
||||
const formData = new FormData();
|
||||
formData.append('attachments[]', file, file.name);
|
||||
formData.append('private', isPrivate);
|
||||
return axios({
|
||||
method: 'post',
|
||||
url: `${this.url}/${conversationId}/messages`,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/* global axios */
|
||||
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class IntegrationsAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('integrations/apps', { accountScoped: true });
|
||||
}
|
||||
|
||||
connectSlack(code) {
|
||||
return axios.post(`${this.baseUrl()}/integrations/slack`, {
|
||||
code: code,
|
||||
});
|
||||
}
|
||||
|
||||
delete(integrationId) {
|
||||
return axios.delete(`${this.baseUrl()}/integrations/${integrationId}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new IntegrationsAPI();
|
||||
@@ -0,0 +1,9 @@
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class LabelsAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('labels', { accountScoped: true });
|
||||
}
|
||||
}
|
||||
|
||||
export default new LabelsAPI();
|
||||
@@ -9,7 +9,5 @@ describe('#FBChannel', () => {
|
||||
expect(fbChannel).toHaveProperty('create');
|
||||
expect(fbChannel).toHaveProperty('update');
|
||||
expect(fbChannel).toHaveProperty('delete');
|
||||
expect(fbChannel).toHaveProperty('markSeen');
|
||||
expect(fbChannel).toHaveProperty('toggleTyping');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import agents from '../contacts';
|
||||
import contacts from '../contacts';
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
describe('#ContactsAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
expect(agents).toBeInstanceOf(ApiClient);
|
||||
expect(agents).toHaveProperty('get');
|
||||
expect(agents).toHaveProperty('show');
|
||||
expect(agents).toHaveProperty('create');
|
||||
expect(agents).toHaveProperty('update');
|
||||
expect(agents).toHaveProperty('delete');
|
||||
expect(agents).toHaveProperty('getConversations');
|
||||
expect(contacts).toBeInstanceOf(ApiClient);
|
||||
expect(contacts).toHaveProperty('get');
|
||||
expect(contacts).toHaveProperty('show');
|
||||
expect(contacts).toHaveProperty('create');
|
||||
expect(contacts).toHaveProperty('update');
|
||||
expect(contacts).toHaveProperty('delete');
|
||||
expect(contacts).toHaveProperty('getConversations');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import conversationAPI from '../../inbox/conversation';
|
||||
import ApiClient from '../../ApiClient';
|
||||
|
||||
describe('#ConversationAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
expect(conversationAPI).toBeInstanceOf(ApiClient);
|
||||
expect(conversationAPI).toHaveProperty('get');
|
||||
expect(conversationAPI).toHaveProperty('show');
|
||||
expect(conversationAPI).toHaveProperty('create');
|
||||
expect(conversationAPI).toHaveProperty('update');
|
||||
expect(conversationAPI).toHaveProperty('delete');
|
||||
expect(conversationAPI).toHaveProperty('toggleStatus');
|
||||
expect(conversationAPI).toHaveProperty('assignAgent');
|
||||
expect(conversationAPI).toHaveProperty('markMessageRead');
|
||||
expect(conversationAPI).toHaveProperty('toggleTyping');
|
||||
expect(conversationAPI).toHaveProperty('mute');
|
||||
expect(conversationAPI).toHaveProperty('meta');
|
||||
expect(conversationAPI).toHaveProperty('sendEmailTranscript');
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import inboxes from '../inboxes';
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
describe('#AgentAPI', () => {
|
||||
describe('#InboxesAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
expect(inboxes).toBeInstanceOf(ApiClient);
|
||||
expect(inboxes).toHaveProperty('get');
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import labels from '../labels';
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
describe('#LabelsAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
expect(labels).toBeInstanceOf(ApiClient);
|
||||
expect(labels).toHaveProperty('get');
|
||||
expect(labels).toHaveProperty('show');
|
||||
expect(labels).toHaveProperty('create');
|
||||
expect(labels).toHaveProperty('update');
|
||||
expect(labels).toHaveProperty('delete');
|
||||
expect(labels.url).toBe('/api/v1/labels');
|
||||
});
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
@@ -1,6 +1,6 @@
|
||||
.button {
|
||||
font-weight: $font-weight-medium;
|
||||
font-family: $body-font-family;
|
||||
font-weight: $font-weight-medium;
|
||||
|
||||
&.round {
|
||||
border-radius: 1000px;
|
||||
@@ -20,10 +20,11 @@
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
max-width: 15rem;
|
||||
padding: $space-smaller $space-small;
|
||||
border-radius: $space-smaller;
|
||||
font-size: $font-size-mini;
|
||||
max-width: 15rem;
|
||||
padding: $space-smaller $space-small;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
code {
|
||||
@@ -36,3 +37,9 @@ code {
|
||||
padding: $space-two;
|
||||
}
|
||||
}
|
||||
|
||||
.text-truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
"Helvetica Neue",
|
||||
Tahoma,
|
||||
Arial,
|
||||
sans-serif;
|
||||
$body-antialiased: true;
|
||||
@@ -346,7 +347,7 @@ $helptext-color: $header-color;
|
||||
$helptext-font-size: $font-size-small;
|
||||
$helptext-font-style: italic;
|
||||
$input-prefix-color: $header-color;
|
||||
$input-prefix-background: $light-gray;
|
||||
$input-prefix-background: var(--b-100);
|
||||
$input-prefix-border: 1px solid $color-border;
|
||||
$input-prefix-padding: 1rem;
|
||||
$form-label-color: $header-color;
|
||||
@@ -382,7 +383,7 @@ $label-color: $primary-color;
|
||||
$label-color-alt: $black;
|
||||
$label-palette: $foundation-palette;
|
||||
$label-font-size: $font-size-micro;
|
||||
$label-padding: $space-micro $space-smaller;
|
||||
$label-padding: $space-smaller $space-small;
|
||||
$label-radius: $space-micro;
|
||||
|
||||
// 21. Media Object
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user