Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
89dafa366e | ||
|
|
dafabac796 | ||
|
|
93d8a25877 | ||
|
|
f78df91dd2 | ||
|
|
8c52a3a953 | ||
|
|
d4783db6fd | ||
|
|
586c616ae3 | ||
|
|
884a1c5971 | ||
|
|
47ec7ad7c9 | ||
|
|
ec197b077d | ||
|
|
10a1758261 | ||
|
|
a9d93b750c | ||
|
|
efc59bb43f | ||
|
|
b7a583b2c4 | ||
|
|
b1aab228ae | ||
|
|
d8d14fc4a4 | ||
|
|
e49a200ee0 | ||
|
|
8ccc00b5ba | ||
|
|
6230f4d796 | ||
|
|
11b4b4ea3f | ||
|
|
00093fa408 | ||
|
|
f6a9f791cf | ||
|
|
8af200ad20 | ||
|
|
3f5ce2ddbf | ||
|
|
6e92d9be9e | ||
|
|
b7129785c6 | ||
|
|
246d0b407f | ||
|
|
59f4dd7ebe | ||
|
|
2e8cd59a06 | ||
|
|
467fcda6fc | ||
|
|
e6aa851987 | ||
|
|
20f39caa42 | ||
|
|
1108446974 | ||
|
|
e6bf81caf4 | ||
|
|
005f332776 | ||
|
|
d219b0a45e | ||
|
|
f819bc0f33 | ||
|
|
c74b5c21d7 | ||
|
|
8859880e55 | ||
|
|
6e0f02a38b | ||
|
|
0f8689b701 | ||
|
|
70341df9b8 | ||
|
|
905c93b8f8 | ||
|
|
76b98cbed4 | ||
|
|
dedbe08a1c | ||
|
|
a5cc3bae6c | ||
|
|
0d7c1e5e11 | ||
|
|
35b2bdd074 | ||
|
|
1a123a4f56 | ||
|
|
a75f49ddcc | ||
|
|
f28ec29b8c | ||
|
|
05ea6308f2 | ||
|
|
628e0c8423 | ||
|
|
217f33b2b2 | ||
|
|
c7120e9637 | ||
|
|
d6d15c8c3c | ||
|
|
b50aeb7ae4 | ||
|
|
e9131ea558 | ||
|
|
5bd7a4c511 | ||
|
|
44fae4a4a1 | ||
|
|
5bc8219db5 | ||
|
|
fabc3170b7 | ||
|
|
98af47aae2 | ||
|
|
ae3f8c02e1 | ||
|
|
19886b40bd | ||
|
|
dc6398ab56 | ||
|
|
ba1e0dbda0 | ||
|
|
db6e3fb43e | ||
|
|
d8131e450c | ||
|
|
8655466fa5 | ||
|
|
7e62000e1b | ||
|
|
ddbc612f0b | ||
|
|
a951fb20cb | ||
|
|
8906fd808d | ||
|
|
8f0c4281c7 | ||
|
|
d4ff9f5b45 | ||
|
|
96da27f1f6 | ||
|
|
66aace7c13 | ||
|
|
29ae232e54 | ||
|
|
1ebe9fdba9 | ||
|
|
0690d3191d | ||
|
|
0b65526b85 | ||
|
|
0cb7333977 | ||
|
|
168042f9a4 | ||
|
|
fde4f9271b | ||
|
|
6770126de0 | ||
|
|
94192e8184 | ||
|
|
3fcd367952 | ||
|
|
7d41b7a5dc | ||
|
|
645d53db1c | ||
|
|
dba6866b6d | ||
|
|
b98a64d2dd | ||
|
|
06153a96ed | ||
|
|
5736f687e8 | ||
|
|
941fbb0d72 | ||
|
|
c0ce70e87b | ||
|
|
ecccb103a0 | ||
|
|
818c769bb7 | ||
|
|
0817414957 | ||
|
|
e3ccfc4662 | ||
|
|
2aa92d46fc | ||
|
|
c2167fcaaf | ||
|
|
743bddc065 | ||
|
|
55892e37f9 | ||
|
|
9db8adab5f | ||
|
|
991585264e | ||
|
|
b0950d6880 | ||
|
|
48f603798b | ||
|
|
1cfa756d49 | ||
|
|
4feca1d88d | ||
|
|
e04b37dfcf | ||
|
|
99eaf59509 | ||
|
|
a1a81e3799 | ||
|
|
8e59564793 | ||
|
|
cb22b396eb | ||
|
|
7fcd2d0e85 | ||
|
|
0afa5c297f | ||
|
|
6c4e1fdaac | ||
|
|
e56132c506 | ||
|
|
bab9d663d2 | ||
|
|
6c48f2e789 | ||
|
|
50ebbc13b6 | ||
|
|
a3c2d4e5bd | ||
|
|
209e0a0fb4 | ||
|
|
2eac97c705 | ||
|
|
bf80a37cfb | ||
|
|
a94f14ca22 | ||
|
|
f7e5f1fabf | ||
|
|
fe70843fae | ||
|
|
29bb2bff3a | ||
|
|
4c40d738a5 | ||
|
|
1c4566df66 | ||
|
|
8f6f07177d | ||
|
|
f69eb7e542 | ||
|
|
71d48854b1 | ||
|
|
4d0f46176b | ||
|
|
d8599c62dd | ||
|
|
bb47b5f086 | ||
|
|
fb17d34b8a | ||
|
|
260e40831a | ||
|
|
a5b1e2b650 | ||
|
|
19ab0fe108 | ||
|
|
2a6670f0da | ||
|
|
1db35caa56 | ||
|
|
60dc564f37 | ||
|
|
8b4df986bf | ||
|
|
b2d5cc7b05 |
+17
-15
@@ -7,7 +7,7 @@ defaults: &defaults
|
||||
working_directory: ~/build
|
||||
docker:
|
||||
# specify the version you desire here
|
||||
- image: circleci/ruby:2.6.5-node-browsers
|
||||
- image: circleci/ruby:2.7.1-node-browsers
|
||||
|
||||
# Specify service dependencies here if necessary
|
||||
# CircleCI maintains a library of pre-built images
|
||||
@@ -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
|
||||
|
||||
+5
-1
@@ -1,7 +1,7 @@
|
||||
version: "2"
|
||||
plugins:
|
||||
rubocop:
|
||||
enabled: true
|
||||
enabled: false
|
||||
channel: rubocop-0-73
|
||||
eslint:
|
||||
enabled: false
|
||||
@@ -26,3 +26,7 @@ exclude_patterns:
|
||||
- "node_modules/**/*"
|
||||
- "lib/tasks/auto_annotate_models.rake"
|
||||
- "app/test-matchers.js"
|
||||
- "docs/*"
|
||||
- "**/*.md"
|
||||
- "**/*.yml"
|
||||
- "app/javascript/dashboard/i18n/locale"
|
||||
|
||||
+71
-33
@@ -1,14 +1,19 @@
|
||||
SECRET_KEY_BASE=
|
||||
# Used to verify the integrity of signed cookies. so ensure a secure value is set
|
||||
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
|
||||
|
||||
# Force all access to the app over SSL, default is set to false
|
||||
FORCE_SSL=
|
||||
FORCE_SSL=false
|
||||
|
||||
# This lets you control new sign ups on your chatwoot installation
|
||||
# true : default option, allows sign ups
|
||||
# false : disables all the end points related to sign ups
|
||||
# api_only: disables the UI for signup, but you can create sign ups via the account apis
|
||||
ENABLE_ACCOUNT_SIGNUP=
|
||||
ENABLE_ACCOUNT_SIGNUP=true
|
||||
|
||||
#redis config
|
||||
# Redis config
|
||||
REDIS_URL=redis://redis:6379
|
||||
# If you are using docker-compose, set this variable's value to be any string,
|
||||
# which will be the password for the redis service running inside the docker-compose
|
||||
@@ -22,18 +27,7 @@ POSTGRES_PASSWORD=
|
||||
RAILS_ENV=development
|
||||
RAILS_MAX_THREADS=5
|
||||
|
||||
#fb app
|
||||
FB_VERIFY_TOKEN=
|
||||
FB_APP_SECRET=
|
||||
FB_APP_ID=
|
||||
|
||||
#twitter app
|
||||
TWITTER_APP_ID=
|
||||
TWITTER_CONSUMER_KEY=
|
||||
TWITTER_CONSUMER_SECRET=
|
||||
TWITTER_ENVIRONMENT=
|
||||
|
||||
#mail
|
||||
# Mail outgoing
|
||||
MAILER_SENDER_EMAIL=accounts@chatwoot.com
|
||||
SMTP_PORT=1025
|
||||
SMTP_DOMAIN=chatwoot.com
|
||||
@@ -45,32 +39,76 @@ SMTP_PASSWORD=
|
||||
SMTP_AUTHENTICATION=
|
||||
SMTP_ENABLE_STARTTLS_AUTO=
|
||||
|
||||
#misc
|
||||
FRONTEND_URL=http://0.0.0.0:3000
|
||||
# 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
|
||||
# :mailgun for Mailgun
|
||||
# :mandrill for Mandrill
|
||||
# :postmark for Postmark
|
||||
# :sendgrid for Sendgrid
|
||||
RAILS_INBOUND_EMAIL_SERVICE=
|
||||
# Use one of the following based on the email ingress service
|
||||
# Ref: https://edgeguides.rubyonrails.org/action_mailbox_basics.html
|
||||
RAILS_INBOUND_EMAIL_PASSWORD=
|
||||
MAILGUN_INGRESS_SIGNING_KEY=
|
||||
MANDRILL_INGRESS_API_KEY=
|
||||
|
||||
# Storage
|
||||
ACTIVE_STORAGE_SERVICE=local
|
||||
|
||||
#s3
|
||||
# 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=
|
||||
AWS_REGION=
|
||||
|
||||
#sentry
|
||||
# Sentry
|
||||
SENTRY_DSN=
|
||||
|
||||
#Log settings
|
||||
LOG_LEVEL=
|
||||
LOG_SIZE=
|
||||
# Log settings
|
||||
# Disable if you want to write logs to a file
|
||||
RAILS_LOG_TO_STDOUT=true
|
||||
LOG_LEVEL=info
|
||||
LOG_SIZE=500
|
||||
|
||||
# Credentials to access sidekiq dashboard in production
|
||||
SIDEKIQ_AUTH_USERNAME=
|
||||
SIDEKIQ_AUTH_PASSWORD=
|
||||
### This environment variables are only required if you are setting up social media channels
|
||||
|
||||
#### This environment variables are only required in hosted version which has billing
|
||||
ENABLE_BILLING=
|
||||
# Facebook
|
||||
# documentation: https://www.chatwoot.com/docs/facebook-setup
|
||||
FB_VERIFY_TOKEN=
|
||||
FB_APP_SECRET=
|
||||
FB_APP_ID=
|
||||
|
||||
## chargebee settings
|
||||
CHARGEBEE_API_KEY=
|
||||
CHARGEBEE_SITE=
|
||||
CHARGEBEE_WEBHOOK_USERNAME=
|
||||
CHARGEBEE_WEBHOOK_PASSWORD=
|
||||
# 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
|
||||
|
||||
## 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
|
||||
|
||||
## Development Only Config
|
||||
# if you want to use letter_opener for local emails
|
||||
# LETTER_OPENER=true
|
||||
|
||||
+4
-3
@@ -1,8 +1,8 @@
|
||||
module.exports = {
|
||||
extends: ['airbnb/base', 'prettier', 'plugin:vue/recommended'],
|
||||
extends: ['airbnb-base/legacy', 'prettier', 'plugin:vue/recommended'],
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
ecmaVersion: 2017,
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['html', 'prettier', 'babel'],
|
||||
@@ -28,7 +28,7 @@ module.exports = {
|
||||
}],
|
||||
'vue/html-self-closing': 'off',
|
||||
"vue/no-v-html": 'off',
|
||||
'import/extensions': ['never']
|
||||
'import/extensions': ['off']
|
||||
|
||||
},
|
||||
settings: {
|
||||
@@ -46,5 +46,6 @@ module.exports = {
|
||||
},
|
||||
globals: {
|
||||
__WEBPACK_ENV__: true,
|
||||
bus: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
open_collective: chatwoot
|
||||
github: chatwoot
|
||||
|
||||
@@ -55,3 +55,7 @@ node_modules
|
||||
package-lock.json
|
||||
|
||||
*.dump
|
||||
|
||||
|
||||
# cypress
|
||||
test/cypress/videos/*
|
||||
+87
-10
@@ -4,22 +4,54 @@ require:
|
||||
- rubocop-rspec
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
Lint/RaiseException:
|
||||
Enabled: true
|
||||
Lint/StructNewOverride:
|
||||
Enabled: true
|
||||
Lint/DeprecatedOpenSSLConstant:
|
||||
Enabled: true
|
||||
Lint/MixedRegexpCaptureTypes:
|
||||
Enabled: true
|
||||
Layout/LineLength:
|
||||
Max: 150
|
||||
Layout/EmptyLinesAroundAttributeAccessor:
|
||||
Enabled: true
|
||||
Layout/SpaceAroundMethodCallOperator:
|
||||
Enabled: true
|
||||
|
||||
Metrics/ClassLength:
|
||||
Max: 125
|
||||
Exclude:
|
||||
- 'app/models/conversation.rb'
|
||||
RSpec/ExampleLength:
|
||||
Max: 15
|
||||
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/RedundantFetchBlock:
|
||||
Enabled: true
|
||||
Style/RedundantRegexpCharacterClass:
|
||||
Enabled: true
|
||||
Style/RedundantRegexpEscape:
|
||||
Enabled: true
|
||||
Style/SlicingWithRange:
|
||||
Enabled: true
|
||||
Style/GlobalVars:
|
||||
Exclude:
|
||||
- 'config/initializers/redis.rb'
|
||||
- 'lib/redis/alfred.rb'
|
||||
- 'lib/global_config.rb'
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
- spec/**/*
|
||||
@@ -41,14 +73,59 @@ RSpec/NestedGroups:
|
||||
Max: 4
|
||||
RSpec/MessageSpies:
|
||||
Enabled: false
|
||||
Metrics/MethodLength:
|
||||
Exclude:
|
||||
- 'db/migrate/20161123131628_devise_token_auth_create_users.rb'
|
||||
Rails/CreateTableWithTimestamps:
|
||||
Exclude:
|
||||
- 'db/migrate/20170207092002_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb'
|
||||
Style/GuardClause:
|
||||
Exclude:
|
||||
- 'app/builders/account_builder.rb'
|
||||
- 'app/models/attachment.rb'
|
||||
- 'app/models/message.rb'
|
||||
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
||||
Metrics/AbcSize:
|
||||
Exclude:
|
||||
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
||||
Metrics/CyclomaticComplexity:
|
||||
Exclude:
|
||||
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
||||
Rails/ReversibleMigration:
|
||||
Exclude:
|
||||
- 'db/migrate/20161025070152_removechannelsfrommodels.rb'
|
||||
- 'db/migrate/20161025070645_remchannel.rb'
|
||||
- 'db/migrate/20161025070645_remchannel.rb'
|
||||
- 'db/migrate/20161110102609_removeinboxid.rb'
|
||||
- 'db/migrate/20170519091539_add_avatar_to_fb.rb'
|
||||
- 'db/migrate/20191020085608_rename_old_tables.rb'
|
||||
- 'db/migrate/20191126185833_update_user_invite_foreign_key.rb'
|
||||
- 'db/migrate/20191130164019_add_template_type_to_messages.rb'
|
||||
Rails/BulkChangeTable:
|
||||
Exclude:
|
||||
- 'db/migrate/20161025070152_removechannelsfrommodels.rb'
|
||||
- 'db/migrate/20200121190901_create_account_users.rb'
|
||||
- 'db/migrate/20170211092540_notnullableusers.rb'
|
||||
- 'db/migrate/20170403095203_contactadder.rb'
|
||||
- 'db/migrate/20170406104018_add_default_status_conv.rb'
|
||||
- 'db/migrate/20170511134418_latlong.rb'
|
||||
- 'db/migrate/20191027054756_create_contact_inboxes.rb'
|
||||
- 'db/migrate/20191130164019_add_template_type_to_messages.rb'
|
||||
Rails/UniqueValidationWithoutIndex:
|
||||
Exclude:
|
||||
- 'app/models/channel/twitter_profile.rb'
|
||||
- 'app/models/webhook.rb'
|
||||
RSpec/NamedSubject:
|
||||
Enabled: false
|
||||
AllCops:
|
||||
Exclude:
|
||||
- db/*
|
||||
- bin/**/*
|
||||
- db/**/*
|
||||
- config/**/*
|
||||
- public/**/*
|
||||
- vendor/**/*
|
||||
- node_modules/**/*
|
||||
- lib/tasks/auto_annotate_models.rake
|
||||
- config/environments/**/*
|
||||
- 'bin/**/*'
|
||||
- 'db/schema.rb'
|
||||
- 'public/**/*'
|
||||
- 'config/initializers/bot.rb'
|
||||
- 'vendor/**/*'
|
||||
- 'node_modules/**/*'
|
||||
- 'lib/tasks/auto_annotate_models.rake'
|
||||
- 'config/environments/**/*'
|
||||
- 'tmp/**/*'
|
||||
- 'storage/**/*'
|
||||
|
||||
@@ -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,33 +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: 7
|
||||
# Configuration parameters: MinBodyLength.
|
||||
Style/GuardClause:
|
||||
Exclude:
|
||||
- 'app/builders/account_builder.rb'
|
||||
- 'app/models/attachment.rb'
|
||||
- 'app/models/message.rb'
|
||||
- 'lib/webhooks/chargebee.rb'
|
||||
|
||||
# Offense count: 4
|
||||
Style/IdenticalConditionalBranches:
|
||||
Exclude:
|
||||
- 'app/controllers/api/v1/reports_controller.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: AllowIfModifier.
|
||||
Style/IfInsideElse:
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.6.5
|
||||
2.7.1
|
||||
|
||||
+7
-3
@@ -51,7 +51,7 @@ linters:
|
||||
|
||||
ElsePlacement:
|
||||
enabled: true
|
||||
style: same_line # or 'new_line'
|
||||
style: new_line
|
||||
|
||||
EmptyLineBetweenBlocks:
|
||||
enabled: true
|
||||
@@ -82,7 +82,7 @@ linters:
|
||||
enabled: true
|
||||
|
||||
ImportantRule:
|
||||
enabled: true
|
||||
enabled: false
|
||||
|
||||
ImportPath:
|
||||
enabled: true
|
||||
@@ -177,6 +177,8 @@ linters:
|
||||
allow_element_with_attribute: false
|
||||
allow_element_with_class: false
|
||||
allow_element_with_id: false
|
||||
exclude:
|
||||
- 'app/assets/stylesheets/administrate/components/_buttons.scss'
|
||||
|
||||
SelectorDepth:
|
||||
enabled: true
|
||||
@@ -252,7 +254,7 @@ linters:
|
||||
enabled: false
|
||||
|
||||
UnnecessaryParentReference:
|
||||
enabled: true
|
||||
enabled: false
|
||||
|
||||
UrlFormat:
|
||||
enabled: true
|
||||
@@ -279,3 +281,5 @@ linters:
|
||||
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'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '2.6.5'
|
||||
ruby '2.7.1'
|
||||
|
||||
##-- base gems for rails --##
|
||||
gem 'rack-cors', require: 'rack/cors'
|
||||
@@ -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'
|
||||
@@ -17,19 +17,27 @@ gem 'jbuilder'
|
||||
gem 'kaminari'
|
||||
gem 'responders'
|
||||
gem 'rest-client'
|
||||
gem 'telephone_number'
|
||||
gem 'time_diff'
|
||||
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
|
||||
gem 'azure-storage', require: false
|
||||
gem 'azure-storage-blob', require: false
|
||||
gem 'google-cloud-storage', require: false
|
||||
gem 'mini_magick'
|
||||
|
||||
##-- gems for database --#
|
||||
gem 'groupdate'
|
||||
gem 'pg'
|
||||
gem 'redis'
|
||||
gem 'redis-namespace'
|
||||
@@ -47,27 +55,24 @@ gem 'devise_token_auth'
|
||||
# authorization
|
||||
gem 'jwt'
|
||||
gem 'pundit'
|
||||
# super admin
|
||||
gem 'administrate'
|
||||
|
||||
##--- gems for pubsub service ---##
|
||||
# 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 reporting ---##
|
||||
gem 'nightfury'
|
||||
|
||||
##--- gems for billing ---##
|
||||
gem 'chargebee'
|
||||
|
||||
##--- gems for channels ---##
|
||||
gem 'facebook-messenger'
|
||||
gem 'telegram-bot-ruby'
|
||||
gem 'twilio-ruby', '~> 5.32.0'
|
||||
# twitty will handle subscription of twitter account events
|
||||
gem 'twitty', git: 'https://github.com/chatwoot/twitty'
|
||||
|
||||
# gem 'twitty', git: 'https://github.com/chatwoot/twitty'
|
||||
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
|
||||
@@ -78,8 +83,9 @@ 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
|
||||
gem 'annotate'
|
||||
@@ -91,6 +97,13 @@ group :development do
|
||||
gem 'json_refs', git: 'https://github.com/tzmfreedom/json_refs', ref: 'e32deb0'
|
||||
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
|
||||
# locking until https://github.com/codeclimate/test-reporter/issues/418 is resolved
|
||||
gem 'action-cable-testing'
|
||||
@@ -99,7 +112,7 @@ group :development, :test do
|
||||
gem 'factory_bot_rails'
|
||||
gem 'faker'
|
||||
gem 'listen'
|
||||
gem 'mock_redis'
|
||||
gem 'mock_redis', git: 'https://github.com/sds/mock_redis', ref: '16d00789f0341a3aac35126c0ffe97a596753ff9'
|
||||
gem 'pry-rails'
|
||||
gem 'rspec-rails', '~> 4.0.0.beta2'
|
||||
gem 'rubocop', require: false
|
||||
@@ -112,4 +125,5 @@ group :development, :test do
|
||||
gem 'simplecov', '0.17.1', require: false
|
||||
gem 'spring'
|
||||
gem 'spring-watcher-listen'
|
||||
gem 'webmock'
|
||||
end
|
||||
|
||||
+274
-198
@@ -1,9 +1,9 @@
|
||||
GIT
|
||||
remote: https://github.com/chatwoot/twitty
|
||||
revision: c1edd557401d1e8a197b19e738f82e39507a8e2d
|
||||
remote: https://github.com/sds/mock_redis
|
||||
revision: 16d00789f0341a3aac35126c0ffe97a596753ff9
|
||||
ref: 16d00789f0341a3aac35126c0ffe97a596753ff9
|
||||
specs:
|
||||
twitty (0.1.0)
|
||||
oauth
|
||||
mock_redis (0.22.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/tzmfreedom/json_refs
|
||||
@@ -16,142 +16,158 @@ GIT
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
action-cable-testing (0.6.0)
|
||||
action-cable-testing (0.6.1)
|
||||
actioncable (>= 5.0)
|
||||
actioncable (6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
actioncable (6.0.3.2)
|
||||
actionpack (= 6.0.3.2)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
activejob (= 6.0.2.1)
|
||||
activerecord (= 6.0.2.1)
|
||||
activestorage (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
actionmailbox (6.0.3.2)
|
||||
actionpack (= 6.0.3.2)
|
||||
activejob (= 6.0.3.2)
|
||||
activerecord (= 6.0.3.2)
|
||||
activestorage (= 6.0.3.2)
|
||||
activesupport (= 6.0.3.2)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
actionview (= 6.0.2.1)
|
||||
activejob (= 6.0.2.1)
|
||||
actionmailer (6.0.3.2)
|
||||
actionpack (= 6.0.3.2)
|
||||
actionview (= 6.0.3.2)
|
||||
activejob (= 6.0.3.2)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.0.2.1)
|
||||
actionview (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
actionpack (6.0.3.2)
|
||||
actionview (= 6.0.3.2)
|
||||
activesupport (= 6.0.3.2)
|
||||
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.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
activerecord (= 6.0.2.1)
|
||||
activestorage (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
actiontext (6.0.3.2)
|
||||
actionpack (= 6.0.3.2)
|
||||
activerecord (= 6.0.3.2)
|
||||
activestorage (= 6.0.3.2)
|
||||
activesupport (= 6.0.3.2)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
actionview (6.0.3.2)
|
||||
activesupport (= 6.0.3.2)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
activejob (6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
activejob (6.0.3.2)
|
||||
activesupport (= 6.0.3.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
activerecord (6.0.2.1)
|
||||
activemodel (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
activestorage (6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
activejob (= 6.0.2.1)
|
||||
activerecord (= 6.0.2.1)
|
||||
activemodel (6.0.3.2)
|
||||
activesupport (= 6.0.3.2)
|
||||
activerecord (6.0.3.2)
|
||||
activemodel (= 6.0.3.2)
|
||||
activesupport (= 6.0.3.2)
|
||||
activestorage (6.0.3.2)
|
||||
actionpack (= 6.0.3.2)
|
||||
activejob (= 6.0.3.2)
|
||||
activerecord (= 6.0.3.2)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (6.0.2.1)
|
||||
activesupport (6.0.3.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2)
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
acts-as-taggable-on (6.5.0)
|
||||
activerecord (>= 5.0, < 6.1)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
annotate (3.0.3)
|
||||
administrate (0.13.0)
|
||||
actionpack (>= 4.2)
|
||||
actionview (>= 4.2)
|
||||
activerecord (>= 4.2)
|
||||
autoprefixer-rails (>= 6.0)
|
||||
datetime_picker_rails (~> 0.0.7)
|
||||
jquery-rails (>= 4.0)
|
||||
kaminari (>= 1.0)
|
||||
momentjs-rails (~> 2.8)
|
||||
sassc-rails (~> 2.1)
|
||||
selectize-rails (~> 0.6)
|
||||
annotate (3.1.1)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.0)
|
||||
attr_extras (6.2.3)
|
||||
aws-eventstream (1.0.3)
|
||||
aws-partitions (1.269.0)
|
||||
aws-sdk-core (3.89.1)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
ast (2.4.1)
|
||||
attr_extras (6.2.4)
|
||||
autoprefixer-rails (9.8.2)
|
||||
execjs
|
||||
aws-eventstream (1.1.0)
|
||||
aws-partitions (1.332.0)
|
||||
aws-sdk-core (3.100.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.28.0)
|
||||
aws-sdk-core (~> 3, >= 3.71.0)
|
||||
aws-sdk-kms (1.34.1)
|
||||
aws-sdk-core (~> 3, >= 3.99.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.60.1)
|
||||
aws-sdk-core (~> 3, >= 3.83.0)
|
||||
aws-sdk-s3 (1.69.1)
|
||||
aws-sdk-core (~> 3, >= 3.99.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.1.0)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
aws-sigv4 (1.2.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
axiom-types (0.1.1)
|
||||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
azure-core (0.1.15)
|
||||
faraday (~> 0.9)
|
||||
faraday_middleware (~> 0.10)
|
||||
nokogiri (~> 1.6)
|
||||
azure-storage (0.15.0.preview)
|
||||
azure-core (~> 0.1)
|
||||
faraday (~> 0.9)
|
||||
faraday_middleware (~> 0.10)
|
||||
nokogiri (~> 1.6, >= 1.6.8)
|
||||
azure-storage-blob (2.0.0)
|
||||
azure-storage-common (~> 2.0)
|
||||
nokogiri (~> 1.10.4)
|
||||
azure-storage-common (2.0.1)
|
||||
faraday (~> 1.0)
|
||||
faraday_middleware (~> 1.0.0.rc1)
|
||||
nokogiri (~> 1.10.4)
|
||||
bcrypt (3.1.13)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.4.5)
|
||||
bootsnap (1.4.6)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (4.7.2)
|
||||
browser (3.0.3)
|
||||
brakeman (4.8.2)
|
||||
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)
|
||||
byebug (11.1.1)
|
||||
chargebee (2.7.3)
|
||||
json_pure (~> 2.1)
|
||||
rest-client (>= 1.8, < 3.0)
|
||||
coderay (1.1.2)
|
||||
thor (>= 0.18, < 2)
|
||||
byebug (11.1.3)
|
||||
coderay (1.1.3)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
concurrent-ruby (1.1.5)
|
||||
connection_pool (2.2.2)
|
||||
concurrent-ruby (1.1.6)
|
||||
connection_pool (2.2.3)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.6)
|
||||
cypress-on-rails (1.7.0)
|
||||
rack
|
||||
database_cleaner (1.8.5)
|
||||
datetime_picker_rails (0.0.7)
|
||||
momentjs-rails (>= 2.8.1)
|
||||
declarative (0.0.10)
|
||||
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.4.1)
|
||||
sprockets (= 3.7.2)
|
||||
diff-lcs (1.4)
|
||||
digest-crc (0.5.1)
|
||||
docile (1.3.2)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
@@ -162,26 +178,29 @@ GEM
|
||||
equalizer (0.0.11)
|
||||
erubi (1.9.0)
|
||||
execjs (2.7.0)
|
||||
facebook-messenger (1.4.1)
|
||||
facebook-messenger (1.5.0)
|
||||
httparty (~> 0.13, >= 0.13.7)
|
||||
rack (>= 1.4.5)
|
||||
factory_bot (5.1.1)
|
||||
activesupport (>= 4.2.0)
|
||||
factory_bot_rails (5.1.1)
|
||||
factory_bot (~> 5.1.0)
|
||||
railties (>= 4.2.0)
|
||||
faker (2.10.1)
|
||||
factory_bot (6.0.2)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.0.0)
|
||||
factory_bot (~> 6.0.0)
|
||||
railties (>= 5.0.0)
|
||||
faker (2.12.0)
|
||||
i18n (>= 1.6, < 2)
|
||||
faraday (0.17.3)
|
||||
faraday (1.0.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday_middleware (0.14.0)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
ffi (1.12.2)
|
||||
faraday_middleware (1.0.0)
|
||||
faraday (~> 1.0)
|
||||
fcm (1.0.1)
|
||||
faraday (~> 1.0.0)
|
||||
ffi (1.13.1)
|
||||
flag_shih_tzu (0.3.23)
|
||||
foreman (0.87.0)
|
||||
foreman (0.87.1)
|
||||
gli (2.19.1)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
google-api-client (0.36.4)
|
||||
google-api-client (0.41.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
@@ -192,68 +211,75 @@ GEM
|
||||
google-cloud-core (1.5.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.3.0)
|
||||
faraday (~> 0.11)
|
||||
google-cloud-errors (1.0.0)
|
||||
google-cloud-storage (1.25.1)
|
||||
google-cloud-env (1.3.2)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.0.1)
|
||||
google-cloud-storage (1.26.2)
|
||||
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.10.0)
|
||||
faraday (~> 0.12)
|
||||
googleauth (0.13.0)
|
||||
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.12)
|
||||
signet (~> 0.14)
|
||||
groupdate (5.0.0)
|
||||
activesupport (>= 5)
|
||||
haikunator (1.1.0)
|
||||
hana (1.3.5)
|
||||
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.17.3)
|
||||
httparty (0.18.1)
|
||||
mime-types (~> 3.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.2)
|
||||
i18n (1.8.3)
|
||||
concurrent-ruby (~> 1.0)
|
||||
ice_nine (0.11.2)
|
||||
inflecto (0.0.2)
|
||||
jaro_winkler (1.5.4)
|
||||
jbuilder (2.9.1)
|
||||
activesupport (>= 4.2.0)
|
||||
jbuilder (2.10.0)
|
||||
activesupport (>= 5.0.0)
|
||||
jmespath (1.4.0)
|
||||
jquery-rails (4.4.0)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.3.0)
|
||||
json_pure (2.2.0)
|
||||
jwt (2.2.1)
|
||||
kaminari (1.2.0)
|
||||
kaminari (1.2.1)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.0)
|
||||
kaminari-activerecord (= 1.2.0)
|
||||
kaminari-core (= 1.2.0)
|
||||
kaminari-actionview (1.2.0)
|
||||
kaminari-actionview (= 1.2.1)
|
||||
kaminari-activerecord (= 1.2.1)
|
||||
kaminari-core (= 1.2.1)
|
||||
kaminari-actionview (1.2.1)
|
||||
actionview
|
||||
kaminari-core (= 1.2.0)
|
||||
kaminari-activerecord (1.2.0)
|
||||
kaminari-core (= 1.2.1)
|
||||
kaminari-activerecord (1.2.1)
|
||||
activerecord
|
||||
kaminari-core (= 1.2.0)
|
||||
kaminari-core (1.2.0)
|
||||
kaminari-core (= 1.2.1)
|
||||
kaminari-core (1.2.1)
|
||||
koala (3.0.0)
|
||||
addressable
|
||||
faraday
|
||||
json (>= 1.8)
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
launchy (2.5.0)
|
||||
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.4.0)
|
||||
loofah (2.6.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
@@ -261,44 +287,44 @@ GEM
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
memoist (0.16.2)
|
||||
method_source (0.9.2)
|
||||
method_source (1.0.0)
|
||||
mime-types (3.3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2019.1009)
|
||||
mimemagic (0.3.4)
|
||||
mime-types-data (3.2020.0512)
|
||||
mimemagic (0.3.5)
|
||||
mini_magick (4.10.1)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.14.0)
|
||||
mock_redis (0.22.0)
|
||||
msgpack (1.3.1)
|
||||
minitest (5.14.1)
|
||||
momentjs-rails (2.20.1)
|
||||
railties (>= 3.1)
|
||||
msgpack (1.3.3)
|
||||
multi_json (1.14.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.1.1)
|
||||
netrc (0.11.0)
|
||||
nightfury (1.0.1)
|
||||
nio4r (2.5.2)
|
||||
nokogiri (1.10.8)
|
||||
nokogiri (1.10.9)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
oauth (0.5.4)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.0.1)
|
||||
parallel (1.19.1)
|
||||
parser (2.7.0.2)
|
||||
ast (~> 2.4.0)
|
||||
pg (1.2.2)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
os (1.1.0)
|
||||
parallel (1.19.2)
|
||||
parser (2.7.1.4)
|
||||
ast (~> 2.4.1)
|
||||
pg (1.2.3)
|
||||
pry (0.13.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (4.0.3)
|
||||
puma (4.3.3)
|
||||
public_suffix (4.0.5)
|
||||
puma (4.3.5)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
rack (2.1.2)
|
||||
rack-cache (1.11.0)
|
||||
rack (2.2.3)
|
||||
rack-cache (1.12.0)
|
||||
rack (>= 0.4)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
@@ -308,50 +334,51 @@ GEM
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.0.2.1)
|
||||
actioncable (= 6.0.2.1)
|
||||
actionmailbox (= 6.0.2.1)
|
||||
actionmailer (= 6.0.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
actiontext (= 6.0.2.1)
|
||||
actionview (= 6.0.2.1)
|
||||
activejob (= 6.0.2.1)
|
||||
activemodel (= 6.0.2.1)
|
||||
activerecord (= 6.0.2.1)
|
||||
activestorage (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
rails (6.0.3.2)
|
||||
actioncable (= 6.0.3.2)
|
||||
actionmailbox (= 6.0.3.2)
|
||||
actionmailer (= 6.0.3.2)
|
||||
actionpack (= 6.0.3.2)
|
||||
actiontext (= 6.0.3.2)
|
||||
actionview (= 6.0.3.2)
|
||||
activejob (= 6.0.3.2)
|
||||
activemodel (= 6.0.3.2)
|
||||
activerecord (= 6.0.3.2)
|
||||
activestorage (= 6.0.3.2)
|
||||
activesupport (= 6.0.3.2)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 6.0.2.1)
|
||||
railties (= 6.0.3.2)
|
||||
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.2.1)
|
||||
actionpack (= 6.0.2.1)
|
||||
activesupport (= 6.0.2.1)
|
||||
railties (6.0.3.2)
|
||||
actionpack (= 6.0.3.2)
|
||||
activesupport (= 6.0.3.2)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
rake (13.0.1)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-fsevent (0.10.4)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
redis (4.1.3)
|
||||
redis (4.2.1)
|
||||
redis-namespace (1.7.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.1)
|
||||
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)
|
||||
@@ -360,15 +387,16 @@ GEM
|
||||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
retriable (3.1.2)
|
||||
rspec-core (3.9.1)
|
||||
rspec-support (~> 3.9.1)
|
||||
rspec-expectations (3.9.0)
|
||||
rexml (3.2.4)
|
||||
rspec-core (3.9.2)
|
||||
rspec-support (~> 3.9.3)
|
||||
rspec-expectations (3.9.2)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-mocks (3.9.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-rails (4.0.0.beta4)
|
||||
rspec-rails (4.0.1)
|
||||
actionpack (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
railties (>= 4.2)
|
||||
@@ -376,46 +404,62 @@ GEM
|
||||
rspec-expectations (~> 3.9)
|
||||
rspec-mocks (~> 3.9)
|
||||
rspec-support (~> 3.9)
|
||||
rspec-support (3.9.2)
|
||||
rubocop (0.79.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
rspec-support (3.9.3)
|
||||
rubocop (0.86.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.7.0.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.7)
|
||||
rexml
|
||||
rubocop-ast (>= 0.0.3, < 1.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
rubocop-performance (1.5.2)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-ast (0.0.3)
|
||||
parser (>= 2.7.0.1)
|
||||
rubocop-performance (1.6.1)
|
||||
rubocop (>= 0.71.0)
|
||||
rubocop-rails (2.4.2)
|
||||
rubocop-rails (2.6.0)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
rubocop-rspec (1.37.1)
|
||||
rubocop (>= 0.82.0)
|
||||
rubocop-rspec (1.40.0)
|
||||
rubocop (>= 0.68.1)
|
||||
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)
|
||||
scout_apm (2.6.6)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
sassc (>= 2.0)
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
scout_apm (2.6.7)
|
||||
parser
|
||||
scss_lint (0.59.0)
|
||||
sass (~> 3.5, >= 3.5.5)
|
||||
seed_dump (3.3.1)
|
||||
activerecord (>= 4)
|
||||
activesupport (>= 4)
|
||||
sentry-raven (2.13.0)
|
||||
faraday (>= 0.7.6, < 1.0)
|
||||
shoulda-matchers (4.2.0)
|
||||
selectize-rails (0.12.6)
|
||||
semantic_range (2.3.0)
|
||||
sentry-raven (3.0.0)
|
||||
faraday (>= 1.0)
|
||||
shoulda-matchers (4.3.0)
|
||||
activesupport (>= 4.2.0)
|
||||
sidekiq (6.0.4)
|
||||
sidekiq (6.0.7)
|
||||
connection_pool (>= 2.2.2)
|
||||
rack (>= 2.0.0)
|
||||
rack (~> 2.0)
|
||||
rack-protection (>= 2.0.0)
|
||||
redis (>= 4.1.0)
|
||||
signet (0.12.0)
|
||||
signet (0.14.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simplecov (0.17.1)
|
||||
@@ -423,11 +467,18 @@ GEM
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
slack-ruby-client (0.14.6)
|
||||
activesupport
|
||||
faraday (>= 0.9)
|
||||
faraday_middleware
|
||||
gli
|
||||
hashie
|
||||
websocket-driver
|
||||
spring (2.1.0)
|
||||
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)
|
||||
@@ -438,24 +489,32 @@ GEM
|
||||
faraday
|
||||
inflecto
|
||||
virtus
|
||||
thor (0.20.3)
|
||||
telephone_number (1.4.7)
|
||||
thor (1.0.1)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.10)
|
||||
time_diff (0.3.0)
|
||||
activesupport
|
||||
i18n
|
||||
tzinfo (1.2.6)
|
||||
twilio-ruby (5.32.0)
|
||||
faraday (~> 1.0.0)
|
||||
jwt (>= 1.5, <= 2.5)
|
||||
nokogiri (>= 1.6, < 2.0)
|
||||
twitty (0.1.1)
|
||||
oauth
|
||||
tzinfo (1.2.7)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2019.3)
|
||||
tzinfo-data (1.2020.1)
|
||||
tzinfo (>= 1.0.0)
|
||||
uber (0.1.0)
|
||||
uglifier (4.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.6)
|
||||
unicode-display_width (1.6.1)
|
||||
unf_ext (0.0.7.7)
|
||||
unicode-display_width (1.7.0)
|
||||
uniform_notifier (1.13.0)
|
||||
valid_email2 (3.1.3)
|
||||
valid_email2 (3.2.2)
|
||||
activemodel (>= 3.2)
|
||||
mail (~> 2.5)
|
||||
virtus (1.0.5)
|
||||
@@ -465,20 +524,28 @@ GEM
|
||||
equalizer (~> 0.0, >= 0.0.9)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
web-console (4.0.1)
|
||||
web-console (4.0.3)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webpacker (4.2.2)
|
||||
activesupport (>= 4.2)
|
||||
webmock (3.8.3)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webpacker (5.1.1)
|
||||
activesupport (>= 5.2)
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 4.2)
|
||||
websocket-driver (0.7.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
webpush (1.0.0)
|
||||
hkdf (~> 0.2)
|
||||
jwt (~> 2.0)
|
||||
websocket-driver (0.7.2)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.4)
|
||||
websocket-extensions (0.1.5)
|
||||
wisper (2.0.0)
|
||||
zeitwerk (2.2.2)
|
||||
zeitwerk (2.3.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@@ -486,26 +553,30 @@ PLATFORMS
|
||||
DEPENDENCIES
|
||||
action-cable-testing
|
||||
acts-as-taggable-on
|
||||
administrate
|
||||
annotate
|
||||
attr_extras
|
||||
aws-sdk-s3
|
||||
azure-storage
|
||||
azure-storage-blob
|
||||
bootsnap
|
||||
brakeman
|
||||
browser
|
||||
bullet
|
||||
bundle-audit
|
||||
byebug
|
||||
chargebee
|
||||
cypress-on-rails (~> 1.0)
|
||||
database_cleaner
|
||||
devise
|
||||
devise_token_auth
|
||||
dotenv-rails
|
||||
facebook-messenger
|
||||
factory_bot_rails
|
||||
faker
|
||||
fcm
|
||||
flag_shih_tzu
|
||||
foreman
|
||||
google-cloud-storage
|
||||
groupdate
|
||||
haikunator
|
||||
hashie
|
||||
jbuilder
|
||||
@@ -514,10 +585,10 @@ DEPENDENCIES
|
||||
kaminari
|
||||
koala
|
||||
letter_opener
|
||||
liquid
|
||||
listen
|
||||
mini_magick
|
||||
mock_redis
|
||||
nightfury
|
||||
mock_redis!
|
||||
pg
|
||||
pry-rails
|
||||
puma
|
||||
@@ -541,20 +612,25 @@ DEPENDENCIES
|
||||
shoulda-matchers
|
||||
sidekiq
|
||||
simplecov (= 0.17.1)
|
||||
slack-ruby-client
|
||||
spring
|
||||
spring-watcher-listen
|
||||
telegram-bot-ruby
|
||||
telephone_number
|
||||
time_diff
|
||||
twitty!
|
||||
twilio-ruby (~> 5.32.0)
|
||||
twitty
|
||||
tzinfo-data
|
||||
uglifier
|
||||
valid_email2
|
||||
web-console
|
||||
webmock
|
||||
webpacker
|
||||
webpush
|
||||
wisper (= 2.0.0)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.6.5p114
|
||||
ruby 2.7.1p83
|
||||
|
||||
BUNDLED WITH
|
||||
2.0.2
|
||||
2.1.4
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
release: bundle exec rails db:prepare
|
||||
web: bin/rails server -p $PORT -e $RAILS_ENV
|
||||
worker: bundle exec sidekiq -C config/sidekiq.yml
|
||||
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
|
||||
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="https://storage.googleapis.com/chatwoot-assets/woot-logo.svg" alt="Woot-logo" width="240">
|
||||
<img src="https://s3.us-west-2.amazonaws.com/gh-assets.chatwoot.com/brand.svg" alt="Woot-logo" width="240">
|
||||
|
||||
<div align="center">A simple and elegant live chat software</div>
|
||||
<div align="center">An opensource alternative to Intercom, Zendesk, Drift, Crisp etc.</div>
|
||||
@@ -19,17 +19,17 @@ ___
|
||||
<a href="https://hub.docker.com/r/chatwoot/chatwoot/"><img src="https://img.shields.io/docker/cloud/build/chatwoot/chatwoot" alt="Docker Build Badge"></a>
|
||||
<img src="https://img.shields.io/github/license/chatwoot/chatwoot" alt="License">
|
||||
<img src="https://img.shields.io/github/commit-activity/m/chatwoot/chatwoot" alt="Commits-per-month">
|
||||
<img src="https://img.shields.io/discord/647412545203994635" alt="Discord">
|
||||
<a href="https://discord.gg/cJXdrwS"><img src="https://img.shields.io/badge/chat-Discord-violet?logo=discord" alt="Chat on Discord"></a>
|
||||
<a title="Crowdin" target="_self" href="https://chatwoot.crowdin.com/chatwoot"><img src="https://badges.crowdin.net/e/37ced7eba411064bd792feb3b7a28b16/localized.svg"></a>
|
||||
<a href="https://discord.gg/cJXdrwS"><img src="https://img.shields.io/discord/647412545203994635" alt="Discord"></a>
|
||||
</p>
|
||||
|
||||

|
||||

|
||||
|
||||
## Background
|
||||
|
||||
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.
|
||||
Chatwoot is a customer support tool for instant messaging channels which can help businesses provide exceptional customer support. 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 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.
|
||||
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.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -39,12 +39,12 @@ You can find the quick setup docs [here](https://www.chatwoot.com/docs/quick-set
|
||||
|
||||
## Branching model
|
||||
|
||||
We use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. The base branch is `develop`.
|
||||
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.
|
||||
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)
|
||||
|
||||
@@ -54,7 +54,7 @@ Follow this [link](https://www.chatwoot.com/docs/environment-variables) to under
|
||||
|
||||
Follow our [docker development guide](https://www.chatwoot.com/docs/installation-guide-docker) to develop and debug the application using `docker-compose`.
|
||||
|
||||
Follow our [environment variables](https://www.chatwoot.com/docs/environment-variables/) guide to setup environment for Docker.
|
||||
Follow our [environment variables](https://www.chatwoot.com/docs/environment-variables/) guide to setup the environment for Docker.
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
class ContactIdentifyAction
|
||||
pattr_initialize [:contact!, :params!]
|
||||
|
||||
def perform
|
||||
ActiveRecord::Base.transaction do
|
||||
@contact = merge_contact(existing_identified_contact, @contact) if merge_contacts?(existing_identified_contact, @contact)
|
||||
@contact = merge_contact(existing_email_contact, @contact) if merge_contacts?(existing_email_contact, @contact)
|
||||
update_contact
|
||||
end
|
||||
@contact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def account
|
||||
@account ||= @contact.account
|
||||
end
|
||||
|
||||
def existing_identified_contact
|
||||
return if params[:identifier].blank?
|
||||
|
||||
@existing_identified_contact ||= Contact.where(account_id: account.id).find_by(identifier: params[:identifier])
|
||||
end
|
||||
|
||||
def existing_email_contact
|
||||
return if params[:email].blank?
|
||||
|
||||
@existing_email_contact ||= Contact.where(account_id: account.id).find_by(email: params[:email])
|
||||
end
|
||||
|
||||
def merge_contacts?(existing_contact, _contact)
|
||||
existing_contact && existing_contact.id != @contact.id
|
||||
end
|
||||
|
||||
def update_contact
|
||||
@contact.update!(params.slice(:name, :email, :identifier))
|
||||
ContactAvatarJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present?
|
||||
end
|
||||
|
||||
def merge_contact(base_contact, merge_contact)
|
||||
ContactMergeAction.new(
|
||||
account: account,
|
||||
base_contact: base_contact,
|
||||
mergee_contact: merge_contact
|
||||
).perform
|
||||
end
|
||||
end
|
||||
@@ -5,9 +5,11 @@ class ContactMergeAction
|
||||
ActiveRecord::Base.transaction do
|
||||
validate_contacts
|
||||
merge_conversations
|
||||
merge_messages
|
||||
merge_contact_inboxes
|
||||
remove_mergee_contact
|
||||
end
|
||||
@base_contact
|
||||
end
|
||||
|
||||
private
|
||||
@@ -15,7 +17,7 @@ class ContactMergeAction
|
||||
def validate_contacts
|
||||
return if belongs_to_account?(@base_contact) && belongs_to_account?(@mergee_contact)
|
||||
|
||||
raise Exception, 'contact does not belong to the account'
|
||||
raise StandardError, 'contact does not belong to the account'
|
||||
end
|
||||
|
||||
def belongs_to_account?(contact)
|
||||
@@ -26,6 +28,10 @@ class ContactMergeAction
|
||||
Conversation.where(contact_id: @mergee_contact.id).update(contact_id: @base_contact.id)
|
||||
end
|
||||
|
||||
def merge_messages
|
||||
Message.where(sender: @mergee_contact).update(sender: @base_contact)
|
||||
end
|
||||
|
||||
def merge_contact_inboxes
|
||||
ContactInbox.where(contact_id: @mergee_contact.id).update(contact_id: @base_contact.id)
|
||||
end
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
//= link_tree ../images
|
||||
//= link administrate/application.css
|
||||
//= link administrate/application.js
|
||||
//= link dashboardChart.js
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// eslint-disable-next-line
|
||||
function prepareData(data) {
|
||||
var labels = [];
|
||||
var dataSet = [];
|
||||
data.forEach(item => {
|
||||
labels.push(item[0]);
|
||||
dataSet.push(item[1]);
|
||||
});
|
||||
return { labels, dataSet };
|
||||
}
|
||||
|
||||
function getChartOptions() {
|
||||
var fontFamily =
|
||||
'Inter,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif';
|
||||
return {
|
||||
responsive: true,
|
||||
legend: { labels: { fontFamily } },
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
barPercentage: 1.26,
|
||||
ticks: { fontFamily },
|
||||
gridLines: { display: false },
|
||||
},
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
ticks: { fontFamily },
|
||||
gridLines: { display: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
function drawSuperAdminDashboard(data) {
|
||||
var ctx = document.getElementById('dashboard-chart').getContext('2d');
|
||||
var chartData = prepareData(data);
|
||||
// eslint-disable-next-line
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: chartData.labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Conversations',
|
||||
data: chartData.dataSet,
|
||||
backgroundColor: '#1f93ff',
|
||||
},
|
||||
],
|
||||
},
|
||||
options: getChartOptions(),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
@charset 'utf-8';
|
||||
|
||||
@import 'reset/normalize';
|
||||
|
||||
@import 'utilities/variables';
|
||||
@import 'utilities/text-color';
|
||||
|
||||
@import 'selectize';
|
||||
@import 'datetime_picker';
|
||||
|
||||
@import 'library/clearfix';
|
||||
@import 'library/data-label';
|
||||
@import 'library/variables';
|
||||
|
||||
@import 'base/forms';
|
||||
@import 'base/layout';
|
||||
@import 'base/lists';
|
||||
@import 'base/tables';
|
||||
@import 'base/typography';
|
||||
|
||||
@import 'components/app-container';
|
||||
@import 'components/attributes';
|
||||
@import 'components/buttons';
|
||||
@import 'components/cells';
|
||||
@import 'components/field-unit';
|
||||
@import 'components/flashes';
|
||||
@import 'components/form-actions';
|
||||
@import 'components/main-content';
|
||||
@import 'components/navigation';
|
||||
@import 'components/pagination';
|
||||
@import 'components/search';
|
||||
@import 'components/reports';
|
||||
@@ -0,0 +1,103 @@
|
||||
fieldset {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
font-weight: $font-weight-medium;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: $font-weight-medium;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
display: block;
|
||||
font-family: $base-font-family;
|
||||
font-size: $base-font-size;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
display: block;
|
||||
font-family: $base-font-family;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
[type="color"],
|
||||
[type="date"],
|
||||
[type="datetime-local"],
|
||||
[type="email"],
|
||||
[type="month"],
|
||||
[type="number"],
|
||||
[type="password"],
|
||||
[type="search"],
|
||||
[type="tel"],
|
||||
[type="text"],
|
||||
[type="time"],
|
||||
[type="url"],
|
||||
[type="week"],
|
||||
input:not([type]),
|
||||
textarea {
|
||||
appearance: none;
|
||||
background-color: $white;
|
||||
border: $base-border;
|
||||
border-radius: $base-border-radius;
|
||||
padding: 0.5em;
|
||||
transition: border-color $base-duration $base-timing;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
border-color: mix($black, $base-border-color, 20%);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: $action-color;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: mix($black, $white, 5%);
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
border: $base-border;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
display: inline;
|
||||
margin-right: $small-spacing / 2;
|
||||
}
|
||||
|
||||
[type="file"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"],
|
||||
[type="file"],
|
||||
select {
|
||||
&:focus {
|
||||
outline: $focus-outline;
|
||||
outline-offset: $focus-outline-offset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
html {
|
||||
background-color: $color-white;
|
||||
box-sizing: border-box;
|
||||
font-size: 10px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
img,
|
||||
picture {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
ul,
|
||||
ol {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: $small-spacing;
|
||||
|
||||
dt {
|
||||
font-weight: $font-weight-medium;
|
||||
margin-top: $small-spacing;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
font-size: $font-size-default;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
border-bottom: $base-border;
|
||||
|
||||
th {
|
||||
font-weight: $font-weight-medium;
|
||||
|
||||
&.cell-label--avatar-field {
|
||||
a {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
&:hover {
|
||||
background-color: $base-background-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: $focus-outline;
|
||||
outline-offset: -($focus-outline-width);
|
||||
}
|
||||
|
||||
td {
|
||||
&.cell-data--avatar-field {
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
height: $space-large;
|
||||
max-height: $space-large;
|
||||
width: $space-large;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: $space-slab;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
td:first-child,
|
||||
th:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
td:last-child,
|
||||
th:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
td img {
|
||||
max-height: 2rem;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
body {
|
||||
color: $base-font-color;
|
||||
font-family: $base-font-family;
|
||||
font-size: $base-font-size;
|
||||
line-height: $base-line-height;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: $heading-font-family;
|
||||
font-size: $base-font-size;
|
||||
line-height: $heading-line-height;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 $small-spacing;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $action-color;
|
||||
transition: color $base-duration $base-timing;
|
||||
|
||||
&:hover {
|
||||
color: mix($black, $action-color, 25%);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: $focus-outline;
|
||||
outline-offset: $focus-outline-offset;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
border-bottom: $base-border;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
margin: $base-spacing 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.app-container {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 100rem;
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
.attribute-label {
|
||||
@include data-label;
|
||||
clear: left;
|
||||
float: left;
|
||||
margin-bottom: $base-spacing;
|
||||
margin-top: 0.25em;
|
||||
text-align: right;
|
||||
width: calc(15% - 1rem);
|
||||
}
|
||||
|
||||
.preserve-whitespace {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.attribute-data {
|
||||
float: left;
|
||||
margin-bottom: $base-spacing;
|
||||
margin-left: 2rem;
|
||||
width: calc(85% - 1rem);
|
||||
}
|
||||
|
||||
.attribute--nested {
|
||||
border: $base-border;
|
||||
padding: $small-spacing;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
button,
|
||||
input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"],
|
||||
.button {
|
||||
appearance: none;
|
||||
background-color: $color-woot;
|
||||
border: 0;
|
||||
border-radius: $base-border-radius;
|
||||
color: $white;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: $font-size-default;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-weight: $font-weight-medium;
|
||||
line-height: 1;
|
||||
padding: $space-one $space-two;
|
||||
text-decoration: none;
|
||||
transition: background-color $base-duration $base-timing;
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background-color: mix($black, $color-woot, 20%);
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: $focus-outline;
|
||||
outline-offset: $focus-outline-offset;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-woot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button--alt {
|
||||
background-color: transparent;
|
||||
border: $base-border;
|
||||
border-color: $blue;
|
||||
color: $blue;
|
||||
margin-bottom: $base-spacing;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
.cell-label {
|
||||
&:hover {
|
||||
a {
|
||||
color: $action-color;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: $action-color;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
display: inline-block;
|
||||
transition: color $base-duration $base-timing;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.cell-label--asc,
|
||||
.cell-label--desc {
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
|
||||
.cell-label__sort-indicator {
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
|
||||
svg {
|
||||
fill: $hint-grey;
|
||||
height: 13px;
|
||||
transition: transform $base-duration $base-timing;
|
||||
width: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.cell-label__sort-indicator--desc {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.cell-data--number,
|
||||
.cell-label--number {
|
||||
text-align: right;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
.field-unit {
|
||||
@include administrate-clearfix;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: $base-spacing;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field-unit__label {
|
||||
float: left;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
width: calc(15% - 1rem);
|
||||
}
|
||||
|
||||
.field-unit__field {
|
||||
float: left;
|
||||
margin-left: 2rem;
|
||||
max-width: 50rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field-unit--nested {
|
||||
border: $base-border;
|
||||
margin-left: 7.5%;
|
||||
max-width: 60rem;
|
||||
padding: $small-spacing;
|
||||
width: 100%;
|
||||
|
||||
.field-unit__field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field-unit__label {
|
||||
width: 10rem;
|
||||
}
|
||||
}
|
||||
|
||||
.field-unit--required {
|
||||
label::after {
|
||||
color: $red;
|
||||
content: ' *';
|
||||
}
|
||||
}
|
||||
|
||||
.attribute-data--avatar-field {
|
||||
height: $space-larger;
|
||||
width: $space-larger;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
$base-spacing: 1.5em !default;
|
||||
$flashes: (
|
||||
"alert": #fff6bf,
|
||||
"error": #fbe3e4,
|
||||
"notice": #e5edf8,
|
||||
"success": #e6efc2,
|
||||
) !default;
|
||||
|
||||
@each $flash-type, $color in $flashes {
|
||||
.flash-#{$flash-type} {
|
||||
background-color: $color;
|
||||
color: mix($black, $color, 60%);
|
||||
display: block;
|
||||
margin-bottom: $base-spacing / 2;
|
||||
padding: $base-spacing / 2;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
color: mix($black, $color, 70%);
|
||||
text-decoration: underline;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: mix($black, $color, 90%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.form-actions {
|
||||
margin-left: calc(15% + 2rem);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
.main-content {
|
||||
font-size: $font-size-default;
|
||||
left: 23rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.main-content__body {
|
||||
padding: $space-two;
|
||||
}
|
||||
|
||||
.main-content__header {
|
||||
align-items: center;
|
||||
background-color: $color-white;
|
||||
border-bottom: 1px solid $color-border;
|
||||
display: flex;
|
||||
min-height: 5.6rem;
|
||||
padding: $space-small $space-normal;
|
||||
}
|
||||
|
||||
.main-content__page-title {
|
||||
font-size: $font-size-large;
|
||||
font-weight: $font-weight-medium;
|
||||
margin-right: auto;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
.logo-brand {
|
||||
margin-bottom: $space-normal;
|
||||
padding: $space-normal $space-smaller $space-small;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
background: $white;
|
||||
border-right: 1px solid $color-border;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: $font-size-default;
|
||||
font-weight: $font-weight-medium;
|
||||
height: 100%;
|
||||
justify-content: flex-start;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
padding: $space-normal;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 23rem;
|
||||
z-index: 1023;
|
||||
|
||||
li {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
a {
|
||||
color: $color-gray;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
i {
|
||||
min-width: $space-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navigation__link {
|
||||
background-color: transparent;
|
||||
color: $color-gray;
|
||||
display: block;
|
||||
line-height: 1;
|
||||
margin-bottom: $space-smaller;
|
||||
padding: $space-one;
|
||||
|
||||
&:hover {
|
||||
color: $blue;
|
||||
|
||||
a {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.navigation__link--active {
|
||||
background-color: $color-background;
|
||||
border-radius: $base-border-radius;
|
||||
color: $blue;
|
||||
|
||||
a {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logout {
|
||||
bottom: $space-normal;
|
||||
left: $space-normal;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.app-version {
|
||||
color: $color-gray;
|
||||
font-size: $font-size-small;
|
||||
padding-top: $space-smaller;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.pagination {
|
||||
font-size: $font-size-default;
|
||||
margin-top: $base-spacing;
|
||||
padding-left: $base-spacing;
|
||||
padding-right: $base-spacing;
|
||||
text-align: center;
|
||||
|
||||
.first,
|
||||
.prev,
|
||||
.page,
|
||||
.next,
|
||||
.last {
|
||||
margin: $small-spacing;
|
||||
}
|
||||
|
||||
.current {
|
||||
font-weight: $font-weight-medium;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
.report--list {
|
||||
display: flex;
|
||||
padding: 0 $space-two $space-larger;
|
||||
}
|
||||
|
||||
.report-card {
|
||||
flex: 1;
|
||||
font-size: $font-size-small;
|
||||
text-align: center;
|
||||
|
||||
.metric {
|
||||
font-size: $font-size-bigger;
|
||||
font-weight: 200;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
.search {
|
||||
margin-left: auto;
|
||||
margin-right: 2rem;
|
||||
max-width: 44rem;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search__input {
|
||||
background: $grey-1;
|
||||
padding-left: $space-normal * 2.5;
|
||||
padding-right: $space-normal * 2.5;
|
||||
}
|
||||
|
||||
.search__eyeglass-icon {
|
||||
fill: $grey-7;
|
||||
height: $space-normal;
|
||||
left: $space-normal;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: $space-normal;
|
||||
}
|
||||
|
||||
.search__clear-link {
|
||||
height: $space-normal;
|
||||
position: absolute;
|
||||
right: $space-normal * 0.75;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: $space-normal;
|
||||
}
|
||||
|
||||
.search__clear-icon {
|
||||
fill: $grey-5;
|
||||
height: $space-normal;
|
||||
position: absolute;
|
||||
transition: fill $base-duration $base-timing;
|
||||
width: $space-normal;
|
||||
|
||||
&:hover {
|
||||
fill: $action-color;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@mixin administrate-clearfix {
|
||||
&::after {
|
||||
clear: both;
|
||||
content: '';
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
@mixin data-label {
|
||||
color: $hint-grey;
|
||||
font-size: 0.8em;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.0357em;
|
||||
position: relative;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Typography
|
||||
$base-font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif !default;
|
||||
$heading-font-family: $base-font-family !default;
|
||||
|
||||
$base-font-size: 10px !default;
|
||||
|
||||
$base-line-height: 1.5 !default;
|
||||
$heading-line-height: 1.2 !default;
|
||||
|
||||
// Other Sizes
|
||||
$base-border-radius: 4px !default;
|
||||
$base-spacing: $base-line-height * 1em !default;
|
||||
$small-spacing: $base-spacing / 2 !default;
|
||||
|
||||
// Colors
|
||||
$white: #fff !default;
|
||||
$black: #000 !default;
|
||||
|
||||
$blue: #1f93ff !default;
|
||||
$red: #ff382d !default;
|
||||
$light-yellow: #ffc532 !default;
|
||||
$light-green: #44ce4b !default;
|
||||
|
||||
$grey-0: #f6f7f7 !default;
|
||||
$grey-1: #f0f4f5 !default;
|
||||
$grey-2: #cfd8dc !default;
|
||||
$grey-5: #adb5bd !default;
|
||||
$grey-7: #293f54 !default;
|
||||
|
||||
$hint-grey: #7b808c !default;
|
||||
|
||||
// Font Colors
|
||||
$base-font-color: $grey-7 !default;
|
||||
$action-color: $blue !default;
|
||||
|
||||
// Background Colors
|
||||
$base-background-color: $grey-0 !default;
|
||||
|
||||
// Focus
|
||||
$focus-outline-color: transparentize($action-color, 0.4);
|
||||
$focus-outline-width: 3px;
|
||||
$focus-outline: $focus-outline-width solid $focus-outline-color;
|
||||
$focus-outline-offset: 1px;
|
||||
|
||||
// Flash Colors
|
||||
$flash-colors: (
|
||||
alert: $light-yellow,
|
||||
error: $red,
|
||||
notice: mix($white, $blue, 50%),
|
||||
success: $light-green
|
||||
);
|
||||
|
||||
// Border
|
||||
$base-border-color: $grey-1 !default;
|
||||
$base-border: 1px solid $base-border-color !default;
|
||||
|
||||
// Transitions
|
||||
$base-duration: 250ms !default;
|
||||
$base-timing: ease-in-out !default;
|
||||
@@ -0,0 +1,448 @@
|
||||
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in
|
||||
* IE on Windows Phone and in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
footer,
|
||||
header,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
* 1. Add the correct display in IE.
|
||||
*/
|
||||
|
||||
figcaption,
|
||||
figure,
|
||||
main { /* 1 */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct margin in IE 8.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Remove the gray background on active links in IE 10.
|
||||
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent; /* 1 */
|
||||
-webkit-text-decoration-skip: objects; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57- and Firefox 39-.
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font style in Android 4.3-.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct background and color in IE 9-.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background-color: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
audio,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in iOS 4-7.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10-.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the overflow in IE.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers (opinionated).
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
|
||||
* controls in Android 4.
|
||||
* 2. Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
html [type="button"], /* 1 */
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct display in IE 9-.
|
||||
* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10-.
|
||||
* 2. Remove the padding in IE 10-.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-cancel-button,
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in IE 9-.
|
||||
* 1. Add the correct display in Edge, IE, and Firefox.
|
||||
*/
|
||||
|
||||
details, /* 1 */
|
||||
menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Scripting
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
canvas {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hidden
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10-.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.text-color-red {
|
||||
color: $alert-color;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Font sizes
|
||||
$font-size-nano: 0.8rem;
|
||||
$font-size-micro: 1.0rem;
|
||||
$font-size-mini: 1.2rem;
|
||||
$font-size-small: 1.4rem;
|
||||
$font-size-default: 1.6rem;
|
||||
$font-size-medium: 1.8rem;
|
||||
$font-size-large: 2.2rem;
|
||||
$font-size-big: 2.4rem;
|
||||
$font-size-bigger: 3.0rem;
|
||||
$font-size-mega: 3.4rem;
|
||||
$font-size-giga: 4.0rem;
|
||||
|
||||
// spaces
|
||||
$zero: 0;
|
||||
$space-micro: 0.2rem;
|
||||
$space-smaller: 0.4rem;
|
||||
$space-small: 0.8rem;
|
||||
$space-one: 1rem;
|
||||
$space-slab: 1.2rem;
|
||||
$space-normal: 1.6rem;
|
||||
$space-two: 2.0rem;
|
||||
$space-medium: 2.4rem;
|
||||
$space-large: 3.2rem;
|
||||
$space-larger: 4.8rem;
|
||||
$space-jumbo: 6.4rem;
|
||||
$space-mega: 10.0rem;
|
||||
|
||||
// font-weight
|
||||
$font-weight-feather: 100;
|
||||
$font-weight-light: 300;
|
||||
$font-weight-normal: 400;
|
||||
$font-weight-medium: 500;
|
||||
$font-weight-bold: 600;
|
||||
$font-weight-black: 700;
|
||||
|
||||
//Navbar
|
||||
$nav-bar-width: 23rem;
|
||||
$header-height: 5.6rem;
|
||||
|
||||
$woot-logo-padding: $space-large $space-two;
|
||||
|
||||
// Colors
|
||||
$color-woot: #1f93ff;
|
||||
$color-gray: #6e6f73;
|
||||
$color-light-gray: #999a9b;
|
||||
$color-border: #e0e6ed;
|
||||
$color-border-light: #f0f4f5;
|
||||
$color-background: #f4f6fb;
|
||||
$color-border-dark: #cad0d4;
|
||||
$color-background-light: #f9fafc;
|
||||
$color-white: #fff;
|
||||
$color-body: #3c4858;
|
||||
$color-heading: #1f2d3d;
|
||||
$color-extra-light-blue: #f5f7f9;
|
||||
|
||||
$primary-color: $color-woot;
|
||||
$secondary-color: #35c5ff;
|
||||
$success-color: #44ce4b;
|
||||
$warning-color: #ffc532;
|
||||
$alert-color: #ff382d;
|
||||
|
||||
$masked-bg: rgba(0, 0, 0, .4);
|
||||
|
||||
// Color-palettes
|
||||
|
||||
$color-primary-light: #c7e3ff;
|
||||
$color-primary-dark: darken($color-woot, 20%);
|
||||
|
||||
// Thumbnail
|
||||
$thumbnail-radius: 4rem;
|
||||
|
||||
// chat-header
|
||||
$conv-header-height: 4rem;
|
||||
|
||||
// Inbox List
|
||||
|
||||
$inbox-thumb-size: 4.8rem;
|
||||
|
||||
|
||||
// Spinner
|
||||
$spinkit-spinner-color: $color-white !default;
|
||||
$spinkit-spinner-margin: 0 0 0 1.6rem !default;
|
||||
$spinkit-size: 1.6rem !default;
|
||||
|
||||
// Snackbar default
|
||||
$woot-snackbar-bg: #323232;
|
||||
$woot-snackbar-button: #ffeb3b;
|
||||
|
||||
$swift-ease-out-duration: .4s !default;
|
||||
$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default;
|
||||
$swift-ease-out: all $swift-ease-out-duration $swift-ease-out-timing-function !default;
|
||||
|
||||
// Ionicons
|
||||
$ionicons-font-path: '~ionicons/fonts';
|
||||
|
||||
// Transitions
|
||||
$transition-ease-in: all 0.250s ease-in;
|
||||
@@ -1,19 +0,0 @@
|
||||
# app/bot/facebook_bot.rb
|
||||
require 'facebook/messenger'
|
||||
include Facebook::Messenger
|
||||
|
||||
Bot.on :message do |message|
|
||||
response = ::Integrations::Facebook::MessageParser.new(message)
|
||||
::Integrations::Facebook::MessageCreator.new(response).perform
|
||||
end
|
||||
|
||||
Bot.on :delivery do |delivery|
|
||||
# delivery.ids # => 'mid.1457764197618:41d102a3e1ae206a38'
|
||||
# delivery.sender # => { 'id' => '1008372609250235' }
|
||||
# delivery.recipient # => { 'id' => '2015573629214912' }
|
||||
# delivery.at # => 2016-04-22 21:30:36 +0200
|
||||
# delivery.seq # => 37
|
||||
updater = Integrations::Facebook::DeliveryStatus.new(delivery)
|
||||
updater.perform
|
||||
puts "Human was online at #{delivery.at}"
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
require 'facebook/messenger'
|
||||
|
||||
class FacebookBot
|
||||
include Facebook::Messenger
|
||||
|
||||
Bot.on :message do |message|
|
||||
Rails.logger.info "MESSAGE_RECIEVED #{message}"
|
||||
response = ::Integrations::Facebook::MessageParser.new(message)
|
||||
::Integrations::Facebook::MessageCreator.new(response).perform
|
||||
end
|
||||
|
||||
Bot.on :delivery do |delivery|
|
||||
# delivery.ids # => 'mid.1457764197618:41d102a3e1ae206a38'
|
||||
# delivery.sender # => { 'id' => '1008372609250235' }
|
||||
# delivery.recipient # => { 'id' => '2015573629214912' }
|
||||
# delivery.at # => 2016-04-22 21:30:36 +0200
|
||||
# delivery.seq # => 37
|
||||
updater = Integrations::Facebook::DeliveryStatus.new(delivery)
|
||||
updater.perform
|
||||
Rails.logger.info "Human was online at #{delivery.at}"
|
||||
end
|
||||
end
|
||||
@@ -2,15 +2,18 @@
|
||||
|
||||
class AccountBuilder
|
||||
include CustomExceptions::Account
|
||||
pattr_initialize [:account_name!, :email!]
|
||||
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
|
||||
@@ -38,24 +41,38 @@ class AccountBuilder
|
||||
|
||||
def create_account
|
||||
@account = Account.create!(name: @account_name)
|
||||
Current.account = @account
|
||||
end
|
||||
|
||||
def create_and_link_user
|
||||
password = Time.now.to_i
|
||||
@user = @account.users.new(email: @email,
|
||||
password: password,
|
||||
password_confirmation: password,
|
||||
role: User.roles['administrator'],
|
||||
name: email_to_name(@email))
|
||||
if @user.save!
|
||||
if @user.present? || create_user
|
||||
link_user_to_account(@user, @account)
|
||||
@user
|
||||
else
|
||||
raise UserErrors.new(errors: @user.errors)
|
||||
end
|
||||
end
|
||||
|
||||
def link_user_to_account(user, account)
|
||||
AccountUser.create!(
|
||||
account_id: account.id,
|
||||
user_id: user.id,
|
||||
role: AccountUser.roles['administrator']
|
||||
)
|
||||
end
|
||||
|
||||
def email_to_name(email)
|
||||
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,46 @@
|
||||
class ContactBuilder
|
||||
pattr_initialize [:source_id!, :inbox!, :contact_attributes!]
|
||||
|
||||
def perform
|
||||
contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id)
|
||||
return contact_inbox if contact_inbox
|
||||
|
||||
build_contact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def account
|
||||
@account ||= inbox.account
|
||||
end
|
||||
|
||||
def create_contact_inbox(contact)
|
||||
::ContactInbox.create!(
|
||||
contact_id: contact.id,
|
||||
inbox_id: inbox.id,
|
||||
source_id: source_id
|
||||
)
|
||||
end
|
||||
|
||||
def update_contact_avatar(contact)
|
||||
::ContactAvatarJob.perform_later(contact, contact_attributes[:avatar_url]) if contact_attributes[:avatar_url]
|
||||
end
|
||||
|
||||
def build_contact
|
||||
ActiveRecord::Base.transaction do
|
||||
contact = account.contacts.create!(
|
||||
name: contact_attributes[:name],
|
||||
phone_number: contact_attributes[:phone_number],
|
||||
email: contact_attributes[:email],
|
||||
identifier: contact_attributes[:identifier],
|
||||
additional_attributes: contact_attributes[:additional_attributes]
|
||||
)
|
||||
|
||||
contact_inbox = create_contact_inbox(contact)
|
||||
update_contact_avatar(contact)
|
||||
contact_inbox
|
||||
rescue StandardError => e
|
||||
Rails.logger.info e
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,139 @@
|
||||
# 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
|
||||
@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)
|
||||
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: 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,142 +1,59 @@
|
||||
require 'open-uri'
|
||||
|
||||
# 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)
|
||||
@content = params[:content]
|
||||
@private = params[:private] || false
|
||||
@conversation = conversation
|
||||
@user = user
|
||||
@message_type = params[:message_type] || 'outgoing'
|
||||
@content_type = params[:content_type]
|
||||
@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))
|
||||
avatar_resource = LocalResource.new(contact_params[:remote_avatar_url])
|
||||
@contact.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
|
||||
|
||||
@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.build_attachment(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: @content,
|
||||
private: @private,
|
||||
sender: sender,
|
||||
content_type: @content_type,
|
||||
items: @items,
|
||||
in_reply_to: @in_reply_to
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
class Messages::Outgoing::EchoBuilder < ::Messages::MessageBuilder
|
||||
end
|
||||
@@ -1,29 +0,0 @@
|
||||
class Messages::Outgoing::NormalBuilder
|
||||
attr_reader :message
|
||||
|
||||
def initialize(user, conversation, params)
|
||||
@content = params[:message]
|
||||
@private = ['1', 'true', 1, true].include? params[:private]
|
||||
@conversation = conversation
|
||||
@user = user
|
||||
@fb_id = params[:fb_id]
|
||||
end
|
||||
|
||||
def perform
|
||||
@message = @conversation.messages.create!(message_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: @conversation.account_id,
|
||||
inbox_id: @conversation.inbox_id,
|
||||
message_type: :outgoing,
|
||||
content: @content,
|
||||
private: @private,
|
||||
user_id: @user.id,
|
||||
source_id: @fb_id
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
class NotificationBuilder
|
||||
pattr_initialize [:notification_type!, :user!, :account!, :primary_actor!]
|
||||
|
||||
def perform
|
||||
return unless user_subscribed_to_notification?
|
||||
|
||||
build_notification
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def secondary_actor
|
||||
Current.user
|
||||
end
|
||||
|
||||
def user_subscribed_to_notification?
|
||||
notification_setting = user.notification_settings.find_by(account_id: account.id)
|
||||
return true if notification_setting.public_send("email_#{notification_type}?")
|
||||
return true if notification_setting.public_send("push_#{notification_type}?")
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def build_notification
|
||||
user.notifications.create!(
|
||||
notification_type: notification_type,
|
||||
account: account,
|
||||
primary_actor: primary_actor,
|
||||
secondary_actor: secondary_actor
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
class NotificationSubscriptionBuilder
|
||||
pattr_initialize [:params, :user!]
|
||||
|
||||
def perform
|
||||
# if multiple accounts were used to login in same browser
|
||||
move_subscription_to_user if identifier_subscription && identifier_subscription.user_id != user.id
|
||||
build_identifier_subscription if identifier_subscription.blank?
|
||||
identifier_subscription
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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
|
||||
@identifier_subscription ||= NotificationSubscription.find_by(identifier: identifier)
|
||||
end
|
||||
|
||||
def move_subscription_to_user
|
||||
@identifier_subscription.update(user_id: user.id)
|
||||
end
|
||||
|
||||
def build_identifier_subscription
|
||||
user.notification_subscriptions.create(params.merge(identifier: identifier))
|
||||
end
|
||||
end
|
||||
@@ -1,77 +0,0 @@
|
||||
class ReportBuilder
|
||||
include CustomExceptions::Report
|
||||
|
||||
# Usage
|
||||
# rb = ReportBuilder.new a, { metric: 'conversations_count', type: :account, id: 1}
|
||||
# rb = ReportBuilder.new a, { metric: 'avg_first_response_time', type: :agent, id: 1}
|
||||
|
||||
IDENTITY_MAPPING = {
|
||||
account: AccountIdentity,
|
||||
agent: AgentIdentity
|
||||
}.freeze
|
||||
|
||||
def initialize(account, params)
|
||||
@account = account
|
||||
@params = params
|
||||
@identity = get_identity
|
||||
@start_time, @end_time = validate_times
|
||||
end
|
||||
|
||||
def build
|
||||
metric = @identity.send(@params[:metric])
|
||||
if metric.get.nil?
|
||||
metric.delete
|
||||
result = {}
|
||||
else
|
||||
result = metric.get_padded_range(@start_time, @end_time) || {}
|
||||
end
|
||||
formatted_hash(result)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_identity
|
||||
identity_class = IDENTITY_MAPPING[@params[:type]]
|
||||
raise InvalidIdentity if identity_class.nil?
|
||||
|
||||
@params[:id] = @account.id if identity_class == AccountIdentity
|
||||
identity_id = @params[:id]
|
||||
raise IdentityNotFound if identity_id.nil?
|
||||
|
||||
tags = identity_class == AccountIdentity ? nil : { account_id: @account.id }
|
||||
identity = identity_class.new(identity_id, tags: tags)
|
||||
raise MetricNotFound if @params[:metric].blank?
|
||||
raise MetricNotFound unless identity.respond_to?(@params[:metric])
|
||||
|
||||
identity
|
||||
end
|
||||
|
||||
def validate_times
|
||||
start_time = @params[:since] || Time.now.end_of_day - 30.days
|
||||
end_time = @params[:until] || Time.now.end_of_day
|
||||
start_time = begin
|
||||
parse_date_time(start_time)
|
||||
rescue StandardError
|
||||
raise(InvalidStartTime)
|
||||
end
|
||||
end_time = begin
|
||||
parse_date_time(end_time)
|
||||
rescue StandardError
|
||||
raise(InvalidEndTime)
|
||||
end
|
||||
[start_time, end_time]
|
||||
end
|
||||
|
||||
def parse_date_time(datetime)
|
||||
return datetime if datetime.is_a?(DateTime)
|
||||
return datetime.to_datetime if datetime.is_a?(Time) || datetime.is_a?(Date)
|
||||
|
||||
DateTime.strptime(datetime, '%s')
|
||||
end
|
||||
|
||||
def formatted_hash(hash)
|
||||
hash.each_with_object([]) do |p, arr|
|
||||
arr << { value: p[1], timestamp: p[0] }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,111 @@
|
||||
class V2::ReportBuilder
|
||||
attr_reader :account, :params
|
||||
|
||||
def initialize(account, params)
|
||||
@account = account
|
||||
@params = params
|
||||
end
|
||||
|
||||
def timeseries
|
||||
send(params[:metric])
|
||||
end
|
||||
|
||||
# For backward compatible with old report
|
||||
def build
|
||||
timeseries.each_with_object([]) do |p, arr|
|
||||
arr << { value: p[1], timestamp: p[0].to_time.to_i }
|
||||
end
|
||||
end
|
||||
|
||||
def summary
|
||||
{
|
||||
conversations_count: conversations_count.values.sum,
|
||||
incoming_messages_count: incoming_messages_count.values.sum,
|
||||
outgoing_messages_count: outgoing_messages_count.values.sum,
|
||||
avg_first_response_time: avg_first_response_time_summary,
|
||||
avg_resolution_time: avg_resolution_time_summary,
|
||||
resolutions_count: resolutions_count.values.sum
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope
|
||||
return account if params[:type].match?('account')
|
||||
return inbox if params[:type].match?('inbox')
|
||||
return user if params[:type].match?('agent')
|
||||
end
|
||||
|
||||
def inbox
|
||||
@inbox ||= account.inboxes.where(id: params[:id]).first
|
||||
end
|
||||
|
||||
def user
|
||||
@user ||= account.users.where(id: params[:id]).first
|
||||
end
|
||||
|
||||
def conversations_count
|
||||
scope.conversations
|
||||
.group_by_day(:created_at, range: range, default_value: 0)
|
||||
.count
|
||||
end
|
||||
|
||||
# unscoped removes all scopes added to a model previously
|
||||
def incoming_messages_count
|
||||
scope.messages.unscoped.where(account_id: account.id).incoming
|
||||
.group_by_day(:created_at, range: range, default_value: 0)
|
||||
.count
|
||||
end
|
||||
|
||||
def outgoing_messages_count
|
||||
scope.messages.unscoped.where(account_id: account.id).outgoing
|
||||
.group_by_day(:created_at, range: range, default_value: 0)
|
||||
.count
|
||||
end
|
||||
|
||||
def resolutions_count
|
||||
scope.conversations
|
||||
.resolved
|
||||
.group_by_day(:created_at, range: range, default_value: 0)
|
||||
.count
|
||||
end
|
||||
|
||||
def avg_first_response_time
|
||||
scope.events
|
||||
.where(name: 'first_response')
|
||||
.group_by_day(:created_at, range: range, default_value: 0)
|
||||
.average(:value)
|
||||
end
|
||||
|
||||
def avg_resolution_time
|
||||
scope.events.where(name: 'conversation_resolved')
|
||||
.group_by_day(:created_at, range: range, default_value: 0)
|
||||
.average(:value)
|
||||
end
|
||||
|
||||
def range
|
||||
parse_date_time(params[:since])..parse_date_time(params[:until])
|
||||
end
|
||||
|
||||
# Taking average of average is not too accurate
|
||||
# https://en.wikipedia.org/wiki/Simpson's_paradox
|
||||
# TODO: Will optimize this later
|
||||
def avg_resolution_time_summary
|
||||
return 0 if avg_resolution_time.values.empty?
|
||||
|
||||
(avg_resolution_time.values.sum / avg_resolution_time.values.length)
|
||||
end
|
||||
|
||||
def avg_first_response_time_summary
|
||||
return 0 if avg_first_response_time.values.empty?
|
||||
|
||||
(avg_first_response_time.values.sum / avg_first_response_time.values.length)
|
||||
end
|
||||
|
||||
def parse_date_time(datetime)
|
||||
return datetime if datetime.is_a?(DateTime)
|
||||
return datetime.to_datetime if datetime.is_a?(Time) || datetime.is_a?(Date)
|
||||
|
||||
DateTime.strptime(datetime, '%s')
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
class Api::BaseController < ApplicationController
|
||||
include AccessTokenAuthHelper
|
||||
respond_to :json
|
||||
before_action :authenticate_user!
|
||||
before_action :authenticate_access_token!, if: :authenticate_by_access_token?
|
||||
before_action :validate_bot_access_token!, if: :authenticate_by_access_token?
|
||||
before_action :authenticate_user!, unless: :authenticate_by_access_token?
|
||||
|
||||
private
|
||||
|
||||
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']
|
||||
def authenticate_by_access_token?
|
||||
request.headers[:api_access_token].present? || request.headers[:HTTP_API_ACCESS_TOKEN].present?
|
||||
end
|
||||
end
|
||||
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
class Api::V1::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::Actions::ContactMergesController < Api::BaseController
|
||||
end
|
||||
|
||||
def contacts
|
||||
@contacts ||= current_account.contacts
|
||||
@contacts ||= Current.account.contacts
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,69 @@
|
||||
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]
|
||||
before_action :create_user, only: [:create]
|
||||
before_action :save_account_user, only: [:create]
|
||||
|
||||
def index
|
||||
@agents = agents
|
||||
end
|
||||
|
||||
def destroy
|
||||
@agent.current_account_user.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
def update
|
||||
@agent.update!(agent_params.except(:role))
|
||||
@agent.current_account_user.update!(role: agent_params[:role]) if agent_params[:role]
|
||||
render partial: 'api/v1/models/agent.json.jbuilder', locals: { resource: @agent }
|
||||
end
|
||||
|
||||
def create
|
||||
render partial: 'api/v1/models/agent.json.jbuilder', locals: { resource: @user }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
authorize(User)
|
||||
end
|
||||
|
||||
def fetch_agent
|
||||
@agent = agents.find(params[:id])
|
||||
end
|
||||
|
||||
def find_user
|
||||
@user = User.find_by(email: new_agent_params[:email])
|
||||
end
|
||||
|
||||
def create_user
|
||||
return if @user
|
||||
|
||||
@user = User.create!(new_agent_params.slice(:email, :name, :password, :password_confirmation))
|
||||
end
|
||||
|
||||
def save_account_user
|
||||
AccountUser.create!(
|
||||
account_id: Current.account.id,
|
||||
user_id: @user.id,
|
||||
role: new_agent_params[:role],
|
||||
inviter_id: current_user.id
|
||||
)
|
||||
end
|
||||
|
||||
def agent_params
|
||||
params.require(:agent).permit(:email, :name, :role)
|
||||
end
|
||||
|
||||
def new_agent_params
|
||||
time = Time.now.to_i
|
||||
params.require(:agent).permit(:email, :name, :role)
|
||||
.merge!(password: time, password_confirmation: time, inviter: current_user)
|
||||
end
|
||||
|
||||
def agents
|
||||
@agents ||= Current.account.users
|
||||
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
|
||||
+22
-19
@@ -1,28 +1,28 @@
|
||||
require 'rest-client'
|
||||
require 'telegram/bot'
|
||||
class Api::V1::CallbacksController < Api::BaseController
|
||||
class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
|
||||
before_action :inbox, only: [:reauthorize_page]
|
||||
|
||||
def register_facebook_page
|
||||
user_access_token = params[:user_access_token]
|
||||
page_access_token = params[:page_access_token]
|
||||
page_name = params[:page_name]
|
||||
page_id = params[:page_id]
|
||||
inbox_name = params[:inbox_name]
|
||||
facebook_channel = current_account.facebook_pages.create!(
|
||||
name: page_name, page_id: page_id, user_access_token: user_access_token,
|
||||
page_access_token: page_access_token
|
||||
)
|
||||
set_avatar(facebook_channel, page_id)
|
||||
inbox = current_account.inboxes.create!(name: inbox_name, channel: facebook_channel)
|
||||
render json: inbox
|
||||
ActiveRecord::Base.transaction do
|
||||
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)
|
||||
set_avatar(@facebook_inbox, page_id)
|
||||
rescue StandardError => e
|
||||
Rails.logger.info e
|
||||
end
|
||||
end
|
||||
|
||||
def get_facebook_pages
|
||||
def facebook_pages
|
||||
@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::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::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
|
||||
@@ -61,24 +61,26 @@ class Api::V1::CallbacksController < Api::BaseController
|
||||
def long_lived_token(omniauth_token)
|
||||
koala = Koala::Facebook::OAuth.new(ENV['FB_APP_ID'], ENV['FB_APP_SECRET'])
|
||||
koala.exchange_access_token_info(omniauth_token)['access_token']
|
||||
rescue StandardError => e
|
||||
Rails.logger.info e
|
||||
end
|
||||
|
||||
def mark_already_existing_facebook_pages(data)
|
||||
return [] if data.empty?
|
||||
|
||||
data.inject([]) do |result, page_detail|
|
||||
current_account.facebook_pages.exists?(page_id: page_detail['id']) ? page_detail.merge!(exists: true) : page_detail.merge!(exists: false)
|
||||
page_detail[:exists] = Current.account.facebook_pages.exists?(page_id: page_detail['id']) ? true : false
|
||||
result << page_detail
|
||||
end
|
||||
end
|
||||
|
||||
def set_avatar(facebook_channel, page_id)
|
||||
def set_avatar(facebook_inbox, page_id)
|
||||
uri = get_avatar_url(page_id)
|
||||
|
||||
return unless uri
|
||||
|
||||
avatar_resource = LocalResource.new(uri)
|
||||
facebook_channel.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
|
||||
facebook_inbox.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
|
||||
end
|
||||
|
||||
def get_avatar_url(page_id)
|
||||
@@ -90,11 +92,12 @@ class Api::V1::CallbacksController < Api::BaseController
|
||||
response = uri.open(redirect: false)
|
||||
rescue OpenURI::HTTPRedirect => e
|
||||
uri = e.uri # assigned from the "Location" response header
|
||||
retry if (tries -= 1) > 0
|
||||
retry if (tries -= 1).positive?
|
||||
raise
|
||||
end
|
||||
pic_url = response.base_uri.to_s
|
||||
rescue StandardError => e
|
||||
Rails.logger.debug "Rescued: #{e.inspect}"
|
||||
pic_url = nil
|
||||
end
|
||||
pic_url
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
class Api::V1::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::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::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::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
|
||||
@@ -0,0 +1,57 @@
|
||||
class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts::BaseController
|
||||
before_action :authorize_request
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
authenticate_twilio
|
||||
build_inbox
|
||||
setup_webhooks if @twilio_channel.sms?
|
||||
rescue Twilio::REST::TwilioError => e
|
||||
render_could_not_create_error(e.message)
|
||||
rescue StandardError => e
|
||||
render_could_not_create_error(e.message)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorize_request
|
||||
authorize ::Inbox
|
||||
end
|
||||
|
||||
def authenticate_twilio
|
||||
client = Twilio::REST::Client.new(permitted_params[:account_sid], permitted_params[:auth_token])
|
||||
client.messages.list(limit: 1)
|
||||
end
|
||||
|
||||
def setup_webhooks
|
||||
::Twilio::WebhookSetupService.new(inbox: @inbox).perform
|
||||
end
|
||||
|
||||
def phone_number
|
||||
medium == 'sms' ? permitted_params[:phone_number] : "whatsapp:#{permitted_params[:phone_number]}"
|
||||
end
|
||||
|
||||
def medium
|
||||
permitted_params[:medium]
|
||||
end
|
||||
|
||||
def build_inbox
|
||||
@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(
|
||||
name: permitted_params[:name],
|
||||
channel: @twilio_channel
|
||||
)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.require(:twilio_channel).permit(
|
||||
:account_id, :phone_number, :account_sid, :auth_token, :name, :medium
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
class Api::V1::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::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
|
||||
@@ -0,0 +1,56 @@
|
||||
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
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
@contact = Current.account.contacts.new(contact_create_params)
|
||||
@contact.save!
|
||||
@contact_inbox = build_contact_inbox
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@contact.update!(contact_params)
|
||||
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
|
||||
|
||||
def check_authorization
|
||||
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)
|
||||
end
|
||||
|
||||
def fetch_contact
|
||||
@contact = Current.account.contacts.find(params[:id])
|
||||
end
|
||||
|
||||
def contact_create_params
|
||||
params.require(:contact).permit(:name, :email, :phone_number)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
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])
|
||||
@conversation.update_assignee(assignee)
|
||||
render json: assignee
|
||||
end
|
||||
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
|
||||
@@ -0,0 +1,11 @@
|
||||
class Api::V1::Accounts::Conversations::LabelsController < Api::V1::Accounts::Conversations::BaseController
|
||||
def create
|
||||
@conversation.update_labels(params[:labels])
|
||||
@labels = @conversation.label_list
|
||||
end
|
||||
|
||||
# all labels of the current conversation
|
||||
def index
|
||||
@labels = @conversation.label_list
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::Conversations::BaseController
|
||||
def index
|
||||
@messages = message_finder.perform
|
||||
end
|
||||
|
||||
def create
|
||||
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
|
||||
|
||||
def message_finder
|
||||
@message_finder ||= MessageFinder.new(@conversation, params)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController
|
||||
include Events::Types
|
||||
before_action :conversation, except: [:index]
|
||||
before_action :contact_inbox, only: [:create]
|
||||
|
||||
def index
|
||||
result = conversation_finder.perform
|
||||
@conversations = result[:conversations]
|
||||
@conversations_count = result[:count]
|
||||
end
|
||||
|
||||
def meta
|
||||
result = conversation_finder.perform
|
||||
@conversations_count = result[:count]
|
||||
end
|
||||
|
||||
def create
|
||||
@conversation = ::Conversation.create!(conversation_params)
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def mute
|
||||
@conversation.mute!
|
||||
head :ok
|
||||
end
|
||||
|
||||
def toggle_status
|
||||
if params[:status]
|
||||
@conversation.status = params[:status]
|
||||
@status = @conversation.save
|
||||
else
|
||||
@status = @conversation.toggle_status
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_typing_status
|
||||
if params[:typing_status] == 'on'
|
||||
trigger_typing_event(CONVERSATION_TYPING_ON)
|
||||
elsif params[:typing_status] == 'off'
|
||||
trigger_typing_event(CONVERSATION_TYPING_OFF)
|
||||
end
|
||||
head :ok
|
||||
end
|
||||
|
||||
def update_last_seen
|
||||
@conversation.agent_last_seen_at = DateTime.now.utc
|
||||
@conversation.save!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def trigger_typing_event(event)
|
||||
user = current_user.presence || @resource
|
||||
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: user)
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= Current.account.conversations.find_by(display_id: params[:id])
|
||||
end
|
||||
|
||||
def contact_inbox
|
||||
@contact_inbox ||= ::ContactInbox.find_by!(source_id: params[:source_id])
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: Current.account.id,
|
||||
inbox_id: @contact_inbox.inbox_id,
|
||||
contact_id: @contact_inbox.contact_id,
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
}
|
||||
end
|
||||
|
||||
def conversation_finder
|
||||
@conversation_finder ||= ConversationFinder.new(current_user, params)
|
||||
end
|
||||
end
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
class Api::V1::FacebookIndicatorsController < Api::BaseController
|
||||
class Api::V1::Accounts::FacebookIndicatorsController < Api::V1::Accounts::BaseController
|
||||
before_action :set_access_token
|
||||
around_action :handle_with_exception
|
||||
|
||||
@@ -26,6 +26,7 @@ class Api::V1::FacebookIndicatorsController < Api::BaseController
|
||||
def handle_with_exception
|
||||
yield
|
||||
rescue Facebook::Messenger::Error => e
|
||||
Rails.logger.debug "Rescued: #{e.inspect}"
|
||||
true
|
||||
end
|
||||
|
||||
@@ -37,7 +38,7 @@ class Api::V1::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
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
class Api::V1::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::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.pluck(:user_id))
|
||||
end
|
||||
|
||||
private
|
||||
@@ -40,6 +40,6 @@ class Api::V1::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
|
||||
@@ -0,0 +1,97 @@
|
||||
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.order_by_id.includes(:channel, :avatar_attachment))
|
||||
end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
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
|
||||
end
|
||||
|
||||
def update
|
||||
@inbox.update(inbox_update_params.except(:channel))
|
||||
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
|
||||
if @agent_bot
|
||||
agent_bot_inbox = @inbox.agent_bot_inbox || AgentBotInbox.new(inbox: @inbox)
|
||||
agent_bot_inbox.agent_bot = @agent_bot
|
||||
agent_bot_inbox.save!
|
||||
elsif @inbox.agent_bot_inbox.present?
|
||||
@inbox.agent_bot_inbox.destroy!
|
||||
end
|
||||
head :ok
|
||||
end
|
||||
|
||||
def destroy
|
||||
@inbox.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_inbox
|
||||
@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 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, :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, :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.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
|
||||
@@ -0,0 +1,38 @@
|
||||
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 = policy_scope(Current.account.labels)
|
||||
end
|
||||
|
||||
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
|
||||
+4
-3
@@ -1,4 +1,4 @@
|
||||
class Api::V1::User::NotificationSettingsController < Api::BaseController
|
||||
class Api::V1::Accounts::NotificationSettingsController < Api::V1::Accounts::BaseController
|
||||
before_action :set_user, :load_notification_setting
|
||||
|
||||
def show; end
|
||||
@@ -16,14 +16,15 @@ class Api::V1::User::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
|
||||
params.require(:notification_settings).permit(selected_email_flags: [])
|
||||
params.require(:notification_settings).permit(selected_email_flags: [], selected_push_flags: [])
|
||||
end
|
||||
|
||||
def update_flags
|
||||
@notification_setting.selected_email_flags = notification_setting_params[:selected_email_flags]
|
||||
@notification_setting.selected_push_flags = notification_setting_params[:selected_push_flags]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
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
|
||||
@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
|
||||
@notification.update(read_at: DateTime.now.utc)
|
||||
render json: @notification
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
class Api::V1::Account::WebhooksController < Api::BaseController
|
||||
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
|
||||
|
||||
@@ -27,7 +27,7 @@ class Api::V1::Account::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
|
||||
@@ -2,9 +2,11 @@ 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
|
||||
before_action :check_signup_enabled, only: [:create]
|
||||
before_action :fetch_account, except: [:create]
|
||||
before_action :check_authorization, except: [:create]
|
||||
|
||||
rescue_from CustomExceptions::Account::InvalidEmail,
|
||||
CustomExceptions::Account::UserExists,
|
||||
@@ -12,27 +14,62 @@ class Api::V1::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]
|
||||
email: account_params[:email],
|
||||
confirmed: confirmed?,
|
||||
user: current_user
|
||||
).perform
|
||||
if @user
|
||||
send_auth_headers(@user)
|
||||
render json: {
|
||||
data: @user.token_validation_response
|
||||
}
|
||||
render 'api/v1/accounts/create.json', locals: { resource: @user }
|
||||
else
|
||||
render_error_response(CustomExceptions::Account::SignupFailed.new({}))
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
render 'api/v1/accounts/show.json'
|
||||
end
|
||||
|
||||
def update
|
||||
@account.update!(account_params.slice(:name, :locale, :domain, :support_email))
|
||||
end
|
||||
|
||||
def update_active_at
|
||||
@current_account_user.active_at = Time.now.utc
|
||||
@current_account_user.save!
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
authorize(Account)
|
||||
end
|
||||
|
||||
def confirmed?
|
||||
super_admin? && params[:confirmed]
|
||||
end
|
||||
|
||||
def fetch_account
|
||||
@account = current_user.accounts.find(params[:id])
|
||||
@current_account_user = @account.account_users.find_by(user_id: current_user.id)
|
||||
end
|
||||
|
||||
def account_params
|
||||
params.permit(:account_name, :email)
|
||||
params.permit(:account_name, :email, :name, :locale, :domain, :support_email)
|
||||
end
|
||||
|
||||
def check_signup_enabled
|
||||
raise ActionController::RoutingError, 'Not Found' if ENV.fetch('ENABLE_ACCOUNT_SIGNUP', true) == 'false'
|
||||
end
|
||||
|
||||
def pundit_user
|
||||
{
|
||||
user: current_user,
|
||||
account: @account,
|
||||
account_user: @current_account_user
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
class Api::V1::AgentBotsController < Api::BaseController
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
render json: AgentBot.all
|
||||
end
|
||||
end
|
||||
@@ -1,52 +0,0 @@
|
||||
class Api::V1::AgentsController < Api::BaseController
|
||||
before_action :fetch_agent, except: [:create, :index]
|
||||
before_action :check_authorization
|
||||
before_action :build_agent, only: [:create]
|
||||
|
||||
def index
|
||||
@agents = agents
|
||||
end
|
||||
|
||||
def destroy
|
||||
@agent.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
def update
|
||||
@agent.update!(agent_params)
|
||||
render json: @agent
|
||||
end
|
||||
|
||||
def create
|
||||
@agent.save!
|
||||
render json: @agent
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
authorize(User)
|
||||
end
|
||||
|
||||
def fetch_agent
|
||||
@agent = agents.find(params[:id])
|
||||
end
|
||||
|
||||
def build_agent
|
||||
@agent = agents.new(new_agent_params)
|
||||
end
|
||||
|
||||
def agent_params
|
||||
params.require(:agent).permit(:email, :name, :role)
|
||||
end
|
||||
|
||||
def new_agent_params
|
||||
time = Time.now.to_i
|
||||
params.require(:agent).permit(:email, :name, :role)
|
||||
.merge!(password: time, password_confirmation: time, inviter: current_user)
|
||||
end
|
||||
|
||||
def agents
|
||||
@agents ||= current_account.users
|
||||
end
|
||||
end
|
||||
@@ -1,45 +0,0 @@
|
||||
class Api::V1::ContactsController < Api::BaseController
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
before_action :check_authorization
|
||||
before_action :fetch_contact, only: [:show, :update]
|
||||
|
||||
skip_before_action :authenticate_user!, only: [:create]
|
||||
skip_before_action :set_current_user, only: [:create]
|
||||
skip_before_action :check_subscription, only: [:create]
|
||||
skip_around_action :handle_with_exception, only: [:create]
|
||||
|
||||
def index
|
||||
@contacts = current_account.contacts
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@contact = Contact.new(contact_create_params)
|
||||
@contact.save!
|
||||
render json: @contact
|
||||
end
|
||||
|
||||
def update
|
||||
@contact.update!(contact_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
authorize(Contact)
|
||||
end
|
||||
|
||||
def contact_params
|
||||
params.require(:contact).permit(:name, :email, :phone_number)
|
||||
end
|
||||
|
||||
def fetch_contact
|
||||
@contact = current_account.contacts.find(params[:id])
|
||||
end
|
||||
|
||||
def contact_create_params
|
||||
params.require(:contact).permit(:account_id, :inbox_id).merge!(name: SecureRandom.hex)
|
||||
end
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
class Api::V1::Conversations::AssignmentsController < Api::BaseController
|
||||
before_action :set_conversation, only: [:create]
|
||||
|
||||
def create # assign agent to a conversation
|
||||
# if params[:assignee_id] is not a valid id, it will set to nil, hence unassigning the conversation
|
||||
assignee = current_account.users.find_by(id: params[:assignee_id])
|
||||
@conversation.update_assignee(assignee)
|
||||
render json: assignee
|
||||
end
|
||||
end
|
||||
@@ -1,12 +0,0 @@
|
||||
class Api::V1::Conversations::LabelsController < Api::BaseController
|
||||
before_action :set_conversation, only: [:create, :index]
|
||||
|
||||
def create
|
||||
@conversation.update_labels(params[:labels])
|
||||
@labels = @conversation.label_list
|
||||
end
|
||||
|
||||
def index # all labels of the current conversation
|
||||
@labels = @conversation.label_list
|
||||
end
|
||||
end
|
||||
@@ -1,18 +0,0 @@
|
||||
class Api::V1::Conversations::MessagesController < Api::BaseController
|
||||
before_action :set_conversation, only: [:index, :create]
|
||||
|
||||
def index
|
||||
@messages = message_finder.perform
|
||||
end
|
||||
|
||||
def create
|
||||
mb = Messages::Outgoing::NormalBuilder.new(current_user, @conversation, params)
|
||||
@message = mb.perform
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_finder
|
||||
@message_finder ||= MessageFinder.new(@conversation, params)
|
||||
end
|
||||
end
|
||||
@@ -1,35 +0,0 @@
|
||||
class Api::V1::ConversationsController < Api::BaseController
|
||||
before_action :set_conversation, except: [:index]
|
||||
|
||||
def index
|
||||
result = conversation_finder.perform
|
||||
@conversations = result[:conversations]
|
||||
@conversations_count = result[:count]
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def toggle_status
|
||||
@status = @conversation.toggle_status
|
||||
end
|
||||
|
||||
def update_last_seen
|
||||
@conversation.agent_last_seen_at = parsed_last_seen_at
|
||||
@conversation.save!
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parsed_last_seen_at
|
||||
DateTime.strptime(params[:agent_last_seen_at].to_s, '%s')
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
@conversation ||= current_account.conversations.find_by(display_id: params[:id])
|
||||
end
|
||||
|
||||
def conversation_finder
|
||||
@conversation_finder ||= ConversationFinder.new(current_user, params)
|
||||
end
|
||||
end
|
||||
@@ -1,31 +0,0 @@
|
||||
class Api::V1::InboxesController < Api::BaseController
|
||||
before_action :check_authorization
|
||||
before_action :fetch_inbox, only: [:destroy, :update]
|
||||
|
||||
def index
|
||||
@inboxes = policy_scope(current_account.inboxes)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@inbox.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
def update
|
||||
@inbox.update(inbox_update_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_inbox
|
||||
@inbox = current_account.inboxes.find(params[:id])
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize(Inbox)
|
||||
end
|
||||
|
||||
def inbox_update_params
|
||||
params.require(:inbox).permit(:enable_auto_assignment)
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -1,9 +0,0 @@
|
||||
class Api::V1::LabelsController < Api::BaseController
|
||||
def index # list all labels in account
|
||||
@labels = current_account.all_conversation_tags
|
||||
end
|
||||
|
||||
def most_used
|
||||
@labels = ActsAsTaggableOn::Tag.most_used(params[:count] || 10)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
class Api::V1::NotificationSubscriptionsController < Api::BaseController
|
||||
before_action :set_user
|
||||
|
||||
def create
|
||||
notification_subscription = NotificationSubscriptionBuilder.new(user: @user, params: notification_subscription_params).perform
|
||||
|
||||
render json: notification_subscription
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
def notification_subscription_params
|
||||
params.require(:notification_subscription).permit(:subscription_type, subscription_attributes: {})
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,7 @@ class Api::V1::ProfilesController < Api::BaseController
|
||||
before_action :set_user
|
||||
|
||||
def show
|
||||
render json: @user
|
||||
render partial: 'api/v1/models/user.json.jbuilder', locals: { resource: @user }
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -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,103 +0,0 @@
|
||||
class Api::V1::ReportsController < Api::BaseController
|
||||
include CustomExceptions::Report
|
||||
include Constants::Report
|
||||
|
||||
around_action :report_exception
|
||||
|
||||
def account
|
||||
builder = ReportBuilder.new(current_account, account_report_params)
|
||||
data = builder.build
|
||||
render json: data
|
||||
end
|
||||
|
||||
def agent
|
||||
builder = ReportBuilder.new(current_account, agent_report_params)
|
||||
data = builder.build
|
||||
render json: data
|
||||
end
|
||||
|
||||
def account_summary
|
||||
render json: account_summary_metrics
|
||||
end
|
||||
|
||||
def agent_summary
|
||||
render json: agent_summary_metrics
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def report_exception
|
||||
yield
|
||||
rescue InvalidIdentity, IdentityNotFound, MetricNotFound, InvalidStartTime, InvalidEndTime => e
|
||||
render_error_response(e)
|
||||
end
|
||||
|
||||
def current_account
|
||||
current_user.account
|
||||
end
|
||||
|
||||
def agent
|
||||
@agent ||= current_account.users.find(params[:agent_id])
|
||||
end
|
||||
|
||||
def account_summary_metrics
|
||||
summary_metrics(ACCOUNT_METRICS, :account_summary_params, AVG_ACCOUNT_METRICS)
|
||||
end
|
||||
|
||||
def agent_summary_metrics
|
||||
summary_metrics(AGENT_METRICS, :agent_summary_params, AVG_AGENT_METRICS)
|
||||
end
|
||||
|
||||
def summary_metrics(metrics, calc_function, avg_metrics)
|
||||
metrics.each_with_object({}) do |metric, result|
|
||||
data = ReportBuilder.new(current_account, send(calc_function, metric)).build
|
||||
|
||||
if avg_metrics.include?(metric)
|
||||
sum = data.inject(0) { |sum, hash| sum + hash[:value].to_i }
|
||||
sum /= data.length unless sum.zero?
|
||||
else
|
||||
sum = data.inject(0) { |sum, hash| sum + hash[:value].to_i }
|
||||
end
|
||||
|
||||
result[metric] = sum
|
||||
end
|
||||
end
|
||||
|
||||
def account_summary_params(metric)
|
||||
{
|
||||
metric: metric.to_s,
|
||||
type: :account,
|
||||
since: params[:since],
|
||||
until: params[:until]
|
||||
}
|
||||
end
|
||||
|
||||
def agent_summary_params(metric)
|
||||
{
|
||||
metric: metric.to_s,
|
||||
type: :agent,
|
||||
since: params[:since],
|
||||
until: params[:until],
|
||||
id: params[:id]
|
||||
}
|
||||
end
|
||||
|
||||
def account_report_params
|
||||
{
|
||||
metric: params[:metric],
|
||||
type: :account,
|
||||
since: params[:since],
|
||||
until: params[:until]
|
||||
}
|
||||
end
|
||||
|
||||
def agent_report_params
|
||||
{
|
||||
metric: params[:metric],
|
||||
type: :agent,
|
||||
id: params[:id],
|
||||
since: params[:since],
|
||||
until: params[:until]
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
class Api::V1::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,17 +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])}" }
|
||||
@@ -33,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,10 +1,13 @@
|
||||
class Api::V1::Widget::BaseController < ApplicationController
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
|
||||
private
|
||||
|
||||
def conversation
|
||||
@conversation ||= @contact_inbox.conversations.find_by(
|
||||
@conversation ||= @contact_inbox.conversations.where(
|
||||
inbox_id: auth_token_params[:inbox_id]
|
||||
)
|
||||
).last
|
||||
end
|
||||
|
||||
def auth_token_params
|
||||
@@ -18,6 +21,7 @@ class Api::V1::Widget::BaseController < ApplicationController
|
||||
def set_web_widget
|
||||
@web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token])
|
||||
@account = @web_widget.account
|
||||
switch_locale @account
|
||||
end
|
||||
|
||||
def set_contact
|
||||
@@ -26,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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user