Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc9db36d62 | ||
|
|
b8836aa41e | ||
|
|
1290e32150 | ||
|
|
10f3d321de | ||
|
|
525718c8c5 | ||
|
|
cf09c2af4d | ||
|
|
f925bc22e2 | ||
|
|
610a7c661e | ||
|
|
18cea3b0ac | ||
|
|
60177ef418 | ||
|
|
ef2611164d | ||
|
|
d5215fea93 | ||
|
|
4aa35953c4 | ||
|
|
d35e8cfd98 | ||
|
|
73d30b7f69 | ||
|
|
836b317b8a | ||
|
|
35f8d01a0c | ||
|
|
0e6cd699e8 | ||
|
|
368bab2553 | ||
|
|
3639fb1d71 | ||
|
|
585dd1b005 | ||
|
|
c1a519db43 | ||
|
|
b5ef9f14e1 | ||
|
|
627a89faf0 | ||
|
|
a216297ce1 | ||
|
|
a33617cadd | ||
|
|
3fc646f330 | ||
|
|
db31bfcee4 | ||
|
|
7f6abdc987 | ||
|
|
055008cf03 | ||
|
|
899ddcef0d | ||
|
|
4c47d6fd97 | ||
|
|
df04450ab9 | ||
|
|
2839454bc0 | ||
|
|
5c80d0ac42 | ||
|
|
6245a10a70 | ||
|
|
381c358ffd | ||
|
|
b30ecb27a6 | ||
|
|
fd0c26cdae | ||
|
|
6469acc750 | ||
|
|
05310e25d4 | ||
|
|
262d153c18 | ||
|
|
fd67a5795a | ||
|
|
2d4acaf8fe | ||
|
|
35f0fba51d | ||
|
|
8459d09b6a | ||
|
|
8bcac2aab0 | ||
|
|
7c7f91e70f | ||
|
|
eaa61c3745 | ||
|
|
98e2a9b8b5 | ||
|
|
b649516fbb | ||
|
|
6a7b98b345 | ||
|
|
49c7bf5ae4 | ||
|
|
a07200bedf | ||
|
|
bd7e9d2790 | ||
|
|
b6f57d7fc1 | ||
|
|
b89cc9cf57 | ||
|
|
3afc9b5f5b | ||
|
|
979d4fe76b | ||
|
|
4b49d21543 | ||
|
|
aa47bad4c1 | ||
|
|
6ea1e0d047 | ||
|
|
8ea35802b4 | ||
|
|
c10efd2c7b | ||
|
|
f1f4a35b2b | ||
|
|
0bb10cff2d | ||
|
|
78996937ce | ||
|
|
285f7bbeb5 | ||
|
|
87078e1abf | ||
|
|
6c7c5064d1 | ||
|
|
fdbc32f7c2 | ||
|
|
5c843b8a51 | ||
|
|
950164429e | ||
|
|
3a4d316a9d | ||
|
|
98e20e8607 | ||
|
|
4635e5bb44 | ||
|
|
fb582614f2 | ||
|
|
90de0e4843 | ||
|
|
ea74462209 | ||
|
|
fa525fc107 | ||
|
|
773557e49b | ||
|
|
654893b6d7 | ||
|
|
8f2234671b | ||
|
|
043919b3e5 | ||
|
|
27e01920ad | ||
|
|
c95aeb894f | ||
|
|
8720f39ffb | ||
|
|
66b42de3a6 | ||
|
|
864471a21e | ||
|
|
c287ad08fb | ||
|
|
98bfef026e | ||
|
|
ce4ce3c86c | ||
|
|
45e43b0b89 | ||
|
|
ba41a10609 | ||
|
|
997447364c | ||
|
|
3e8c25b4a0 | ||
|
|
a14e8ba250 | ||
|
|
4387736f1a | ||
|
|
2bb2d4aa25 | ||
|
|
c4ae901591 | ||
|
|
04a9dc630a | ||
|
|
7eb766a457 | ||
|
|
03bd34be86 | ||
|
|
98f4a2f6f3 | ||
|
|
28fb6469a3 | ||
|
|
2140443f07 | ||
|
|
3e03b08ad0 | ||
|
|
012502a2d1 | ||
|
|
cc3da031e2 | ||
|
|
a377da9028 | ||
|
|
7c9cd8aa1e | ||
|
|
7a890e543a | ||
|
|
dbb41c12a2 | ||
|
|
c5ff86667f | ||
|
|
bce5e29c49 | ||
|
|
1d3b1c1ae5 | ||
|
|
1b99ab3098 | ||
|
|
bf8f19b334 | ||
|
|
de72530022 | ||
|
|
c8b81b066b | ||
|
|
f5a961b27c | ||
|
|
bb37f7b263 | ||
|
|
71214b59d8 | ||
|
|
42534e1df4 | ||
|
|
c95ad8dd4f | ||
|
|
6bb026621d | ||
|
|
24fea5fbe0 | ||
|
|
1ce36539aa | ||
|
|
bbf74f218f | ||
|
|
33ca4e0e80 | ||
|
|
6a55960d4a | ||
|
|
8285f5df15 | ||
|
|
8612f307b3 | ||
|
|
c453455ad1 | ||
|
|
9903e47896 | ||
|
|
a246989551 | ||
|
|
ff69ef7317 | ||
|
|
a0cd1f8bcc | ||
|
|
5dfc4f35a6 | ||
|
|
43b5a435ec | ||
|
|
818f45916e | ||
|
|
b94e87584b | ||
|
|
763d8d37df | ||
|
|
3043ee5058 | ||
|
|
484c32fae3 | ||
|
|
4657e5c713 | ||
|
|
e289106c5b | ||
|
|
6afa1fb556 | ||
|
|
54d578676b | ||
|
|
e44e9fc025 | ||
|
|
fe8fc570f6 | ||
|
|
000f17c2dd | ||
|
|
866de4ae2d | ||
|
|
7d5493ac78 | ||
|
|
b6f4006c15 | ||
|
|
3fd398879e | ||
|
|
36f086c5cb | ||
|
|
c99c63cd79 | ||
|
|
941d4219f0 | ||
|
|
cadb246eaa | ||
|
|
efcf10c00f | ||
|
|
33b73451b7 | ||
|
|
2a28e05a77 | ||
|
|
6c87001a0e | ||
|
|
1d2e1a2823 | ||
|
|
dbf515ab5a | ||
|
|
42e83de5b0 | ||
|
|
38d1b0eae3 | ||
|
|
22fecf0d84 | ||
|
|
cdd8d85815 | ||
|
|
1c7e5df91a | ||
|
|
94159ca9df | ||
|
|
0e40f8a10f | ||
|
|
759ed43745 | ||
|
|
d70208a0a8 | ||
|
|
d5c6e8e8c8 | ||
|
|
d692eac260 | ||
|
|
d8cb03525f | ||
|
|
9e8a943ec7 | ||
|
|
ca4a766b82 | ||
|
|
6aed01de0c | ||
|
|
5f250e5b3a | ||
|
|
2120734f67 | ||
|
|
89e5f18dfb | ||
|
|
03bc4bf224 | ||
|
|
60b25760fb | ||
|
|
d6391be4db | ||
|
|
aab70bf1f1 | ||
|
|
00954aa16f | ||
|
|
82a1ad891d | ||
|
|
f424a832f4 | ||
|
|
01ee3d7f8b | ||
|
|
0e721653e5 | ||
|
|
a3b0de63de | ||
|
|
4cbdbbe4bd | ||
|
|
c17380d48a | ||
|
|
6ba25bae3d | ||
|
|
d1b9e21ea5 | ||
|
|
1612f515b0 | ||
|
|
eec90a1319 | ||
|
|
6bbeb005f5 | ||
|
|
5daf970a87 | ||
|
|
e9fb43c61e | ||
|
|
2245233618 | ||
|
|
ec1ddc06fd | ||
|
|
6154690c05 | ||
|
|
850041bc1d | ||
|
|
ac15f08995 | ||
|
|
ec557205f4 | ||
|
|
1706199a0e | ||
|
|
3ade7d4445 | ||
|
|
037ffc7419 | ||
|
|
5f2bf7dfd2 | ||
|
|
ee3ccbf0c8 | ||
|
|
c1a2f75509 | ||
|
|
64d7dc5335 | ||
|
|
27cf5ecdce | ||
|
+1 |
84560953ea | ||
|
|
eaafc45f45 | ||
|
|
8d45849d0c | ||
|
|
5defe648b5 | ||
|
|
9315cb81e8 | ||
|
|
d92a6a3078 | ||
|
|
a880845d2b | ||
|
|
da94e65d20 | ||
|
|
1484849cc7 | ||
|
|
bf2b56a988 | ||
|
|
88fd2c22d3 | ||
|
|
df0d7262b4 | ||
|
|
d4c2a78db6 | ||
|
|
f46c4b5130 | ||
|
|
933661b22c | ||
|
|
235e4325bc | ||
|
|
19f7a2eda1 | ||
|
|
55982666b0 | ||
|
|
351403457e | ||
|
|
2012aab1d5 | ||
|
|
6a614a520b | ||
|
|
c61edff189 | ||
|
|
7748e0c56e | ||
|
|
5d5e75ce9c | ||
|
|
0ff81e3b53 | ||
|
|
b0563a666e | ||
|
|
f9c3b7f2f1 | ||
|
+9 |
bf7915c8a3 | ||
|
|
229fcc18b7 | ||
|
|
b93388b330 | ||
|
|
b894b13e14 | ||
|
|
ecdeedf914 | ||
|
|
dce719b5b1 | ||
|
|
f569f3a01e | ||
|
|
c9285449fc | ||
|
|
7878b9fbb9 | ||
|
|
c087e75808 | ||
|
|
e75916d562 | ||
|
|
d64ba1df8b | ||
|
|
bba296b5c6 | ||
|
|
b4a547e0ae | ||
|
|
12491fa8d5 | ||
|
|
2ff0af3c8d | ||
|
|
0f313875f7 | ||
|
|
f36af7a7de | ||
|
|
114f00b88a | ||
|
|
1aaced3027 | ||
|
|
f50cecf865 | ||
|
|
b4ef6f64be | ||
|
|
13073629bb | ||
|
|
54f15b73d3 | ||
|
|
58fad2561d | ||
|
|
cd7f17f280 | ||
|
|
ba06fe1cb2 | ||
|
|
a0c33254e7 | ||
|
|
dd90e24d02 | ||
|
|
b6e8173b24 | ||
|
|
d758df8807 | ||
|
|
14eefe3824 | ||
|
|
a5c3c4301c | ||
|
|
5adbc84e0c | ||
|
|
18d3c40fb3 | ||
|
|
2e19de5d01 | ||
|
|
7542330d61 | ||
|
|
75c2a7cb2f | ||
|
|
dad737021f | ||
|
|
fd181f18a1 | ||
|
|
2d5aa9d3bd | ||
|
|
8c66dbd342 | ||
|
|
764c90174e | ||
|
|
346830ab1d | ||
|
|
160a6fc6cf | ||
|
|
37d8e1c9a0 | ||
|
|
a9344fbf4c | ||
|
|
df27aab959 | ||
|
|
7884090551 | ||
|
|
64b0d48dbf | ||
|
|
03487019b6 | ||
|
|
3d2db95417 | ||
|
|
db189e3c26 | ||
|
|
627d3a575a | ||
|
|
45059b6fe9 | ||
|
|
b49ce6b624 | ||
|
|
1266f0ab8c | ||
|
|
a2d6fa0f74 | ||
|
|
ea08fbcac4 | ||
|
|
8b7e43bdbb | ||
|
|
d35ce1b868 | ||
|
|
7672e49148 | ||
|
|
811657c9e7 | ||
|
|
08f1e0eb32 | ||
|
|
99125d3b9c | ||
|
|
4a9e665f94 | ||
|
|
bde323ffaf | ||
|
|
56280eaf35 | ||
|
|
7c62d3629c | ||
|
|
3e61ea5cfa | ||
|
+5 |
677f56f802 | ||
|
|
fe6571c3ff | ||
|
|
9d3dda9a61 | ||
|
|
796e376fa7 | ||
|
|
f3f2542bae | ||
|
|
8e2b4acc43 | ||
|
|
090b716ec1 | ||
|
|
dd3d91c67e | ||
|
|
fa883a2780 | ||
|
|
a214372c7c | ||
|
|
50f87f373e | ||
|
|
6373e6dec8 | ||
|
|
49a12bd560 | ||
|
|
610142c1b8 | ||
|
|
ac7fdaa997 | ||
|
|
0619894560 | ||
|
|
e6f7f5530b | ||
|
|
faafdc3bf8 | ||
|
|
f0fd93bfea | ||
|
|
edb09d5a9c | ||
|
|
060ef4a427 | ||
|
|
f9bd447912 | ||
|
|
7f2f984bea | ||
|
|
1ade4f75ea | ||
|
|
b6ca77158c | ||
|
|
88c4b63eec | ||
|
|
1f02c09a41 | ||
|
|
e07ecd547b | ||
|
|
70002afc60 | ||
|
|
870a24126e | ||
|
|
2880f4942a | ||
|
|
594871ed96 | ||
|
|
94c231366a | ||
|
|
c87cdf25c5 | ||
|
|
5e190aad3c | ||
|
|
e1d4584770 | ||
|
|
af2fb1b146 | ||
|
|
a5ceddc454 | ||
|
|
64733ca3bc | ||
|
|
d51c5d5768 | ||
|
|
bb31b21060 | ||
|
|
d5a126745e | ||
|
|
88f5f825b7 | ||
|
|
f397c0c087 | ||
|
|
a988724c91 | ||
|
|
cb2a528be6 | ||
|
|
34c1091d55 | ||
|
|
d88118e8d5 | ||
|
|
30802e0c13 | ||
|
|
dffc888f9c | ||
|
|
942d1fd0af | ||
|
|
4fe29fefb4 | ||
|
|
fcf91f60fb | ||
|
|
be2d3ea124 | ||
|
|
61d26f71c1 | ||
|
|
29bd05ccfb | ||
|
|
c40ba0d3dd | ||
|
|
d5dff3c710 | ||
|
|
3ee453c01e | ||
|
|
2d70131097 | ||
|
|
9d2542eecd | ||
|
|
481baa32ba | ||
|
|
f4facee8d8 | ||
|
|
eb2ded6f65 | ||
|
|
faaed17418 | ||
|
|
ef99fd3311 | ||
|
|
563da0e052 | ||
|
|
f275cf8809 | ||
|
|
5c3de5e095 | ||
|
|
32fce96503 | ||
|
|
f214c9c47c | ||
|
|
2babfd6148 | ||
|
|
7718cf7d2c | ||
|
|
22683cae66 | ||
|
|
39c2270227 | ||
|
|
afa234a207 | ||
|
|
074084b258 | ||
|
|
65ed4c78a4 | ||
|
|
3d64ba49fc | ||
|
|
12b7358773 | ||
|
|
84ce0a9054 | ||
|
|
85738f6408 | ||
|
|
1d9debaee0 | ||
|
|
ff96d43953 | ||
|
|
9045b8d9ca | ||
|
|
d0f2ed38c1 | ||
|
|
46098f2970 | ||
|
|
fa864cc9ee | ||
|
|
54214c73aa | ||
|
|
f38f778091 | ||
|
|
b9be43b276 | ||
|
|
552df63621 | ||
|
|
cbe9e06472 | ||
|
|
bbef03edea | ||
|
|
cf7271ecfb | ||
|
|
c7402c6c3f | ||
|
|
9e3b0f0731 | ||
|
|
85514cae8d | ||
|
|
bd11b2ec58 | ||
|
|
62b4ce2be1 | ||
|
|
666948b809 | ||
|
|
0bea0217b4 | ||
|
|
4a2a309d18 | ||
|
|
2c324d9421 | ||
|
|
58c0792920 | ||
|
|
59bee66e63 | ||
|
|
7d409321e9 | ||
|
|
3bb2d27646 | ||
|
|
f11d7fe992 | ||
|
|
5d5d2f9779 | ||
|
|
e01fdb5f00 | ||
|
|
b1a8430258 | ||
|
|
ecebe163e1 | ||
|
|
2aad33a5be | ||
|
|
db1953de24 | ||
|
|
88b2469dc8 | ||
|
|
11725de09a | ||
|
|
399f9e004a | ||
|
|
ee119b2174 | ||
|
|
e44afa03f3 | ||
|
|
77d380dd6b | ||
|
|
1e1300c9d2 | ||
|
|
df527088e9 | ||
|
|
3d379b071d | ||
|
|
807f79ef5d | ||
|
|
db4997c07f | ||
|
|
70f08103aa | ||
|
|
98cb09104e | ||
|
|
b81eef6ffd | ||
|
|
127ffda762 | ||
|
|
f61b731fb3 | ||
|
|
5cd08b3f98 | ||
|
|
b862817b29 | ||
|
|
99ca54fd3c | ||
|
|
dbd1720aeb | ||
|
|
701eccb35c | ||
|
|
4b27ac63d4 | ||
|
|
d6ada922c8 | ||
|
|
532331edb6 | ||
|
|
bc8becf49c | ||
|
|
17e5c01948 | ||
|
|
381c448627 | ||
|
|
f9b0427751 | ||
|
|
313b2da703 | ||
|
|
ea6577af6d | ||
|
|
0deb1af852 | ||
|
|
cdd428f503 | ||
|
|
fc7b84d612 | ||
|
|
74d07c876e | ||
|
|
646746aa10 | ||
|
|
8b953917e1 | ||
|
|
5ae3026125 | ||
|
|
c9a56f1f02 | ||
|
|
49b4c2ae07 | ||
|
|
1312d07aa2 | ||
|
|
f3a357d832 | ||
|
|
739c062676 | ||
|
|
afc062537c | ||
|
|
fe3cb76a19 | ||
|
|
31c07771e8 | ||
|
|
3b92c744d6 | ||
|
|
ac5d755545 | ||
|
|
3ffb29bf4e | ||
|
|
3544514470 | ||
|
|
c668cccb53 | ||
|
|
d18c8fc08a | ||
|
|
715bd368f4 | ||
|
|
afb5694ed6 | ||
|
|
e4336ffcb3 | ||
|
|
3ac32a3fbd | ||
|
|
9b23a11c5b | ||
|
|
dfb533db8d | ||
|
|
17968e6d3f | ||
|
|
cb6fc9fb2e | ||
|
|
29d13fbb4f | ||
|
|
5ac093fca2 | ||
|
|
85ae6d92b9 | ||
|
|
45cd949c40 | ||
|
|
ce13efd273 | ||
|
|
2193de9853 | ||
|
|
124e43b477 | ||
|
|
fe1259a29f | ||
|
|
d605619eba | ||
|
|
e1b7165bec | ||
|
|
31fd673f71 | ||
|
|
b38952d6ca | ||
|
|
37335c413a | ||
|
|
62e70ddcc8 | ||
|
|
aea5ea3a06 | ||
|
|
8cf05f1d9f | ||
|
|
ec6cd4bbba | ||
|
|
5917cd5d98 | ||
|
|
83eb92e7ff | ||
|
|
cdd385b269 | ||
|
|
507b40a51d | ||
|
|
e8912655a5 | ||
|
|
662bb882f7 | ||
|
|
dfe4b70d91 | ||
|
|
22880df429 | ||
|
|
4b70e4e3d6 | ||
|
|
104f1fb1cc | ||
|
|
f5374e0cd9 | ||
|
|
be91b322a4 | ||
|
|
0117997fb1 | ||
|
|
04bce69ded | ||
|
|
2063e248a9 | ||
|
|
4216d63311 | ||
|
|
a6a62d92bf | ||
|
|
ec3c2ed4bb | ||
|
|
0adbc346df | ||
|
|
a04ca24def | ||
|
|
db877453a4 | ||
|
|
3b23aa7913 | ||
|
|
941272cccd | ||
|
|
a5262a6ff3 | ||
|
|
5e5f34bedc | ||
|
|
6d4cfcceba | ||
|
|
1dd3573c39 | ||
|
|
4b773f2a0e | ||
|
|
a16d2c653d | ||
|
|
2b1d445003 | ||
|
|
f30c8943d9 | ||
|
|
703027b834 | ||
|
|
89ed0b425b | ||
|
|
858b72a404 | ||
|
|
0f2d3418f9 | ||
|
|
12ee7e5d82 | ||
|
|
80b083c7fc | ||
|
|
bb83d6ee4d | ||
|
|
c7d962b20f | ||
|
|
e72f29a26e | ||
|
|
d6f309ce22 | ||
|
|
fcb7625616 | ||
|
|
8079bf50a0 | ||
|
|
fa04098c20 | ||
|
|
d800b55ac6 | ||
|
|
29838f9424 | ||
|
|
440f449e24 | ||
|
|
b803ae3300 | ||
|
|
96efc44b82 | ||
|
|
7607e8edb4 | ||
|
|
7ca192615d | ||
|
|
7ef45e5844 | ||
|
|
642efe8d40 | ||
|
|
9e50d22472 | ||
|
|
a18d54b706 | ||
|
|
196741d975 | ||
|
|
d5a6f5e352 | ||
|
|
a8ce9ae59c | ||
|
|
196ffe3ea2 | ||
|
|
80dcc8339e | ||
|
|
ba5539d9bf | ||
|
|
a260495f3d | ||
|
|
aac3c27483 | ||
|
|
a3d14c1884 | ||
|
|
7483796a14 | ||
|
|
5dcc8a9e81 | ||
|
|
6f47c07362 | ||
|
|
24101eb117 | ||
|
|
e8994165ea | ||
|
|
929575f11a | ||
|
|
3593a84f26 | ||
|
|
6ede64cb6b | ||
|
|
acbb79a7c2 | ||
|
|
abc335cc8e | ||
|
|
d03fad3e1d | ||
|
|
a77cc713c2 | ||
|
|
0fc0dc1683 | ||
|
|
49db9c5d8a | ||
|
|
6a7d810c95 | ||
|
|
f8a3f2df27 | ||
|
|
e41bd56f41 | ||
|
|
b633126b35 | ||
|
|
43147b3163 | ||
|
|
4612494923 | ||
|
|
36661ea45d | ||
|
|
c98907db49 | ||
|
|
bd87927576 | ||
|
|
1b1deb9626 | ||
|
|
c0cd747f29 | ||
|
|
a699d7bdfe | ||
|
|
83be2e1edb | ||
|
|
534409d25a | ||
|
|
cc02611007 | ||
|
|
4f83d5451e | ||
|
|
0aab717bb3 | ||
|
|
97ad39713b | ||
|
|
8b61452d56 | ||
|
|
1ef8d03e18 | ||
|
|
aa8a85b8bd | ||
|
|
77c16932f8 | ||
|
|
d4214cd78f | ||
|
|
963f173730 | ||
|
|
04f6460afb | ||
|
|
b0bbd757b5 | ||
|
|
667e3abbe1 | ||
|
|
7c6ea3a05a | ||
|
|
5cb88237f5 | ||
|
|
5ec9af9325 | ||
|
|
ecd5e20133 | ||
|
|
944fbf27b7 | ||
|
|
f9e8bb8e10 | ||
|
|
ed1c871633 | ||
|
|
4f3b483066 | ||
|
|
ca7554f88c | ||
|
|
e769282e7f | ||
|
|
3823ad8fef | ||
|
|
04a0aa5f1b | ||
|
|
eadc33d230 | ||
|
|
432dad203b | ||
|
|
8b022311c0 | ||
|
|
40481f6462 | ||
|
|
602132119b | ||
|
|
52d28105e4 | ||
|
|
051871a3cd | ||
|
|
3d84568a37 | ||
|
|
915eefdc36 | ||
|
|
ede4dd9cb0 | ||
|
|
b7f7b54f9b | ||
|
|
a13a474c23 |
+17
-15
@@ -7,7 +7,7 @@ defaults: &defaults
|
||||
working_directory: ~/build
|
||||
docker:
|
||||
# specify the version you desire here
|
||||
- image: circleci/ruby:2.7.1-node-browsers
|
||||
- image: circleci/ruby:2.7.2-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
|
||||
|
||||
@@ -30,3 +30,6 @@ exclude_patterns:
|
||||
- "**/*.md"
|
||||
- "**/*.yml"
|
||||
- "app/javascript/dashboard/i18n/locale"
|
||||
- "stories/**/*"
|
||||
- "**/*.stories.js"
|
||||
- "**/stories/"
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# pre-build stage
|
||||
ARG VARIANT=2.7
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT}
|
||||
|
||||
# Update args in docker-compose.yaml to set the UID/GID of the "vscode" user.
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
RUN if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then \
|
||||
groupmod --gid $USER_GID vscode \
|
||||
&& usermod --uid $USER_UID --gid $USER_GID vscode \
|
||||
&& chmod -R $USER_UID:$USER_GID /home/vscode; \
|
||||
fi
|
||||
|
||||
# [Option] Install Node.js
|
||||
ARG INSTALL_NODE="true"
|
||||
ARG NODE_VERSION="lts/*"
|
||||
RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
|
||||
|
||||
|
||||
# tmux is for overmind
|
||||
# TODO : install foreman in future
|
||||
# packages: postgresql-server-dev-all
|
||||
# may be postgres in same machine
|
||||
|
||||
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get -y install --no-install-recommends \
|
||||
libssl-dev \
|
||||
tar \
|
||||
tzdata \
|
||||
postgresql-client \
|
||||
yarn \
|
||||
git \
|
||||
imagemagick \
|
||||
tmux \
|
||||
zsh
|
||||
|
||||
# [Optional] Uncomment this line to install global node packages.
|
||||
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
|
||||
|
||||
|
||||
# Do the set up required for chatwoot app
|
||||
WORKDIR /workspace
|
||||
COPY . /workspace
|
||||
|
||||
# TODO: figure out installing rvm
|
||||
# RUN rvm install
|
||||
COPY Gemfile Gemfile.lock ./
|
||||
RUN gem install bundler
|
||||
RUN bundle install
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn install
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "Chatwoot Development Codespace",
|
||||
"service": "app",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/zsh"
|
||||
},
|
||||
|
||||
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"rebornix.Ruby",
|
||||
"misogi.ruby-rubocop",
|
||||
"wingrunr21.vscode-ruby"
|
||||
],
|
||||
|
||||
|
||||
// TODO: figure whether we can get all this ports work properly
|
||||
|
||||
// 3000 rails
|
||||
// 3035 webpacker
|
||||
// 5432 postgres
|
||||
// 6379 redis
|
||||
// 1025,8025 mailhog
|
||||
"forwardPorts": [5432, 6379, 1025, 8025],
|
||||
//your application may need to listen on all interfaces (0.0.0.0) not just localhost for it to be available externally. Defaults to []
|
||||
"appPort": [3000, 3035],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// #TODO: can we move logic of copy env file into dockerfile ?
|
||||
"postCreateCommand": "cp .env.example .env",
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
# https://github.com/microsoft/vscode-dev-containers/blob/master/containers/python-3-postgres/.devcontainer/docker-compose.yml
|
||||
# https://github.com/microsoft/vscode-dev-containers/blob/master/containers/ruby-rails/.devcontainer/devcontainer.json
|
||||
#
|
||||
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: .devcontainer/Dockerfile
|
||||
args:
|
||||
# Update 'VARIANT' to pick a Ruby version: 2, 2.7, 2.6, 2.5
|
||||
VARIANT: 2.7
|
||||
# [Choice] Install Node.js
|
||||
INSTALL_NODE: "true"
|
||||
NODE_VERSION: "lts/*"
|
||||
# On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000.
|
||||
USER_UID: 1000
|
||||
USER_GID: 1000
|
||||
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: sleep infinity
|
||||
|
||||
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
|
||||
network_mode: service:db
|
||||
|
||||
db:
|
||||
image: postgres:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
redis:
|
||||
image: redis:latest
|
||||
restart: unless-stopped
|
||||
network_mode: service:db
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
|
||||
mailhog:
|
||||
restart: unless-stopped
|
||||
image: mailhog/mailhog
|
||||
network_mode: service:db
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
|
||||
+58
-21
@@ -4,6 +4,13 @@ SECRET_KEY_BASE=replace_with_lengthy_secure_hex
|
||||
# Replace with the URL you are planning to use for your app
|
||||
FRONTEND_URL=http://0.0.0.0:3000
|
||||
|
||||
# If the variable is set, all non-authenticated pages would fallback to the default locale.
|
||||
# Whenever a new account is created, the default language will be DEFAULT_LOCALE instead of en
|
||||
# DEFAULT_LOCALE=en
|
||||
|
||||
# If you plan to use CDN for your assets, set Asset CDN Host
|
||||
ASSET_CDN_HOST=
|
||||
|
||||
# Force all access to the app over SSL, default is set to false
|
||||
FORCE_SSL=false
|
||||
|
||||
@@ -11,7 +18,7 @@ FORCE_SSL=false
|
||||
# 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=true
|
||||
ENABLE_ACCOUNT_SIGNUP=false
|
||||
|
||||
# Redis config
|
||||
REDIS_URL=redis://redis:6379
|
||||
@@ -19,6 +26,11 @@ REDIS_URL=redis://redis:6379
|
||||
# which will be the password for the redis service running inside the docker-compose
|
||||
# to make it secure
|
||||
REDIS_PASSWORD=
|
||||
# Redis Sentinel can be used by passing list of sentinel host and ports e,g. sentinel_host1:port1,sentinel_host2:port2
|
||||
REDIS_SENTINELS=
|
||||
# Redis sentinel master name is required when using sentinel, default value is "mymaster".
|
||||
# You can find list of master using "SENTINEL masters" command
|
||||
REDIS_SENTINEL_MASTER_NAME=
|
||||
|
||||
# Postgres Database config variables
|
||||
POSTGRES_HOST=postgres
|
||||
@@ -27,27 +39,35 @@ POSTGRES_PASSWORD=
|
||||
RAILS_ENV=development
|
||||
RAILS_MAX_THREADS=5
|
||||
|
||||
# Mail outgoing
|
||||
MAILER_SENDER_EMAIL=accounts@chatwoot.com
|
||||
SMTP_PORT=1025
|
||||
# The email from which all outgoing emails are sent
|
||||
# could user either `email@yourdomain.com` or `BrandName <email@yourdomain.com>`
|
||||
MAILER_SENDER_EMAIL=Chatwoot <accounts@chatwoot.com>
|
||||
|
||||
|
||||
#SMTP domain key is set up for HELO checking
|
||||
SMTP_DOMAIN=chatwoot.com
|
||||
# if you are running docker-compose, set SMTP_ADDRESS value as "mailhog",
|
||||
# else set the value as "localhost"
|
||||
# the default value is set "mailhog" and is used by docker-compose for development environments,
|
||||
# Set the value as "localhost" or your SMTP address in other environments
|
||||
SMTP_ADDRESS=mailhog
|
||||
SMTP_PORT=1025
|
||||
SMTP_USERNAME=
|
||||
SMTP_PASSWORD=
|
||||
# plain,login,cram_md5
|
||||
SMTP_AUTHENTICATION=
|
||||
SMTP_ENABLE_STARTTLS_AUTO=
|
||||
SMTP_ENABLE_STARTTLS_AUTO=true
|
||||
# Can be: 'none', 'peer', 'client_once', 'fail_if_no_peer_cert', see http://api.rubyonrails.org/classes/ActionMailer/Base.html
|
||||
SMTP_OPENSSL_VERIFY_MODE=peer
|
||||
|
||||
# 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
|
||||
# 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
|
||||
@@ -59,6 +79,7 @@ MANDRILL_INGRESS_API_KEY=
|
||||
ACTIVE_STORAGE_SERVICE=local
|
||||
|
||||
# Amazon S3
|
||||
# documentation: https://www.chatwoot.com/docs/configuring-s3-bucket-as-cloud-storage
|
||||
S3_BUCKET_NAME=
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
@@ -74,38 +95,54 @@ LOG_LEVEL=info
|
||||
LOG_SIZE=500
|
||||
|
||||
### This environment variables are only required if you are setting up social media channels
|
||||
#facebook
|
||||
|
||||
# Facebook
|
||||
# documentation: https://www.chatwoot.com/docs/facebook-setup
|
||||
FB_VERIFY_TOKEN=
|
||||
FB_APP_SECRET=
|
||||
FB_APP_ID=
|
||||
|
||||
# Twitter
|
||||
# documentation: https://www.chatwoot.com/docs/twitter-app-setup
|
||||
TWITTER_APP_ID=
|
||||
TWITTER_CONSUMER_KEY=
|
||||
TWITTER_CONSUMER_SECRET=
|
||||
TWITTER_ENVIRONMENT=
|
||||
|
||||
#slack integration
|
||||
SLACK_CLIENT_ID=
|
||||
SLACK_CLIENT_SECRET=
|
||||
|
||||
### Change this env variable only if you are using a custom build mobile app
|
||||
## Mobile app env variables
|
||||
IOS_APP_ID=6C953F3RX2.com.chatwoot.app
|
||||
|
||||
#### This environment variables are only required in hosted version which has billing
|
||||
ENABLE_BILLING=
|
||||
|
||||
## chargebee settings
|
||||
CHARGEBEE_API_KEY=
|
||||
CHARGEBEE_SITE=
|
||||
CHARGEBEE_WEBHOOK_USERNAME=
|
||||
CHARGEBEE_WEBHOOK_PASSWORD=
|
||||
### Smart App Banner
|
||||
# https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html
|
||||
# You can find your app-id in https://itunesconnect.apple.com
|
||||
#IOS_APP_IDENTIFIER=1495796682
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
|
||||
## IP look up configuration
|
||||
## ref https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md
|
||||
## works only on accounts with ip look up feature enabled
|
||||
# IP_LOOKUP_SERVICE=geoip2
|
||||
# maxmindb api key to use geoip2 service
|
||||
# IP_LOOKUP_API_KEY=
|
||||
|
||||
## Development Only Config
|
||||
# if you want to use letter_opener for local emails
|
||||
# LETTER_OPENER=true
|
||||
|
||||
@@ -46,5 +46,6 @@ module.exports = {
|
||||
},
|
||||
globals: {
|
||||
__WEBPACK_ENV__: true,
|
||||
bus: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
open_collective: chatwoot
|
||||
github: chatwoot
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: 'Bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
@@ -18,11 +19,25 @@ Steps to reproduce the behavior:
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Browser logs**
|
||||
|
||||
Share the browser logs to debug the issue further
|
||||
|
||||
**Server logs**
|
||||
|
||||
Share the server logs to debug the issue further
|
||||
|
||||
**Environment**
|
||||
|
||||
Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self hosted installation of Chatwoot. If you are using a self hosted installation of Chatwoot describe the type of deployment (Docker/Linux VM installation/Heroku)
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
@@ -35,4 +50,5 @@ If applicable, add screenshots to help explain your problem.
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
|
||||
Add any other context about the problem here.
|
||||
|
||||
@@ -24,9 +24,9 @@ Please describe the tests that you ran to verify your changes. Provide instructi
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
|
||||
+9
-1
@@ -16,6 +16,7 @@
|
||||
/tmp/*
|
||||
!/log/.keep
|
||||
!/tmp/.keep
|
||||
*.mmdb
|
||||
|
||||
# Ignore Byebug command history file.
|
||||
.byebug_history
|
||||
@@ -23,7 +24,7 @@
|
||||
*.log
|
||||
# Ignore application configuration
|
||||
node_modules
|
||||
|
||||
master.key
|
||||
*.rdb
|
||||
|
||||
# Ignore env files
|
||||
@@ -55,3 +56,10 @@ node_modules
|
||||
package-lock.json
|
||||
|
||||
*.dump
|
||||
|
||||
|
||||
# cypress
|
||||
test/cypress/videos/*
|
||||
|
||||
/config/master.key
|
||||
/config/*.enc
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
|
||||
+31
-12
@@ -4,35 +4,40 @@ require:
|
||||
- rubocop-rspec
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
Lint/RaiseException:
|
||||
Enabled: true
|
||||
Lint/StructNewOverride:
|
||||
Enabled: true
|
||||
Layout/LineLength:
|
||||
Max: 150
|
||||
|
||||
Metrics/ClassLength:
|
||||
Max: 125
|
||||
Exclude:
|
||||
- 'app/models/conversation.rb'
|
||||
- 'app/mailers/conversation_reply_mailer.rb'
|
||||
- 'app/models/message.rb'
|
||||
RSpec/ExampleLength:
|
||||
Max: 25
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
Style/ExponentialNotation:
|
||||
Enabled: false
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: false
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
Style/HashEachMethods:
|
||||
Enabled: true
|
||||
Style/HashTransformKeys:
|
||||
Enabled: true
|
||||
Style/HashTransformValues:
|
||||
Enabled: true
|
||||
Style/OptionalBooleanParameter:
|
||||
Exclude:
|
||||
- 'app/services/email_templates/db_resolver_service.rb'
|
||||
- 'app/dispatchers/dispatcher.rb'
|
||||
Style/GlobalVars:
|
||||
Exclude:
|
||||
- 'config/initializers/redis.rb'
|
||||
- 'lib/redis/alfred.rb'
|
||||
- 'lib/global_config.rb'
|
||||
Style/ClassVars:
|
||||
Exclude:
|
||||
- 'app/services/email_templates/db_resolver_service.rb'
|
||||
Lint/MissingSuper:
|
||||
Exclude:
|
||||
- 'app/drops/base_drop.rb'
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
- spec/**/*
|
||||
@@ -45,6 +50,7 @@ Rails/ApplicationController:
|
||||
- 'app/controllers/dashboard_controller.rb'
|
||||
- 'app/controllers/widget_tests_controller.rb'
|
||||
- 'app/controllers/widgets_controller.rb'
|
||||
- 'app/controllers/platform_controller.rb'
|
||||
Style/ClassAndModuleChildren:
|
||||
EnforcedStyle: compact
|
||||
Exclude:
|
||||
@@ -65,12 +71,14 @@ Style/GuardClause:
|
||||
- 'app/builders/account_builder.rb'
|
||||
- 'app/models/attachment.rb'
|
||||
- 'app/models/message.rb'
|
||||
- 'lib/webhooks/chargebee.rb'
|
||||
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
||||
Metrics/AbcSize:
|
||||
Exclude:
|
||||
- 'app/controllers/concerns/auth_helper.rb'
|
||||
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
||||
- 'db/migrate/20161123131628_devise_token_auth_create_users.rb'
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 7
|
||||
Exclude:
|
||||
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
||||
Rails/ReversibleMigration:
|
||||
@@ -93,21 +101,32 @@ Rails/BulkChangeTable:
|
||||
- 'db/migrate/20170511134418_latlong.rb'
|
||||
- 'db/migrate/20191027054756_create_contact_inboxes.rb'
|
||||
- 'db/migrate/20191130164019_add_template_type_to_messages.rb'
|
||||
- 'db/migrate/20210425093724_convert_integration_hook_settings_field.rb'
|
||||
Rails/UniqueValidationWithoutIndex:
|
||||
Exclude:
|
||||
- 'app/models/channel/twitter_profile.rb'
|
||||
- 'app/models/webhook.rb'
|
||||
- 'app/models/contact.rb'
|
||||
Rails/RenderInline:
|
||||
Exclude:
|
||||
- 'app/controllers/swagger_controller.rb'
|
||||
RSpec/NamedSubject:
|
||||
Enabled: false
|
||||
# we should bring this down
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 12
|
||||
AllCops:
|
||||
NewCops: enable
|
||||
Exclude:
|
||||
- 'bin/**/*'
|
||||
- 'db/schema.rb'
|
||||
- 'config/**/*'
|
||||
- 'public/**/*'
|
||||
- 'config/initializers/bot.rb'
|
||||
- 'vendor/**/*'
|
||||
- 'node_modules/**/*'
|
||||
- 'lib/tasks/auto_annotate_models.rake'
|
||||
- 'config/environments/**/*'
|
||||
- 'tmp/**/*'
|
||||
- 'storage/**/*'
|
||||
- 'db/migrate/20200225162150_init_schema.rb'
|
||||
- 'config/initializers/azure_storage_service_patch.rb'
|
||||
|
||||
@@ -6,13 +6,6 @@
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: native, lf, crlf
|
||||
Layout/EndOfLine:
|
||||
Exclude:
|
||||
- 'deploy/after_restart.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Lint/DuplicateMethods:
|
||||
Exclude:
|
||||
@@ -88,7 +81,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 +179,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 +217,6 @@ Rails/Output:
|
||||
Rails/TimeZone:
|
||||
Exclude:
|
||||
- 'app/builders/report_builder.rb'
|
||||
- 'app/models/subscription.rb'
|
||||
- 'lib/reports/update_account_identity.rb'
|
||||
- 'lib/reports/update_agent_identity.rb'
|
||||
- 'lib/reports/update_identity.rb'
|
||||
@@ -269,24 +259,6 @@ Style/CommentedKeyword:
|
||||
- 'app/controllers/api/v1/conversations/labels_controller.rb'
|
||||
- 'app/controllers/api/v1/labels_controller.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: annotated, template, unannotated
|
||||
Style/FormatStringToken:
|
||||
Exclude:
|
||||
- 'lib/constants/redis_keys.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# Configuration parameters: AllowedVariables.
|
||||
Style/GlobalVars:
|
||||
Exclude:
|
||||
- 'lib/redis/alfred.rb'
|
||||
|
||||
# Offense count: 4
|
||||
Style/IdenticalConditionalBranches:
|
||||
Exclude:
|
||||
- 'app/controllers/api/v1/reports_controller.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: AllowIfModifier.
|
||||
Style/IfInsideElse:
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.7.1
|
||||
2.7.2
|
||||
|
||||
+2
-1
@@ -51,7 +51,7 @@ linters:
|
||||
|
||||
ElsePlacement:
|
||||
enabled: true
|
||||
style: same_line # or 'new_line'
|
||||
style: new_line
|
||||
|
||||
EmptyLineBetweenBlocks:
|
||||
enabled: true
|
||||
@@ -282,3 +282,4 @@ exclude:
|
||||
- 'app/javascript/widget/assets/scss/_reset.scss'
|
||||
- 'app/javascript/widget/assets/scss/sdk.css'
|
||||
- 'app/assets/stylesheets/administrate/reset/_normalize.scss'
|
||||
- 'app/javascript/shared/assets/stylesheets/*.scss'
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
const path = require('path');
|
||||
const resolve = require('../config/webpack/resolve');
|
||||
|
||||
// Chatwoot's webpack.config.js
|
||||
process.env.NODE_ENV = 'development';
|
||||
const custom = require('../config/webpack/environment');
|
||||
|
||||
module.exports = {
|
||||
stories: [
|
||||
'../stories/**/*.stories.mdx',
|
||||
'../app/javascript/**/*.stories.@(js|jsx|ts|tsx)',
|
||||
],
|
||||
addons: [
|
||||
{
|
||||
name: '@storybook/addon-docs',
|
||||
options: {
|
||||
vueDocgenOptions: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '../'),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
],
|
||||
webpackFinal: config => {
|
||||
const newConfig = {
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
modules: custom.resolvedModules.map(i => i.value),
|
||||
},
|
||||
};
|
||||
|
||||
newConfig.module.rules.push({
|
||||
test: /\.scss$/,
|
||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||
include: path.resolve(__dirname, '../app/javascript'),
|
||||
});
|
||||
|
||||
return newConfig;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import { addDecorator } from '@storybook/vue';
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import VueI18n from 'vue-i18n';
|
||||
|
||||
import WootUiKit from '../app/javascript/dashboard/components';
|
||||
import i18n from '../app/javascript/dashboard/i18n';
|
||||
|
||||
import '../app/javascript/dashboard/assets/scss/storybook.scss';
|
||||
|
||||
Vue.use(VueI18n);
|
||||
Vue.use(WootUiKit);
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({});
|
||||
const i18nConfig = new VueI18n({
|
||||
locale: 'en',
|
||||
messages: i18n,
|
||||
});
|
||||
|
||||
addDecorator(() => ({
|
||||
template: '<story/>',
|
||||
i18n: i18nConfig,
|
||||
store,
|
||||
beforeCreate: function() {
|
||||
this.$root._i18n = this.$i18n;
|
||||
},
|
||||
}));
|
||||
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '2.7.1'
|
||||
ruby '2.7.2'
|
||||
|
||||
##-- 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'
|
||||
@@ -23,6 +23,16 @@ 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 safely
|
||||
gem 'liquid'
|
||||
# Parse Markdown to HTML
|
||||
gem 'commonmarker'
|
||||
# Validate Data against JSON Schema
|
||||
gem 'json_schemer'
|
||||
|
||||
##-- for active storage --##
|
||||
gem 'aws-sdk-s3', require: false
|
||||
@@ -36,12 +46,16 @@ gem 'pg'
|
||||
gem 'redis'
|
||||
gem 'redis-namespace'
|
||||
gem 'redis-rack-cache'
|
||||
# super fast record imports in bulk
|
||||
gem 'activerecord-import'
|
||||
|
||||
##--- gems for server & infra configuration ---##
|
||||
gem 'dotenv-rails'
|
||||
gem 'foreman'
|
||||
gem 'puma'
|
||||
gem 'webpacker'
|
||||
gem 'webpacker', '~> 5.x'
|
||||
# metrics on heroku
|
||||
gem 'barnes'
|
||||
|
||||
##--- gems for authentication & authorization ---##
|
||||
gem 'devise'
|
||||
@@ -56,11 +70,9 @@ gem 'administrate'
|
||||
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
||||
gem 'wisper', '2.0.0'
|
||||
|
||||
##--- gems for billing ---##
|
||||
gem 'chargebee'
|
||||
|
||||
##--- gems for channels ---##
|
||||
gem 'facebook-messenger'
|
||||
# TODO: bump up gem to 2.0
|
||||
gem 'facebook-messenger', '1.5.0'
|
||||
gem 'telegram-bot-ruby'
|
||||
gem 'twilio-ruby', '~> 5.32.0'
|
||||
# twitty will handle subscription of twitter account events
|
||||
@@ -68,8 +80,10 @@ gem 'twilio-ruby', '~> 5.32.0'
|
||||
gem 'twitty'
|
||||
# facebook client
|
||||
gem 'koala'
|
||||
# Random name generator
|
||||
gem 'haikunator'
|
||||
# slack client
|
||||
gem 'slack-ruby-client'
|
||||
# for dialogflow integrations
|
||||
gem 'google-cloud-dialogflow'
|
||||
|
||||
##--- gems for debugging and error reporting ---##
|
||||
# static analysis
|
||||
@@ -79,13 +93,24 @@ gem 'sentry-raven'
|
||||
|
||||
##-- background job processing --##
|
||||
gem 'sidekiq'
|
||||
|
||||
##-- used for single column multiple binary flags in notification settings/feature flagging --##
|
||||
gem 'flag_shih_tzu'
|
||||
# We want cron jobs
|
||||
gem 'sidekiq-cron'
|
||||
|
||||
##-- Push notification service --##
|
||||
gem 'fcm'
|
||||
gem 'webpush'
|
||||
|
||||
##-- geocoding / parse location from ip --##
|
||||
# http://www.rubygeocoder.com/
|
||||
gem 'geocoder'
|
||||
# to parse maxmind db
|
||||
gem 'maxminddb'
|
||||
|
||||
# to create db triggers
|
||||
gem 'hairtrigger'
|
||||
|
||||
gem 'procore-sift'
|
||||
|
||||
group :development do
|
||||
gem 'annotate'
|
||||
gem 'bullet'
|
||||
@@ -93,7 +118,17 @@ group :development do
|
||||
gem 'web-console'
|
||||
|
||||
# used in swagger build
|
||||
gem 'json_refs', git: 'https://github.com/tzmfreedom/json_refs', ref: 'e32deb0'
|
||||
gem 'json_refs', git: 'https://github.com/tzmfreedom/json_refs', ref: '131b11294fd6af9c428171f38516e6222a58c874'
|
||||
|
||||
# When we want to squash migrations
|
||||
gem 'squasher'
|
||||
end
|
||||
|
||||
group :test do
|
||||
# Cypress in rails.
|
||||
gem 'cypress-on-rails', '~> 1.0'
|
||||
# fast cleaning of database
|
||||
gem 'database_cleaner'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
@@ -117,4 +152,5 @@ group :development, :test do
|
||||
gem 'simplecov', '0.17.1', require: false
|
||||
gem 'spring'
|
||||
gem 'spring-watcher-listen'
|
||||
gem 'webmock'
|
||||
end
|
||||
|
||||
+282
-180
@@ -7,10 +7,10 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: https://github.com/tzmfreedom/json_refs
|
||||
revision: e32deb073ce9aef39bdd63556bffd7fe7c2a803d
|
||||
ref: e32deb0
|
||||
revision: 131b11294fd6af9c428171f38516e6222a58c874
|
||||
ref: 131b11294fd6af9c428171f38516e6222a58c874
|
||||
specs:
|
||||
json_refs (0.1.2)
|
||||
json_refs (0.1.6)
|
||||
hana
|
||||
|
||||
GEM
|
||||
@@ -18,56 +18,58 @@ GEM
|
||||
specs:
|
||||
action-cable-testing (0.6.1)
|
||||
actioncable (>= 5.0)
|
||||
actioncable (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
actioncable (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
activejob (= 6.0.3.1)
|
||||
activerecord (= 6.0.3.1)
|
||||
activestorage (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
actionmailbox (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
activejob (= 6.0.3.7)
|
||||
activerecord (= 6.0.3.7)
|
||||
activestorage (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
actionview (= 6.0.3.1)
|
||||
activejob (= 6.0.3.1)
|
||||
actionmailer (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
actionview (= 6.0.3.7)
|
||||
activejob (= 6.0.3.7)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.0.3.1)
|
||||
actionview (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
actionpack (6.0.3.7)
|
||||
actionview (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
rack (~> 2.0, >= 2.0.8)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
activerecord (= 6.0.3.1)
|
||||
activestorage (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
actiontext (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
activerecord (= 6.0.3.7)
|
||||
activestorage (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
actionview (6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
activejob (6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
activejob (6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
activerecord (6.0.3.1)
|
||||
activemodel (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
activestorage (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
activejob (= 6.0.3.1)
|
||||
activerecord (= 6.0.3.1)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (6.0.3.1)
|
||||
activemodel (6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
activerecord (6.0.3.7)
|
||||
activemodel (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
activerecord-import (1.0.7)
|
||||
activerecord (>= 3.2)
|
||||
activestorage (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
activejob (= 6.0.3.7)
|
||||
activerecord (= 6.0.3.7)
|
||||
marcel (~> 1.0.0)
|
||||
activesupport (6.0.3.7)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
@@ -77,11 +79,10 @@ GEM
|
||||
activerecord (>= 5.0, < 6.1)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
administrate (0.13.0)
|
||||
actionpack (>= 4.2)
|
||||
actionview (>= 4.2)
|
||||
activerecord (>= 4.2)
|
||||
autoprefixer-rails (>= 6.0)
|
||||
administrate (0.16.0)
|
||||
actionpack (>= 5.0)
|
||||
actionview (>= 5.0)
|
||||
activerecord (>= 5.0)
|
||||
datetime_picker_rails (~> 0.0.7)
|
||||
jquery-rails (>= 4.0)
|
||||
kaminari (>= 1.0)
|
||||
@@ -91,110 +92,135 @@ GEM
|
||||
annotate (3.1.1)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.0)
|
||||
attr_extras (6.2.3)
|
||||
autoprefixer-rails (9.7.6)
|
||||
execjs
|
||||
ast (2.4.1)
|
||||
attr_extras (6.2.4)
|
||||
aws-eventstream (1.1.0)
|
||||
aws-partitions (1.317.0)
|
||||
aws-sdk-core (3.96.1)
|
||||
aws-partitions (1.360.0)
|
||||
aws-sdk-core (3.105.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.31.0)
|
||||
aws-sdk-core (~> 3, >= 3.71.0)
|
||||
aws-sdk-kms (1.37.0)
|
||||
aws-sdk-core (~> 3, >= 3.99.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.65.0)
|
||||
aws-sdk-core (~> 3, >= 3.96.1)
|
||||
aws-sdk-s3 (1.79.1)
|
||||
aws-sdk-core (~> 3, >= 3.104.3)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.1.3)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
aws-sigv4 (1.2.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
axiom-types (0.1.1)
|
||||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
azure-storage-blob (2.0.0)
|
||||
azure-storage-blob (2.0.1)
|
||||
azure-storage-common (~> 2.0)
|
||||
nokogiri (~> 1.10.4)
|
||||
azure-storage-common (2.0.1)
|
||||
nokogiri (~> 1.11.0.rc2)
|
||||
azure-storage-common (2.0.2)
|
||||
faraday (~> 1.0)
|
||||
faraday_middleware (~> 1.0.0.rc1)
|
||||
nokogiri (~> 1.10.4)
|
||||
bcrypt (3.1.13)
|
||||
net-http-persistent (~> 4.0)
|
||||
nokogiri (~> 1.11.0.rc2)
|
||||
barnes (0.0.8)
|
||||
multi_json (~> 1)
|
||||
statsd-ruby (~> 1.1)
|
||||
bcrypt (3.1.15)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.4.6)
|
||||
bootsnap (1.4.8)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (4.8.2)
|
||||
browser (4.1.0)
|
||||
brakeman (4.9.0)
|
||||
browser (5.3.1)
|
||||
builder (3.2.4)
|
||||
bullet (6.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
bundle-audit (0.1.0)
|
||||
bundler-audit
|
||||
bundler-audit (0.6.1)
|
||||
bundler-audit (0.7.0.1)
|
||||
bundler (>= 1.2.0, < 3)
|
||||
thor (~> 0.18)
|
||||
thor (>= 0.18, < 2)
|
||||
byebug (11.1.3)
|
||||
chargebee (2.7.5)
|
||||
json_pure (~> 2.1)
|
||||
rest-client (>= 1.8, < 3.0)
|
||||
coderay (1.1.2)
|
||||
coderay (1.1.3)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
concurrent-ruby (1.1.6)
|
||||
connection_pool (2.2.2)
|
||||
commonmarker (0.21.1)
|
||||
ruby-enum (~> 0.5)
|
||||
concurrent-ruby (1.1.8)
|
||||
connection_pool (2.2.3)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.6)
|
||||
cypress-on-rails (1.8.0)
|
||||
rack
|
||||
database_cleaner (1.8.5)
|
||||
datetime_picker_rails (0.0.7)
|
||||
momentjs-rails (>= 2.8.1)
|
||||
declarative (0.0.10)
|
||||
declarative (0.0.20)
|
||||
declarative-option (0.1.0)
|
||||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
devise (4.7.1)
|
||||
devise (4.7.2)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise_token_auth (1.1.3)
|
||||
devise_token_auth (1.1.4)
|
||||
bcrypt (~> 3.0)
|
||||
devise (> 3.5.2, < 5)
|
||||
rails (>= 4.2.0, < 6.1)
|
||||
diff-lcs (1.3)
|
||||
digest-crc (0.5.1)
|
||||
sprockets (= 3.7.2)
|
||||
diff-lcs (1.4.4)
|
||||
digest-crc (0.6.1)
|
||||
rake (~> 13.0)
|
||||
docile (1.3.2)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.5)
|
||||
dotenv-rails (2.7.5)
|
||||
dotenv (= 2.7.5)
|
||||
railties (>= 3.2, < 6.1)
|
||||
dotenv (2.7.6)
|
||||
dotenv-rails (2.7.6)
|
||||
dotenv (= 2.7.6)
|
||||
railties (>= 3.2)
|
||||
ecma-re-validator (0.2.1)
|
||||
regexp_parser (~> 1.2)
|
||||
equalizer (0.0.11)
|
||||
erubi (1.9.0)
|
||||
erubi (1.10.0)
|
||||
et-orbi (1.2.4)
|
||||
tzinfo
|
||||
execjs (2.7.0)
|
||||
facebook-messenger (1.5.0)
|
||||
httparty (~> 0.13, >= 0.13.7)
|
||||
rack (>= 1.4.5)
|
||||
factory_bot (5.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
factory_bot_rails (5.2.0)
|
||||
factory_bot (~> 5.2.0)
|
||||
railties (>= 4.2.0)
|
||||
faker (2.11.0)
|
||||
factory_bot (6.1.0)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.1.0)
|
||||
factory_bot (~> 6.1.0)
|
||||
railties (>= 5.0.0)
|
||||
faker (2.13.0)
|
||||
i18n (>= 1.6, < 2)
|
||||
faraday (1.0.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday_middleware (1.0.0)
|
||||
faraday (~> 1.0)
|
||||
ffi (1.12.2)
|
||||
fcm (1.0.2)
|
||||
faraday (~> 1.0.0)
|
||||
ffi (1.15.0)
|
||||
flag_shih_tzu (0.3.23)
|
||||
foreman (0.87.1)
|
||||
foreman (0.87.2)
|
||||
fugit (1.4.1)
|
||||
et-orbi (~> 1.1, >= 1.1.8)
|
||||
raabro (~> 1.4)
|
||||
gapic-common (0.3.4)
|
||||
google-protobuf (~> 3.12, >= 3.12.2)
|
||||
googleapis-common-protos (>= 1.3.9, < 2.0)
|
||||
googleapis-common-protos-types (>= 1.0.4, < 2.0)
|
||||
googleauth (~> 0.9)
|
||||
grpc (~> 1.25)
|
||||
geocoder (1.6.3)
|
||||
gli (2.19.2)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
google-api-client (0.39.4)
|
||||
google-api-client (0.43.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
@@ -202,40 +228,61 @@ GEM
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.12)
|
||||
google-cloud-core (1.5.0)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.3.1)
|
||||
google-cloud-dialogflow (1.2.0)
|
||||
google-cloud-core (~> 1.5)
|
||||
google-cloud-dialogflow-v2 (~> 0.1)
|
||||
google-cloud-dialogflow-v2 (0.6.4)
|
||||
gapic-common (~> 0.3)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.5.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.0.0)
|
||||
google-cloud-storage (1.26.1)
|
||||
google-cloud-errors (1.1.0)
|
||||
google-cloud-storage (1.28.0)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.33)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.12.0)
|
||||
google-protobuf (3.15.8)
|
||||
googleapis-common-protos (1.3.11)
|
||||
google-protobuf (~> 3.14)
|
||||
googleapis-common-protos-types (>= 1.0.6, < 2.0)
|
||||
grpc (~> 1.27)
|
||||
googleapis-common-protos-types (1.0.6)
|
||||
google-protobuf (~> 3.14)
|
||||
googleauth (0.16.2)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.14)
|
||||
groupdate (5.0.0)
|
||||
groupdate (5.1.0)
|
||||
activesupport (>= 5)
|
||||
grpc (1.37.1)
|
||||
google-protobuf (~> 3.15)
|
||||
googleapis-common-protos-types (~> 1.0)
|
||||
haikunator (1.1.0)
|
||||
hana (1.3.6)
|
||||
hairtrigger (0.2.23)
|
||||
activerecord (>= 5.0, < 7)
|
||||
ruby2ruby (~> 2.4)
|
||||
ruby_parser (~> 3.10)
|
||||
hana (1.3.7)
|
||||
hashdiff (1.0.1)
|
||||
hashie (4.1.0)
|
||||
hkdf (0.3.0)
|
||||
http-accept (1.7.0)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httparty (0.18.0)
|
||||
httparty (0.18.1)
|
||||
mime-types (~> 3.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.2)
|
||||
i18n (1.8.10)
|
||||
concurrent-ruby (~> 1.0)
|
||||
ice_nine (0.11.2)
|
||||
inflecto (0.0.2)
|
||||
@@ -246,9 +293,13 @@ GEM
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.3.0)
|
||||
json_pure (2.3.0)
|
||||
jwt (2.2.1)
|
||||
json (2.3.1)
|
||||
json_schemer (0.2.16)
|
||||
ecma-re-validator (~> 0.2)
|
||||
hana (~> 1.3)
|
||||
regexp_parser (~> 1.5)
|
||||
uri_template (~> 0.7)
|
||||
jwt (2.2.3)
|
||||
kaminari (1.2.1)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.1)
|
||||
@@ -269,108 +320,114 @@ GEM
|
||||
addressable (~> 2.7)
|
||||
letter_opener (1.7.0)
|
||||
launchy (~> 2.2)
|
||||
listen (3.2.1)
|
||||
liquid (4.0.3)
|
||||
listen (3.3.3)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
loofah (2.5.0)
|
||||
loofah (2.9.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
marcel (1.0.1)
|
||||
maxminddb (0.1.22)
|
||||
memoist (0.16.2)
|
||||
method_source (1.0.0)
|
||||
mime-types (3.3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
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.1)
|
||||
mini_mime (1.1.0)
|
||||
mini_portile2 (2.5.1)
|
||||
minitest (5.14.4)
|
||||
momentjs-rails (2.20.1)
|
||||
railties (>= 3.1)
|
||||
msgpack (1.3.3)
|
||||
multi_json (1.14.1)
|
||||
multi_json (1.15.0)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.1.1)
|
||||
net-http-persistent (4.0.0)
|
||||
connection_pool (~> 2.2)
|
||||
netrc (0.11.0)
|
||||
nio4r (2.5.2)
|
||||
nokogiri (1.10.9)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
oauth (0.5.4)
|
||||
nio4r (2.5.7)
|
||||
nokogiri (1.11.3)
|
||||
mini_portile2 (~> 2.5.0)
|
||||
racc (~> 1.4)
|
||||
oauth (0.5.6)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.1.0)
|
||||
parallel (1.19.1)
|
||||
parser (2.7.1.2)
|
||||
ast (~> 2.4.0)
|
||||
os (1.1.1)
|
||||
parallel (1.19.2)
|
||||
parser (2.7.1.4)
|
||||
ast (~> 2.4.1)
|
||||
pg (1.2.3)
|
||||
procore-sift (0.15.0)
|
||||
rails (> 4.2.0)
|
||||
pry (0.13.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (4.0.5)
|
||||
puma (4.3.5)
|
||||
public_suffix (4.0.6)
|
||||
puma (4.3.6)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
rack (2.2.2)
|
||||
rack-cache (1.11.1)
|
||||
raabro (1.4.0)
|
||||
racc (1.5.2)
|
||||
rack (2.2.3)
|
||||
rack-cache (1.12.0)
|
||||
rack (>= 0.4)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-protection (2.0.8.1)
|
||||
rack
|
||||
rack-proxy (0.6.5)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.0.3.1)
|
||||
actioncable (= 6.0.3.1)
|
||||
actionmailbox (= 6.0.3.1)
|
||||
actionmailer (= 6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
actiontext (= 6.0.3.1)
|
||||
actionview (= 6.0.3.1)
|
||||
activejob (= 6.0.3.1)
|
||||
activemodel (= 6.0.3.1)
|
||||
activerecord (= 6.0.3.1)
|
||||
activestorage (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
rails (6.0.3.7)
|
||||
actioncable (= 6.0.3.7)
|
||||
actionmailbox (= 6.0.3.7)
|
||||
actionmailer (= 6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
actiontext (= 6.0.3.7)
|
||||
actionview (= 6.0.3.7)
|
||||
activejob (= 6.0.3.7)
|
||||
activemodel (= 6.0.3.7)
|
||||
activerecord (= 6.0.3.7)
|
||||
activestorage (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 6.0.3.1)
|
||||
railties (= 6.0.3.7)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.3.0)
|
||||
loofah (~> 2.3)
|
||||
railties (6.0.3.1)
|
||||
actionpack (= 6.0.3.1)
|
||||
activesupport (= 6.0.3.1)
|
||||
railties (6.0.3.7)
|
||||
actionpack (= 6.0.3.7)
|
||||
activesupport (= 6.0.3.7)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
rake (13.0.1)
|
||||
rake (13.0.3)
|
||||
rb-fsevent (0.10.4)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
redis (4.1.4)
|
||||
redis-namespace (1.7.0)
|
||||
redis (4.2.1)
|
||||
redis-namespace (1.8.0)
|
||||
redis (>= 3.0.4)
|
||||
redis-rack-cache (2.2.1)
|
||||
rack-cache (>= 1.10, < 2)
|
||||
redis-store (>= 1.6, < 2)
|
||||
redis-store (1.8.2)
|
||||
redis-store (1.9.0)
|
||||
redis (>= 4, < 5)
|
||||
regexp_parser (1.7.1)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
responders (3.0.0)
|
||||
responders (3.0.1)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rest-client (2.1.0)
|
||||
@@ -379,7 +436,7 @@ GEM
|
||||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.4)
|
||||
rexml (3.2.5)
|
||||
rspec-core (3.9.2)
|
||||
rspec-support (~> 3.9.3)
|
||||
rspec-expectations (3.9.2)
|
||||
@@ -397,28 +454,41 @@ GEM
|
||||
rspec-mocks (~> 3.9)
|
||||
rspec-support (~> 3.9)
|
||||
rspec-support (3.9.3)
|
||||
rubocop (0.83.0)
|
||||
rubocop (0.89.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.7.0.1)
|
||||
parser (>= 2.7.1.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.7)
|
||||
rexml
|
||||
rubocop-ast (>= 0.3.0, < 1.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-performance (1.5.2)
|
||||
rubocop (>= 0.71.0)
|
||||
rubocop-rails (2.5.2)
|
||||
activesupport
|
||||
rubocop-ast (0.3.0)
|
||||
parser (>= 2.7.1.4)
|
||||
rubocop-performance (1.7.1)
|
||||
rubocop (>= 0.82.0)
|
||||
rubocop-rails (2.8.1)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
rubocop-rspec (1.39.0)
|
||||
rubocop (>= 0.68.1)
|
||||
rubocop (>= 0.87.0)
|
||||
rubocop-rspec (1.43.2)
|
||||
rubocop (~> 0.87)
|
||||
ruby-enum (0.9.0)
|
||||
i18n
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby2ruby (2.4.4)
|
||||
ruby_parser (~> 3.1)
|
||||
sexp_processor (~> 4.6)
|
||||
ruby_parser (3.15.0)
|
||||
rubocop (>= 0.87.0)
|
||||
sexp_processor (~> 4.9)
|
||||
safe_yaml (1.0.5)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sassc (2.3.0)
|
||||
sassc (2.4.0)
|
||||
ffi (~> 1.9)
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
@@ -426,7 +496,7 @@ GEM
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
scout_apm (2.6.7)
|
||||
scout_apm (2.6.9)
|
||||
parser
|
||||
scss_lint (0.59.0)
|
||||
sass (~> 3.5, >= 3.5.5)
|
||||
@@ -435,16 +505,19 @@ GEM
|
||||
activesupport (>= 4)
|
||||
selectize-rails (0.12.6)
|
||||
semantic_range (2.3.0)
|
||||
sentry-raven (3.0.0)
|
||||
sentry-raven (3.0.3)
|
||||
faraday (>= 1.0)
|
||||
shoulda-matchers (4.3.0)
|
||||
sexp_processor (4.15.1)
|
||||
shoulda-matchers (4.4.1)
|
||||
activesupport (>= 4.2.0)
|
||||
sidekiq (6.0.7)
|
||||
sidekiq (6.1.1)
|
||||
connection_pool (>= 2.2.2)
|
||||
rack (~> 2.0)
|
||||
rack-protection (>= 2.0.0)
|
||||
redis (>= 4.1.0)
|
||||
signet (0.14.0)
|
||||
redis (>= 4.2.0)
|
||||
sidekiq-cron (1.2.0)
|
||||
fugit (~> 1.1)
|
||||
sidekiq (>= 4.2.1)
|
||||
signet (0.15.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
@@ -454,23 +527,31 @@ GEM
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
spring (2.1.0)
|
||||
slack-ruby-client (0.15.0)
|
||||
faraday (>= 1.0)
|
||||
faraday_middleware
|
||||
gli
|
||||
hashie
|
||||
websocket-driver
|
||||
spring (2.1.1)
|
||||
spring-watcher-listen (2.0.1)
|
||||
listen (>= 2.7, < 4.0)
|
||||
spring (>= 1.2, < 3.0)
|
||||
sprockets (4.0.0)
|
||||
sprockets (3.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.1)
|
||||
sprockets-rails (3.2.2)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
squasher (0.6.2)
|
||||
statsd-ruby (1.4.0)
|
||||
telegram-bot-ruby (0.12.0)
|
||||
faraday
|
||||
inflecto
|
||||
virtus
|
||||
telephone_number (1.4.7)
|
||||
thor (0.20.3)
|
||||
telephone_number (1.4.9)
|
||||
thor (1.1.0)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.10)
|
||||
time_diff (0.3.0)
|
||||
@@ -482,7 +563,7 @@ GEM
|
||||
nokogiri (>= 1.6, < 2.0)
|
||||
twitty (0.1.1)
|
||||
oauth
|
||||
tzinfo (1.2.7)
|
||||
tzinfo (1.2.9)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2020.1)
|
||||
tzinfo (>= 1.0.0)
|
||||
@@ -494,7 +575,8 @@ GEM
|
||||
unf_ext (0.0.7.7)
|
||||
unicode-display_width (1.7.0)
|
||||
uniform_notifier (1.13.0)
|
||||
valid_email2 (3.2.2)
|
||||
uri_template (0.7.0)
|
||||
valid_email2 (3.3.1)
|
||||
activemodel (>= 3.2)
|
||||
mail (~> 2.5)
|
||||
virtus (1.0.5)
|
||||
@@ -504,12 +586,16 @@ GEM
|
||||
equalizer (~> 0.0, >= 0.0.9)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
web-console (4.0.2)
|
||||
web-console (4.0.4)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webpacker (5.1.1)
|
||||
webmock (3.8.3)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webpacker (5.2.1)
|
||||
activesupport (>= 5.2)
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
@@ -517,52 +603,64 @@ GEM
|
||||
webpush (1.0.0)
|
||||
hkdf (~> 0.2)
|
||||
jwt (~> 2.0)
|
||||
websocket-driver (0.7.1)
|
||||
websocket-driver (0.7.3)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.4)
|
||||
websocket-extensions (0.1.5)
|
||||
wisper (2.0.0)
|
||||
zeitwerk (2.3.0)
|
||||
zeitwerk (2.4.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
action-cable-testing
|
||||
activerecord-import
|
||||
acts-as-taggable-on
|
||||
administrate
|
||||
annotate
|
||||
attr_extras
|
||||
aws-sdk-s3
|
||||
azure-storage-blob
|
||||
barnes
|
||||
bootsnap
|
||||
brakeman
|
||||
browser
|
||||
bullet
|
||||
bundle-audit
|
||||
byebug
|
||||
chargebee
|
||||
commonmarker
|
||||
cypress-on-rails (~> 1.0)
|
||||
database_cleaner
|
||||
devise
|
||||
devise_token_auth
|
||||
dotenv-rails
|
||||
facebook-messenger
|
||||
facebook-messenger (= 1.5.0)
|
||||
factory_bot_rails
|
||||
faker
|
||||
fcm
|
||||
flag_shih_tzu
|
||||
foreman
|
||||
geocoder
|
||||
google-cloud-dialogflow
|
||||
google-cloud-storage
|
||||
groupdate
|
||||
haikunator
|
||||
hairtrigger
|
||||
hashie
|
||||
jbuilder
|
||||
json_refs!
|
||||
json_schemer
|
||||
jwt
|
||||
kaminari
|
||||
koala
|
||||
letter_opener
|
||||
liquid
|
||||
listen
|
||||
maxminddb
|
||||
mini_magick
|
||||
mock_redis!
|
||||
pg
|
||||
procore-sift
|
||||
pry-rails
|
||||
puma
|
||||
pundit
|
||||
@@ -584,9 +682,12 @@ DEPENDENCIES
|
||||
sentry-raven
|
||||
shoulda-matchers
|
||||
sidekiq
|
||||
sidekiq-cron
|
||||
simplecov (= 0.17.1)
|
||||
slack-ruby-client
|
||||
spring
|
||||
spring-watcher-listen
|
||||
squasher
|
||||
telegram-bot-ruby
|
||||
telephone_number
|
||||
time_diff
|
||||
@@ -596,12 +697,13 @@ DEPENDENCIES
|
||||
uglifier
|
||||
valid_email2
|
||||
web-console
|
||||
webpacker
|
||||
webmock
|
||||
webpacker (~> 5.x)
|
||||
webpush
|
||||
wisper (= 2.0.0)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.7.1p83
|
||||
ruby 2.7.2p137
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.4
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2020 ThoughtWoot Inc.
|
||||
Copyright (c) 2017-2021 Chatwoot Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
release: bundle exec rails db:prepare
|
||||
release: bundle exec rails db:chatwoot_prepare
|
||||
web: bin/rails server -p $PORT -e $RAILS_ENV
|
||||
worker: bundle exec sidekiq -C config/sidekiq.yml
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
backend: RAILS_ENV=test bin/rails s -p 5050
|
||||
frontend: bin/webpack-dev-server
|
||||
worker: RAILS_ENV=test bundle exec sidekiq -C config/sidekiq.yml
|
||||
@@ -1,8 +1,7 @@
|
||||
<p align="center">
|
||||
<img src="https://s3.us-west-2.amazonaws.com/gh-assets.chatwoot.com/brand.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>
|
||||
<p align="center">Customer engagement suite, an open-source alternative to Intercom, Zendesk, Salesforce Service Cloud etc.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -16,51 +15,88 @@ ___
|
||||
<p align="center">
|
||||
<a href="https://codeclimate.com/github/chatwoot/chatwoot/maintainability"><img src="https://api.codeclimate.com/v1/badges/80f9e1a7c72d186289ad/maintainability" alt="Maintainability"></a>
|
||||
<img src="https://img.shields.io/circleci/build/github/chatwoot/chatwoot" alt="CircleCI Badge">
|
||||
<a href="https://hub.docker.com/r/chatwoot/chatwoot/"><img src="https://img.shields.io/docker/pulls/chatwoot/chatwoot" alt="Docker Pull Badge"></a>
|
||||
<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">
|
||||
<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>
|
||||
<a href="https://huntr.dev/bounties/disclose"><img src="https://cdn.huntr.dev/huntr_security_badge_mono.svg" alt="Huntr"></a>
|
||||
</p>
|
||||
|
||||

|
||||
<img src="https://s3.us-west-2.amazonaws.com/gh-assets.chatwoot.com/chatwoot-dashboard-assets.png" width="100%" alt="Chat dashboard"/>
|
||||
|
||||
## Background
|
||||
Chatwoot is an open-source omnichannel customer support software. The development of Chatwoot started in 2016. It failed to succeed as a business and eventually shut up shop in 2017. During 2019 #Hacktoberfest, the maintainers decided to make it open-source, instead of letting the code rust in a private repo. With a pleasant surprise, Chatwoot became a trending project on Hacker News and best of all, got lots of love from the community.
|
||||
Now, a failed project is back on track and the prospects are looking great. The team is back to working on the project and this time, we are building it in the open. Thanks to the ideas and contributions from the community.
|
||||
|
||||
Chatwoot is a customer support tool for instant messaging channels which can help businesses to provide exceptional customer support. The development of Chatwoot started in 2016 and it failed to succeed as a business and eventually shut the shop in 2017. During 2019 #Hacktoberfest, the maintainers decided to make it opensource instead of letting the code rust in a private repo. With a pleasant surprise, Chatwoot became a trending project on Hacker News and best of all, got lots of love from the community.
|
||||
---
|
||||
|
||||
Now, a failed project is back on track and the prospects are looking great. The team is back to working on the project and we are building it in the open. Thanks to the ideas and contributions from the community.
|
||||
### Features
|
||||
|
||||
## Documentation
|
||||
Chatwoot gives an integrated view of conversations happening in different communication channels.
|
||||
|
||||
Detailed documentation is available at [www.chatwoot.com/docs](https://www.chatwoot.com/docs).
|
||||
It supports the following conversation channels:
|
||||
|
||||
You can find the quick setup docs [here](https://www.chatwoot.com/docs/quick-setup).
|
||||
- **Website**: Talk to your customers using our live chat widget and make use of our SDK to identify a user and provide contextual support.
|
||||
- **Facebook**: Connect your Facebook pages and start replying to the direct messages to your page.
|
||||
- **Twitter**: Connect your Twitter profiles and reply to direct messages or the tweets where you are mentioned.
|
||||
- **Whatsapp**: Connect your Whatsapp business account and manage the conversation in Chatwoot
|
||||
- **SMS**: Connect your Twilio SMS account and reply to the SMS queries in Chatwoot
|
||||
- **API Channel**: Build custom communication channels using our API channel.
|
||||
- **Email (beta)**: Forward all your email queries to Chatwoot and view it in our integrated dashboard.
|
||||
|
||||
## Branching model
|
||||
Other features include:
|
||||
|
||||
We use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. The base branch is `develop`.
|
||||
If you are looking for a stable version, please use the `master` or tags labelled as `v1.x.x`.
|
||||
- **Multi-brand inboxes**: Manage multiple brands or pages using a single dashboard.
|
||||
- **Private notes**: Inter team communication is possible using private notes in a conversation.
|
||||
- **Canned responses (Saved replies)**: Improve the response rate by adding saved replies for frequently asked questions.
|
||||
- **Conversation Labels**: Use conversation labelling to create custom workflows.
|
||||
- **Auto assignment**: Chatwoot intelligently assigns a ticket to the agents who have access to the inbox depending on their availability and load.
|
||||
- **Conversation continuity**: If the user has provided an email address through the chat widget, Chatwoot would send an email to the customer under the agent name so that the user can continue the conversation over the email.
|
||||
- **Multi-lingual support**: Chatwoot supports 10+ languages.
|
||||
- **Powerful API & Webhooks**: Extend the capability of the software using Chatwoot’s webhooks and APIs.
|
||||
- **Integrations**: Chatwoot natively integrates with Slack right now. Manage your conversations in Slack without logging into the dashboard.
|
||||
|
||||
## Heroku one-click deploy
|
||||
---
|
||||
|
||||
Deploying chatwoot to heroku, it's a breeze. It's as simple as clicking this button.
|
||||
### Documentation
|
||||
|
||||
Detailed documentation is available at [www.chatwoot.com/help-center](https://www.chatwoot.com/help-center).
|
||||
|
||||
### Translation process
|
||||
|
||||
The translation process for Chatwoot web and mobile app is managed at [https://translate.chatwoot.com](https://translate.chatwoot.com) using Crowdin. Please read the [translation guide](https://www.chatwoot.com/docs/contributing/translating-chatwoot-to-your-language) for contributing to Chatwoot.
|
||||
|
||||
---
|
||||
|
||||
### Branching model
|
||||
|
||||
We use the [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. The base branch is `develop`.
|
||||
If you are looking for a stable version, please use the `master` or tags labelled as `v1.x.x`.
|
||||
|
||||
---
|
||||
|
||||
### Deployment
|
||||
|
||||
#### Heroku one-click deploy
|
||||
|
||||
Deploying Chatwoot to Heroku is a breeze. It's as simple as clicking this button:
|
||||
|
||||
[](https://heroku.com/deploy?template=https://github.com/chatwoot/chatwoot/tree/master)
|
||||
|
||||
Follow this [link](https://www.chatwoot.com/docs/environment-variables) to understand setting the correct environment variables for the app to work with all the features. There might be breakages if you do not set the relevant environment variables. This applies to deploying the docker image as well.
|
||||
Follow this [link](https://www.chatwoot.com/docs/environment-variables) to understand setting the correct environment variables for the app to work with all the features. There might be breakages if you do not set the relevant environment variables.
|
||||
|
||||
## Docker
|
||||
#### Other deployment options
|
||||
|
||||
Follow our [docker development guide](https://www.chatwoot.com/docs/installation-guide-docker) to develop and debug the application using `docker-compose`.
|
||||
Please follow [deployment architecture guide](https://www.chatwoot.com/docs/deployment/architecture) to deploy with Docker or Caprover.
|
||||
|
||||
Follow our [environment variables](https://www.chatwoot.com/docs/environment-variables/) guide to setup environment for Docker.
|
||||
---
|
||||
|
||||
## Contributors ✨
|
||||
### Contributors ✨
|
||||
|
||||
Thanks goes to all these [wonderful people](https://www.chatwoot.com/docs/contributors):
|
||||
|
||||
<a href="https://github.com/chatwoot/chatwoot/graphs/contributors"><img src="https://opencollective.com/chatwoot/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
|
||||
*Chatwoot* © 2017-2020, ThoughtWoot Inc - Released under the MIT License.
|
||||
*Chatwoot* © 2017-2021, Chatwoot Inc - Released under the MIT License.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We use [huntr.dev](https://huntr.dev/) for security issues that affect our project. If you believe you have found a vulnerability, please disclose it via this [form](https://huntr.dev/bounties/disclose).
|
||||
This will enable us to review the vulnerability, fix it promptly, and reward you for your efforts.
|
||||
|
||||
If you have any questions about the process, feel free to reach out to hello@chatwoot.com.
|
||||
@@ -11,7 +11,7 @@
|
||||
"rails",
|
||||
"vue"
|
||||
],
|
||||
"success_url": "/app/login",
|
||||
"success_url": "/",
|
||||
"env": {
|
||||
"SECRET_TOKEN": {
|
||||
"description": "A secret key for verifying the integrity of signed cookies.",
|
||||
@@ -34,20 +34,14 @@
|
||||
"web": {
|
||||
"quantity": 1,
|
||||
"size": "FREE"
|
||||
},
|
||||
"worker": {
|
||||
"quantity": 1,
|
||||
"size": "FREE"
|
||||
}
|
||||
},
|
||||
"image": "heroku/ruby",
|
||||
"addons": [
|
||||
{
|
||||
"plan": "heroku-redis"
|
||||
},
|
||||
{
|
||||
"plan": "heroku-postgresql",
|
||||
"options": {
|
||||
"version": "9.5"
|
||||
}
|
||||
}
|
||||
],
|
||||
"addons": [ "heroku-redis", "heroku-postgresql"],
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "heroku/ruby"
|
||||
|
||||
@@ -33,7 +33,10 @@ class ContactIdentifyAction
|
||||
end
|
||||
|
||||
def update_contact
|
||||
@contact.update!(params.slice(:name, :email, :identifier))
|
||||
custom_attributes = params[:custom_attributes] ? @contact.custom_attributes.merge(params[:custom_attributes]) : @contact.custom_attributes
|
||||
# blank identifier or email will throw unique index error
|
||||
# TODO: replace reject { |_k, v| v.blank? } with compact_blank when rails is upgraded
|
||||
@contact.update!(params.slice(:name, :email, :identifier).reject { |_k, v| v.blank? }.merge({ custom_attributes: custom_attributes }))
|
||||
ContactAvatarJob.perform_later(@contact, params[:avatar_url]) if params[:avatar_url].present?
|
||||
end
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class ContactMergeAction
|
||||
end
|
||||
|
||||
def merge_messages
|
||||
Message.where(contact_id: @mergee_contact.id).update(contact_id: @base_contact.id)
|
||||
Message.where(sender: @mergee_contact).update(sender: @base_contact)
|
||||
end
|
||||
|
||||
def merge_contact_inboxes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.logo-brand {
|
||||
margin-bottom: $space-normal;
|
||||
padding: $space-normal $space-smaller;
|
||||
padding: $space-normal $space-smaller $space-small;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -70,3 +70,9 @@
|
||||
left: $space-normal;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.app-version {
|
||||
color: $color-gray;
|
||||
font-size: $font-size-small;
|
||||
padding-top: $space-smaller;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Sections
|
||||
|
||||
@@ -55,7 +55,7 @@ $color-heading: #1f2d3d;
|
||||
$color-extra-light-blue: #f5f7f9;
|
||||
|
||||
$primary-color: $color-woot;
|
||||
$secondary-color: #35c5ff;
|
||||
$secondary-color: #5d7592;
|
||||
$success-color: #44ce4b;
|
||||
$warning-color: #ffc532;
|
||||
$alert-color: #ff382d;
|
||||
|
||||
@@ -19,4 +19,10 @@ class FacebookBot
|
||||
updater.perform
|
||||
Rails.logger.info "Human was online at #{delivery.at}"
|
||||
end
|
||||
|
||||
Bot.on :message_echo do |message|
|
||||
Rails.logger.info "MESSAGE_ECHO #{message}"
|
||||
response = ::Integrations::Facebook::MessageParser.new(message)
|
||||
::Integrations::Facebook::MessageCreator.new(response).perform
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
|
||||
class AccountBuilder
|
||||
include CustomExceptions::Account
|
||||
pattr_initialize [:account_name!, :email!, :confirmed!]
|
||||
pattr_initialize [:account_name!, :email!, :confirmed!, :user, :user_full_name, :user_password]
|
||||
|
||||
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
|
||||
raise e
|
||||
end
|
||||
@@ -24,7 +26,7 @@ class AccountBuilder
|
||||
if address.valid? # && !address.disposable?
|
||||
true
|
||||
else
|
||||
raise InvalidEmail.new(valid: address.valid?) # , disposable: address.disposable?})
|
||||
raise InvalidEmail.new(valid: address.valid?)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,18 +39,12 @@ class AccountBuilder
|
||||
end
|
||||
|
||||
def create_account
|
||||
@account = Account.create!(name: @account_name)
|
||||
@account = Account.create!(name: @account_name, locale: I18n.locale)
|
||||
Current.account = @account
|
||||
end
|
||||
|
||||
def create_and_link_user
|
||||
password = Time.now.to_i
|
||||
@user = User.new(email: @email,
|
||||
password: password,
|
||||
password_confirmation: password,
|
||||
name: email_to_name(@email))
|
||||
@user.confirm if @confirmed
|
||||
if @user.save!
|
||||
if @user.present? || create_user
|
||||
link_user_to_account(@user, @account)
|
||||
@user
|
||||
else
|
||||
@@ -64,8 +60,14 @@ class AccountBuilder
|
||||
)
|
||||
end
|
||||
|
||||
def email_to_name(email)
|
||||
name = email[/[^@]+/]
|
||||
name.split('.').map(&:capitalize).join(' ')
|
||||
def create_user
|
||||
password = user_password || SecureRandom.alphanumeric(12)
|
||||
|
||||
@user = User.new(email: @email,
|
||||
password: password,
|
||||
password_confirmation: password,
|
||||
name: @user_full_name)
|
||||
@user.confirm if @confirmed
|
||||
@user.save!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
class Campaigns::CampaignConversationBuilder
|
||||
pattr_initialize [:contact_inbox_id!, :campaign_display_id!, :conversation_additional_attributes]
|
||||
|
||||
def perform
|
||||
@contact_inbox = ContactInbox.find(@contact_inbox_id)
|
||||
@campaign = @contact_inbox.inbox.campaigns.find_by!(display_id: campaign_display_id)
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
@contact_inbox.lock!
|
||||
|
||||
# We won't send campaigns if a conversation is already present
|
||||
return if @contact_inbox.reload.conversations.present?
|
||||
|
||||
@conversation = ::Conversation.create!(conversation_params)
|
||||
Messages::MessageBuilder.new(@campaign.sender, @conversation, message_params).perform
|
||||
end
|
||||
@conversation
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_params
|
||||
ActionController::Parameters.new({
|
||||
content: @campaign.message
|
||||
})
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @campaign.account_id,
|
||||
inbox_id: @contact_inbox.inbox_id,
|
||||
contact_id: @contact_inbox.contact_id,
|
||||
contact_inbox_id: @contact_inbox.id,
|
||||
campaign_id: @campaign.id,
|
||||
additional_attributes: conversation_additional_attributes
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -5,7 +5,7 @@ class ContactBuilder
|
||||
contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id)
|
||||
return contact_inbox if contact_inbox
|
||||
|
||||
build_contact
|
||||
build_contact_inbox
|
||||
end
|
||||
|
||||
private
|
||||
@@ -26,16 +26,31 @@ class ContactBuilder
|
||||
::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]
|
||||
)
|
||||
def create_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]
|
||||
)
|
||||
end
|
||||
|
||||
def find_contact
|
||||
contact = nil
|
||||
|
||||
contact = account.contacts.find_by(identifier: contact_attributes[:identifier]) if contact_attributes[:identifier].present?
|
||||
|
||||
contact ||= account.contacts.find_by(email: contact_attributes[:email]) if contact_attributes[:email].present?
|
||||
|
||||
contact ||= account.contacts.find_by(phone_number: contact_attributes[:phone_number]) if contact_attributes[:phone_number].present?
|
||||
|
||||
contact
|
||||
end
|
||||
|
||||
def build_contact_inbox
|
||||
ActiveRecord::Base.transaction do
|
||||
contact = find_contact || create_contact
|
||||
contact_inbox = create_contact_inbox(contact)
|
||||
update_contact_avatar(contact)
|
||||
contact_inbox
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
class ContactInboxBuilder
|
||||
pattr_initialize [:contact_id!, :inbox_id!, :source_id]
|
||||
|
||||
def perform
|
||||
@contact = Contact.find(contact_id)
|
||||
@inbox = @contact.account.inboxes.find(inbox_id)
|
||||
return unless ['Channel::TwilioSms', 'Channel::Email', 'Channel::Api'].include? @inbox.channel_type
|
||||
|
||||
source_id = @source_id || generate_source_id
|
||||
create_contact_inbox(source_id) if source_id.present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_source_id
|
||||
return twilio_source_id if @inbox.channel_type == 'Channel::TwilioSms'
|
||||
return @contact.email if @inbox.channel_type == 'Channel::Email'
|
||||
return SecureRandom.uuid if @inbox.channel_type == 'Channel::Api'
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def twilio_source_id
|
||||
return unless @contact.phone_number
|
||||
|
||||
case @inbox.channel.medium
|
||||
when 'sms'
|
||||
@contact.phone_number
|
||||
when 'whatsapp'
|
||||
"whatsapp:#{@contact.phone_number}"
|
||||
end
|
||||
end
|
||||
|
||||
def create_contact_inbox(source_id)
|
||||
::ContactInbox.find_or_create_by!(
|
||||
contact_id: @contact.id,
|
||||
inbox_id: @inbox.id,
|
||||
source_id: source_id
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,142 @@
|
||||
# This class creates both outgoing messages from chatwoot and echo outgoing messages based on the flag `outgoing_echo`
|
||||
# Assumptions
|
||||
# 1. Incase of an outgoing message which is echo, source_id will NOT be nil,
|
||||
# based on this we are showing "not sent from chatwoot" message in frontend
|
||||
# Hence there is no need to set user_id in message for outgoing echo messages.
|
||||
|
||||
class Messages::Facebook::MessageBuilder
|
||||
attr_reader :response
|
||||
|
||||
def initialize(response, inbox, outgoing_echo: false)
|
||||
@response = response
|
||||
@inbox = inbox
|
||||
@outgoing_echo = outgoing_echo
|
||||
@sender_id = (@outgoing_echo ? @response.recipient_id : @response.sender_id)
|
||||
@message_type = (@outgoing_echo ? :outgoing : :incoming)
|
||||
end
|
||||
|
||||
def perform
|
||||
ActiveRecord::Base.transaction do
|
||||
build_contact
|
||||
build_message
|
||||
end
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contact
|
||||
@contact ||= @inbox.contact_inboxes.find_by(source_id: @sender_id)&.contact
|
||||
end
|
||||
|
||||
def build_contact
|
||||
return if contact.present?
|
||||
|
||||
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
|
||||
ContactAvatarJob.perform_later(@contact, contact_params[:remote_avatar_url]) if contact_params[:remote_avatar_url]
|
||||
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
|
||||
end
|
||||
|
||||
def build_message
|
||||
@message = conversation.messages.create!(message_params)
|
||||
(response.attachments || []).each do |attachment|
|
||||
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
||||
attachment_obj.save!
|
||||
attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url]
|
||||
end
|
||||
end
|
||||
|
||||
def attach_file(attachment, file_url)
|
||||
file_resource = LocalResource.new(file_url)
|
||||
attachment.file.attach(io: file_resource.file, filename: file_resource.filename, content_type: file_resource.encoding)
|
||||
rescue *ExceptionList::URI_EXCEPTIONS => e
|
||||
Rails.logger.info "invalid url #{file_url} : #{e.message}"
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= Conversation.find_by(conversation_params) || build_conversation
|
||||
end
|
||||
|
||||
def build_conversation
|
||||
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: @sender_id)
|
||||
Conversation.create!(conversation_params.merge(
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
))
|
||||
end
|
||||
|
||||
def attachment_params(attachment)
|
||||
file_type = attachment['type'].to_sym
|
||||
params = { file_type: file_type, account_id: @message.account_id }
|
||||
|
||||
if [:image, :file, :audio, :video].include? file_type
|
||||
params.merge!(file_type_params(attachment))
|
||||
elsif file_type == :location
|
||||
params.merge!(location_params(attachment))
|
||||
elsif file_type == :fallback
|
||||
params.merge!(fallback_params(attachment))
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
def file_type_params(attachment)
|
||||
{
|
||||
external_url: attachment['payload']['url'],
|
||||
remote_file_url: attachment['payload']['url']
|
||||
}
|
||||
end
|
||||
|
||||
def location_params(attachment)
|
||||
lat = attachment['payload']['coordinates']['lat']
|
||||
long = attachment['payload']['coordinates']['long']
|
||||
{
|
||||
external_url: attachment['url'],
|
||||
coordinates_lat: lat,
|
||||
coordinates_long: long,
|
||||
fallback_title: attachment['title']
|
||||
}
|
||||
end
|
||||
|
||||
def fallback_params(attachment)
|
||||
{
|
||||
fallback_title: attachment['title'],
|
||||
external_url: attachment['url']
|
||||
}
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
contact_id: contact.id
|
||||
}
|
||||
end
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: @message_type,
|
||||
content: response.content,
|
||||
source_id: response.identifier,
|
||||
sender: @outgoing_echo ? nil : contact
|
||||
}
|
||||
end
|
||||
|
||||
def contact_params
|
||||
begin
|
||||
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
||||
result = k.get_object(@sender_id) || {}
|
||||
rescue StandardError => e
|
||||
result = {}
|
||||
Raven.capture_exception(e)
|
||||
end
|
||||
{
|
||||
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
||||
account_id: @inbox.account_id,
|
||||
remote_avatar_url: result['profile_pic'] || ''
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -1,2 +0,0 @@
|
||||
class Messages::IncomingMessageBuilder < Messages::MessageBuilder
|
||||
end
|
||||
@@ -1,138 +1,59 @@
|
||||
# This class creates both outgoing messages from chatwoot and echo outgoing messages based on the flag `outgoing_echo`
|
||||
# Assumptions
|
||||
# 1. Incase of an outgoing message which is echo, source_id will NOT be nil,
|
||||
# based on this we are showing "not sent from chatwoot" message in frontend
|
||||
# Hence there is no need to set user_id in message for outgoing echo messages.
|
||||
|
||||
class Messages::MessageBuilder
|
||||
attr_reader :response
|
||||
include ::FileTypeHelper
|
||||
attr_reader :message
|
||||
|
||||
def initialize(response, inbox, outgoing_echo = false)
|
||||
@response = response
|
||||
@inbox = inbox
|
||||
@sender_id = (outgoing_echo ? @response.recipient_id : @response.sender_id)
|
||||
@message_type = (outgoing_echo ? :outgoing : :incoming)
|
||||
def initialize(user, conversation, params)
|
||||
@params = params
|
||||
@private = params[:private] || false
|
||||
@conversation = conversation
|
||||
@user = user
|
||||
@message_type = params[:message_type] || 'outgoing'
|
||||
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
|
||||
@attachments = params[:attachments]
|
||||
@in_reply_to = params.to_unsafe_h&.dig(:content_attributes, :in_reply_to)
|
||||
end
|
||||
|
||||
def perform
|
||||
ActiveRecord::Base.transaction do
|
||||
build_contact
|
||||
build_message
|
||||
@message = @conversation.messages.build(message_params)
|
||||
if @attachments.present?
|
||||
@attachments.each do |uploaded_attachment|
|
||||
attachment = @message.attachments.new(
|
||||
account_id: @message.account_id,
|
||||
file_type: file_type(uploaded_attachment&.content_type)
|
||||
)
|
||||
attachment.file.attach(uploaded_attachment)
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
true
|
||||
@message.save
|
||||
@message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contact
|
||||
@contact ||= @inbox.contact_inboxes.find_by(source_id: @sender_id)&.contact
|
||||
end
|
||||
|
||||
def build_contact
|
||||
return if contact.present?
|
||||
|
||||
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
|
||||
ContactAvatarJob.perform_later(@contact, contact_params[:remote_avatar_url]) if contact_params[:remote_avatar_url]
|
||||
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
|
||||
end
|
||||
|
||||
def build_message
|
||||
@message = conversation.messages.create!(message_params)
|
||||
(response.attachments || []).each do |attachment|
|
||||
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
||||
attachment_obj.save!
|
||||
attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url]
|
||||
end
|
||||
end
|
||||
|
||||
def attach_file(attachment, file_url)
|
||||
file_resource = LocalResource.new(file_url)
|
||||
attachment.file.attach(io: file_resource.file, filename: file_resource.tmp_filename, content_type: file_resource.encoding)
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= Conversation.find_by(conversation_params) || build_conversation
|
||||
end
|
||||
|
||||
def build_conversation
|
||||
@contact_inbox ||= contact.contact_inboxes.find_by!(source_id: @sender_id)
|
||||
Conversation.create!(conversation_params.merge(
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
))
|
||||
end
|
||||
|
||||
def attachment_params(attachment)
|
||||
file_type = attachment['type'].to_sym
|
||||
params = { file_type: file_type, account_id: @message.account_id }
|
||||
|
||||
if [:image, :file, :audio, :video].include? file_type
|
||||
params.merge!(file_type_params(attachment))
|
||||
elsif file_type == :location
|
||||
params.merge!(location_params(attachment))
|
||||
elsif file_type == :fallback
|
||||
params.merge!(fallback_params(attachment))
|
||||
def message_type
|
||||
if @conversation.inbox.channel_type != 'Channel::Api' && @message_type == 'incoming'
|
||||
raise StandardError, 'Incoming messages are only allowed in Api inboxes'
|
||||
end
|
||||
|
||||
params
|
||||
@message_type
|
||||
end
|
||||
|
||||
def file_type_params(attachment)
|
||||
{
|
||||
external_url: attachment['payload']['url'],
|
||||
remote_file_url: attachment['payload']['url']
|
||||
}
|
||||
end
|
||||
|
||||
def location_params(attachment)
|
||||
lat = attachment['payload']['coordinates']['lat']
|
||||
long = attachment['payload']['coordinates']['long']
|
||||
{
|
||||
external_url: attachment['url'],
|
||||
coordinates_lat: lat,
|
||||
coordinates_long: long,
|
||||
fallback_title: attachment['title']
|
||||
}
|
||||
end
|
||||
|
||||
def fallback_params(attachment)
|
||||
{
|
||||
fallback_title: attachment['title'],
|
||||
external_url: attachment['url']
|
||||
}
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
contact_id: contact.id
|
||||
}
|
||||
def sender
|
||||
message_type == 'outgoing' ? @user : @conversation.contact
|
||||
end
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: @message_type,
|
||||
content: response.content,
|
||||
source_id: response.identifier
|
||||
}
|
||||
end
|
||||
|
||||
def contact_params
|
||||
begin
|
||||
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
||||
result = k.get_object(@sender_id) || {}
|
||||
rescue Exception => e
|
||||
result = {}
|
||||
Raven.capture_exception(e)
|
||||
end
|
||||
{
|
||||
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
||||
account_id: @inbox.account_id,
|
||||
remote_avatar_url: result['profile_pic'] || ''
|
||||
account_id: @conversation.account_id,
|
||||
inbox_id: @conversation.inbox_id,
|
||||
message_type: message_type,
|
||||
content: @params[:content],
|
||||
private: @private,
|
||||
sender: sender,
|
||||
content_type: @params[:content_type],
|
||||
items: @items,
|
||||
in_reply_to: @in_reply_to,
|
||||
echo_id: @params[:echo_id]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
class Messages::Outgoing::EchoBuilder < ::Messages::MessageBuilder
|
||||
end
|
||||
@@ -1,46 +0,0 @@
|
||||
class Messages::Outgoing::NormalBuilder
|
||||
include ::FileTypeHelper
|
||||
attr_reader :message
|
||||
|
||||
def initialize(user, conversation, params)
|
||||
@content = params[:content]
|
||||
@private = params[:private] || false
|
||||
@conversation = conversation
|
||||
@user = user
|
||||
@fb_id = params[:fb_id]
|
||||
@content_type = params[:content_type]
|
||||
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
|
||||
@attachments = params[:attachments]
|
||||
end
|
||||
|
||||
def perform
|
||||
@message = @conversation.messages.build(message_params)
|
||||
if @attachments.present?
|
||||
@attachments.each do |uploaded_attachment|
|
||||
attachment = @message.attachments.new(
|
||||
account_id: @message.account_id,
|
||||
file_type: file_type(uploaded_attachment&.content_type)
|
||||
)
|
||||
attachment.file.attach(uploaded_attachment)
|
||||
end
|
||||
end
|
||||
@message.save
|
||||
@message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: @conversation.account_id,
|
||||
inbox_id: @conversation.inbox_id,
|
||||
message_type: :outgoing,
|
||||
content: @content,
|
||||
private: @private,
|
||||
user_id: @user&.id,
|
||||
source_id: @fb_id,
|
||||
content_type: @content_type,
|
||||
items: @items
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -12,6 +12,8 @@ class NotificationSubscriptionBuilder
|
||||
|
||||
def identifier
|
||||
@identifier ||= params[:subscription_attributes][:endpoint] if params[:subscription_type] == 'browser_push'
|
||||
@identifier ||= params[:subscription_attributes][:device_id] if params[:subscription_type] == 'fcm'
|
||||
@identifier
|
||||
end
|
||||
|
||||
def identifier_subscription
|
||||
|
||||
@@ -1,10 +1,47 @@
|
||||
class RoomChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_from params[:pubsub_token]
|
||||
::OnlineStatusTracker.add_subscription(params[:pubsub_token])
|
||||
ensure_stream
|
||||
current_user
|
||||
current_account
|
||||
update_subscription
|
||||
broadcast_presence
|
||||
end
|
||||
|
||||
def unsubscribed
|
||||
::OnlineStatusTracker.remove_subscription(params[:pubsub_token])
|
||||
def update_presence
|
||||
update_subscription
|
||||
broadcast_presence
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def broadcast_presence
|
||||
data = { account_id: @current_account.id, users: ::OnlineStatusTracker.get_available_users(@current_account.id) }
|
||||
data[:contacts] = ::OnlineStatusTracker.get_available_contacts(@current_account.id) if @current_user.is_a? User
|
||||
ActionCable.server.broadcast(@pubsub_token, { event: 'presence.update', data: data })
|
||||
end
|
||||
|
||||
def ensure_stream
|
||||
@pubsub_token = params[:pubsub_token]
|
||||
stream_from @pubsub_token
|
||||
end
|
||||
|
||||
def update_subscription
|
||||
::OnlineStatusTracker.update_presence(@current_account.id, @current_user.class.name, @current_user.id)
|
||||
end
|
||||
|
||||
def current_user
|
||||
@current_user ||= if params[:user_id].blank?
|
||||
Contact.find_by!(pubsub_token: @pubsub_token)
|
||||
else
|
||||
User.find_by!(pubsub_token: @pubsub_token, id: params[:user_id])
|
||||
end
|
||||
end
|
||||
|
||||
def current_account
|
||||
@current_account ||= if @current_user.is_a? Contact
|
||||
@current_user.account
|
||||
else
|
||||
@current_user.accounts.find(params[:account_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,11 +11,9 @@ class Api::BaseController < ApplicationController
|
||||
request.headers[:api_access_token].present? || request.headers[:HTTP_API_ACCESS_TOKEN].present?
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
@conversation ||= current_account.conversations.find_by(display_id: params[:conversation_id])
|
||||
end
|
||||
def check_authorization(model = nil)
|
||||
model ||= controller_name.classify.constantize
|
||||
|
||||
def check_billing_enabled
|
||||
raise ActionController::RoutingError, 'Not Found' unless ENV['BILLING_ENABLED']
|
||||
authorize(model)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
class Api::V1::Accounts::Actions::ContactMergesController < Api::BaseController
|
||||
class Api::V1::Accounts::Actions::ContactMergesController < Api::V1::Accounts::BaseController
|
||||
before_action :set_base_contact, only: [:create]
|
||||
before_action :set_mergee_contact, only: [:create]
|
||||
|
||||
def create
|
||||
contact_merge_action = ContactMergeAction.new(
|
||||
account: current_account,
|
||||
account: Current.account,
|
||||
base_contact: @base_contact,
|
||||
mergee_contact: @mergee_contact
|
||||
)
|
||||
@@ -23,6 +23,6 @@ class Api::V1::Accounts::Actions::ContactMergesController < Api::BaseController
|
||||
end
|
||||
|
||||
def contacts
|
||||
@contacts ||= current_account.contacts
|
||||
@contacts ||= Current.account.contacts
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::AgentsController < Api::BaseController
|
||||
class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_agent, except: [:create, :index]
|
||||
before_action :check_authorization
|
||||
before_action :find_user, only: [:create]
|
||||
@@ -27,7 +27,7 @@ class Api::V1::Accounts::AgentsController < Api::BaseController
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
authorize(User)
|
||||
super(User)
|
||||
end
|
||||
|
||||
def fetch_agent
|
||||
@@ -46,7 +46,7 @@ class Api::V1::Accounts::AgentsController < Api::BaseController
|
||||
|
||||
def save_account_user
|
||||
AccountUser.create!(
|
||||
account_id: current_account.id,
|
||||
account_id: Current.account.id,
|
||||
user_id: @user.id,
|
||||
role: new_agent_params[:role],
|
||||
inviter_id: current_user.id
|
||||
@@ -64,6 +64,6 @@ class Api::V1::Accounts::AgentsController < Api::BaseController
|
||||
end
|
||||
|
||||
def agents
|
||||
@agents ||= current_account.users
|
||||
@agents ||= Current.account.users.order_by_full_name.includes({ avatar_attachment: [:blob] })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
class Api::V1::Accounts::BaseController < Api::BaseController
|
||||
include SwitchLocale
|
||||
before_action :current_account
|
||||
around_action :switch_locale_using_account_locale
|
||||
|
||||
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
|
||||
account
|
||||
end
|
||||
|
||||
def account_accessible_for_user?(account)
|
||||
@current_account_user = account.account_users.find_by(user_id: current_user.id)
|
||||
Current.account_user = @current_account_user
|
||||
render_unauthorized('You are not authorized to access this account') unless @current_account_user
|
||||
end
|
||||
|
||||
def account_accessible_for_bot?(account)
|
||||
render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
|
||||
before_action :inbox, only: [:reauthorize_page]
|
||||
|
||||
def register_facebook_page
|
||||
@@ -7,11 +7,11 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
page_id = params[:page_id]
|
||||
inbox_name = params[:inbox_name]
|
||||
ActiveRecord::Base.transaction do
|
||||
facebook_channel = current_account.facebook_pages.create!(
|
||||
facebook_channel = Current.account.facebook_pages.create!(
|
||||
page_id: page_id, user_access_token: user_access_token,
|
||||
page_access_token: page_access_token
|
||||
)
|
||||
@facebook_inbox = current_account.inboxes.create!(name: inbox_name, channel: facebook_channel)
|
||||
@facebook_inbox = Current.account.inboxes.create!(name: inbox_name, channel: facebook_channel)
|
||||
set_avatar(@facebook_inbox, page_id)
|
||||
rescue StandardError => e
|
||||
Rails.logger.info e
|
||||
@@ -22,7 +22,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
@page_details = mark_already_existing_facebook_pages(fb_object.get_connections('me', 'accounts'))
|
||||
end
|
||||
|
||||
# get params[:inbox_id], current_account, params[:omniauth_token]
|
||||
# get params[:inbox_id], current_account. params[:omniauth_token]
|
||||
def reauthorize_page
|
||||
if @inbox&.facebook?
|
||||
fb_page_id = @inbox.channel.page_id
|
||||
@@ -30,7 +30,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
|
||||
if (page_detail = (page_details || []).detect { |page| fb_page_id == page['id'] })
|
||||
update_fb_page(fb_page_id, page_detail['access_token'])
|
||||
return head :ok
|
||||
render and return
|
||||
end
|
||||
end
|
||||
|
||||
@@ -40,17 +40,17 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
private
|
||||
|
||||
def inbox
|
||||
@inbox = current_account.inboxes.find_by(id: params[:inbox_id])
|
||||
@inbox = Current.account.inboxes.find_by(id: params[:inbox_id])
|
||||
end
|
||||
|
||||
def update_fb_page(fb_page_id, access_token)
|
||||
get_fb_page(fb_page_id)&.update!(
|
||||
user_access_token: @user_access_token, page_access_token: access_token
|
||||
)
|
||||
fb_page = get_fb_page(fb_page_id)
|
||||
fb_page&.update!(user_access_token: @user_access_token, page_access_token: access_token)
|
||||
fb_page&.reauthorized!
|
||||
end
|
||||
|
||||
def get_fb_page(fb_page_id)
|
||||
current_account.facebook_pages.find_by(page_id: fb_page_id)
|
||||
Current.account.facebook_pages.find_by(page_id: fb_page_id)
|
||||
end
|
||||
|
||||
def fb_object
|
||||
@@ -69,7 +69,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
return [] if data.empty?
|
||||
|
||||
data.inject([]) do |result, page_detail|
|
||||
page_detail[:exists] = current_account.facebook_pages.exists?(page_id: page_detail['id']) ? true : false
|
||||
page_detail[:exists] = Current.account.facebook_pages.exists?(page_id: page_detail['id']) ? true : false
|
||||
result << page_detail
|
||||
end
|
||||
end
|
||||
@@ -81,6 +81,8 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
|
||||
|
||||
avatar_resource = LocalResource.new(uri)
|
||||
facebook_inbox.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
|
||||
rescue *ExceptionList::URI_EXCEPTIONS => e
|
||||
Rails.logger.info "invalid url #{file_url} : #{e.message}"
|
||||
end
|
||||
|
||||
def get_avatar_url(page_id)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
class Api::V1::Accounts::CampaignsController < Api::V1::Accounts::BaseController
|
||||
before_action :campaign, except: [:index, :create]
|
||||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
@campaigns = Current.account.campaigns
|
||||
end
|
||||
|
||||
def create
|
||||
@campaign = Current.account.campaigns.create!(campaign_params)
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
@campaign.update(campaign_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def campaign
|
||||
@campaign ||= Current.account.campaigns.find_by(display_id: params[:id])
|
||||
end
|
||||
|
||||
def campaign_params
|
||||
params.require(:campaign).permit(:title, :description, :message, :enabled, :inbox_id, :sender_id, trigger_rules: {})
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
||||
class Api::V1::Accounts::CannedResponsesController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_canned_response, only: [:update, :destroy]
|
||||
|
||||
def index
|
||||
@@ -6,7 +6,7 @@ class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
||||
end
|
||||
|
||||
def create
|
||||
@canned_response = current_account.canned_responses.new(canned_response_params)
|
||||
@canned_response = Current.account.canned_responses.new(canned_response_params)
|
||||
@canned_response.save!
|
||||
render json: @canned_response
|
||||
end
|
||||
@@ -24,7 +24,7 @@ class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
||||
private
|
||||
|
||||
def fetch_canned_response
|
||||
@canned_response = current_account.canned_responses.find(params[:id])
|
||||
@canned_response = Current.account.canned_responses.find(params[:id])
|
||||
end
|
||||
|
||||
def canned_response_params
|
||||
@@ -33,9 +33,9 @@ class Api::V1::Accounts::CannedResponsesController < Api::BaseController
|
||||
|
||||
def canned_responses
|
||||
if params[:search]
|
||||
current_account.canned_responses.where('short_code ILIKE ?', "#{params[:search]}%")
|
||||
Current.account.canned_responses.where('short_code ILIKE ?', "#{params[:search]}%")
|
||||
else
|
||||
current_account.canned_responses
|
||||
Current.account.canned_responses
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseController
|
||||
before_action :current_account
|
||||
class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts::BaseController
|
||||
before_action :authorize_request
|
||||
|
||||
def create
|
||||
@@ -38,13 +37,13 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseControlle
|
||||
end
|
||||
|
||||
def build_inbox
|
||||
@twilio_channel = current_account.twilio_sms.create!(
|
||||
@twilio_channel = Current.account.twilio_sms.create!(
|
||||
account_sid: permitted_params[:account_sid],
|
||||
auth_token: permitted_params[:auth_token],
|
||||
phone_number: phone_number,
|
||||
medium: medium
|
||||
)
|
||||
@inbox = current_account.inboxes.create(
|
||||
@inbox = Current.account.inboxes.create(
|
||||
name: permitted_params[:name],
|
||||
channel: @twilio_channel
|
||||
)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
class Api::V1::Accounts::Contacts::ContactInboxesController < Api::V1::Accounts::BaseController
|
||||
before_action :ensure_contact
|
||||
before_action :ensure_inbox, only: [:create]
|
||||
|
||||
def create
|
||||
source_id = params[:source_id] || SecureRandom.uuid
|
||||
@contact_inbox = ContactInbox.create!(contact: @contact, inbox: @inbox, source_id: source_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_inbox
|
||||
@inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||
end
|
||||
|
||||
def ensure_contact
|
||||
@contact = Current.account.contacts.find(params[:contact_id])
|
||||
end
|
||||
end
|
||||
@@ -1,17 +1,17 @@
|
||||
class Api::V1::Accounts::Contacts::ConversationsController < Api::BaseController
|
||||
class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::BaseController
|
||||
def index
|
||||
@conversations = current_account.conversations.includes(
|
||||
:assignee, :contact, :inbox
|
||||
@conversations = Current.account.conversations.includes(
|
||||
:assignee, :contact, :inbox, :taggings
|
||||
).where(inbox_id: inbox_ids, contact_id: permitted_params[:contact_id])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def inbox_ids
|
||||
if current_user.administrator?
|
||||
current_account.inboxes.pluck(:id)
|
||||
elsif current_user.agent?
|
||||
current_user.assigned_inboxes.pluck(:id)
|
||||
if Current.user.administrator?
|
||||
Current.account.inboxes.pluck(:id)
|
||||
elsif Current.user.agent?
|
||||
Current.user.assigned_inboxes.pluck(:id)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
class Api::V1::Accounts::Contacts::LabelsController < Api::V1::Accounts::BaseController
|
||||
include LabelConcern
|
||||
|
||||
private
|
||||
|
||||
def model
|
||||
@model ||= Current.account.contacts.find(permitted_params[:contact_id])
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:contact_id, labels: [])
|
||||
end
|
||||
end
|
||||
@@ -1,40 +1,118 @@
|
||||
class Api::V1::Accounts::ContactsController < Api::BaseController
|
||||
class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
include Sift
|
||||
|
||||
sort_on :email, type: :string
|
||||
sort_on :name, type: :string
|
||||
sort_on :phone_number, type: :string
|
||||
sort_on :last_activity_at, type: :datetime
|
||||
|
||||
RESULTS_PER_PAGE = 15
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
before_action :check_authorization
|
||||
before_action :fetch_contact, only: [:show, :update]
|
||||
before_action :set_current_page, only: [:index, :active, :search]
|
||||
before_action :fetch_contact, only: [:show, :update, :contactable_inboxes]
|
||||
|
||||
def index
|
||||
@contacts = current_account.contacts
|
||||
@contacts_count = resolved_contacts.count
|
||||
@contacts = fetch_contact_last_seen_at(resolved_contacts)
|
||||
end
|
||||
|
||||
def search
|
||||
render json: { error: 'Specify search string with parameter q' }, status: :unprocessable_entity if params[:q].blank? && return
|
||||
|
||||
contacts = resolved_contacts.where(
|
||||
'name ILIKE :search OR email ILIKE :search OR phone_number ILIKE :search',
|
||||
search: "%#{params[:q]}%"
|
||||
)
|
||||
@contacts_count = contacts.count
|
||||
@contacts = fetch_contact_last_seen_at(contacts)
|
||||
end
|
||||
|
||||
def import
|
||||
ActiveRecord::Base.transaction do
|
||||
import = Current.account.data_imports.create!(data_type: 'contacts')
|
||||
import.import_file.attach(params[:import_file])
|
||||
end
|
||||
head :ok
|
||||
end
|
||||
|
||||
# returns online contacts
|
||||
def active
|
||||
contacts = Current.account.contacts.where(id: ::OnlineStatusTracker
|
||||
.get_available_contact_ids(Current.account.id))
|
||||
@contacts_count = contacts.count
|
||||
@contacts = contacts.page(@current_page)
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def contactable_inboxes
|
||||
@contactable_inboxes = Contacts::ContactableInboxesService.new(contact: @contact).get
|
||||
end
|
||||
|
||||
def create
|
||||
@contact = Contact.new(contact_create_params)
|
||||
@contact.save!
|
||||
render json: @contact
|
||||
ActiveRecord::Base.transaction do
|
||||
@contact = Current.account.contacts.new(contact_params)
|
||||
@contact.save!
|
||||
@contact_inbox = build_contact_inbox
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@contact.update!(contact_params)
|
||||
@contact.assign_attributes(contact_update_params)
|
||||
@contact.save!
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
render json: {
|
||||
message: e.record.errors.full_messages.join(', '),
|
||||
contact: Current.account.contacts.find_by(email: contact_params[:email])
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
authorize(Contact)
|
||||
def resolved_contacts
|
||||
@resolved_contacts ||= Current.account.contacts
|
||||
.where.not(email: [nil, ''])
|
||||
.or(Current.account.contacts.where.not(phone_number: [nil, '']))
|
||||
end
|
||||
|
||||
def set_current_page
|
||||
@current_page = params[:page] || 1
|
||||
end
|
||||
|
||||
def fetch_contact_last_seen_at(contacts)
|
||||
filtrate(contacts).left_outer_joins(:conversations)
|
||||
.select('contacts.*, COUNT(conversations.id) as conversations_count')
|
||||
.group('contacts.id')
|
||||
.includes([{ avatar_attachment: [:blob] }, { contact_inboxes: [:inbox] }])
|
||||
.page(@current_page).per(RESULTS_PER_PAGE)
|
||||
end
|
||||
|
||||
def build_contact_inbox
|
||||
return if params[:inbox_id].blank?
|
||||
|
||||
inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||
source_id = params[:source_id] || SecureRandom.uuid
|
||||
ContactInbox.create(contact: @contact, inbox: inbox, source_id: source_id)
|
||||
end
|
||||
|
||||
def contact_params
|
||||
params.require(:contact).permit(:name, :email, :phone_number)
|
||||
params.require(:contact).permit(:name, :email, :phone_number, additional_attributes: {}, custom_attributes: {})
|
||||
end
|
||||
|
||||
def contact_custom_attributes
|
||||
return @contact.custom_attributes.merge(contact_params[:custom_attributes]) if contact_params[:custom_attributes]
|
||||
|
||||
@contact.custom_attributes
|
||||
end
|
||||
|
||||
def contact_update_params
|
||||
# we want the merged custom attributes not the original one
|
||||
contact_params.except(:custom_attributes).merge({ custom_attributes: contact_custom_attributes })
|
||||
end
|
||||
|
||||
def fetch_contact
|
||||
@contact = current_account.contacts.find(params[:id])
|
||||
end
|
||||
|
||||
def contact_create_params
|
||||
params.require(:contact).permit(:account_id, :inbox_id).merge!(name: SecureRandom.hex)
|
||||
@contact = Current.account.contacts.includes(contact_inboxes: [:inbox]).find(params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
class Api::V1::Accounts::Conversations::AssignmentsController < Api::BaseController
|
||||
before_action :set_conversation, only: [:create]
|
||||
|
||||
# assign agent to a conversation
|
||||
class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Accounts::Conversations::BaseController
|
||||
# assigns agent/team to a conversation
|
||||
def create
|
||||
set_assignee
|
||||
render json: @assignee
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_assignee
|
||||
# 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
|
||||
if params.key?(:assignee_id)
|
||||
@assignee = Current.account.users.find_by(id: params[:assignee_id])
|
||||
@conversation.update_assignee(@assignee)
|
||||
elsif params.key?(:team_id)
|
||||
@assignee = Current.account.teams.find_by(id: params[:team_id])
|
||||
@conversation.update!(team: @assignee)
|
||||
end
|
||||
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
|
||||
@@ -1,13 +1,13 @@
|
||||
class Api::V1::Accounts::Conversations::LabelsController < Api::BaseController
|
||||
before_action :set_conversation, only: [:create, :index]
|
||||
class Api::V1::Accounts::Conversations::LabelsController < Api::V1::Accounts::Conversations::BaseController
|
||||
include LabelConcern
|
||||
|
||||
def create
|
||||
@conversation.update_labels(params[:labels])
|
||||
@labels = @conversation.label_list
|
||||
private
|
||||
|
||||
def model
|
||||
@model ||= @conversation
|
||||
end
|
||||
|
||||
# all labels of the current conversation
|
||||
def index
|
||||
@labels = @conversation.label_list
|
||||
def permitted_params
|
||||
params.permit(:conversation_id, labels: [])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
class Api::V1::Accounts::Conversations::MessagesController < Api::BaseController
|
||||
before_action :set_conversation, only: [:index, :create]
|
||||
|
||||
class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::Conversations::BaseController
|
||||
def index
|
||||
@messages = message_finder.perform
|
||||
end
|
||||
|
||||
def create
|
||||
mb = Messages::Outgoing::NormalBuilder.new(current_user, @conversation, params)
|
||||
user = Current.user || @resource
|
||||
mb = Messages::MessageBuilder.new(user, @conversation, params)
|
||||
@message = mb.perform
|
||||
rescue StandardError => e
|
||||
render_could_not_create_error(e.message)
|
||||
end
|
||||
|
||||
def destroy
|
||||
ActiveRecord::Base.transaction do
|
||||
message.update!(content: I18n.t('conversations.messages.deleted'), deleted: true)
|
||||
message.attachments.destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message
|
||||
@message ||= @conversation.messages.find(permitted_params[:id])
|
||||
end
|
||||
|
||||
def message_finder
|
||||
@message_finder ||= MessageFinder.new(@conversation, params)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||
class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseController
|
||||
include Events::Types
|
||||
before_action :current_account
|
||||
|
||||
before_action :conversation, except: [:index]
|
||||
before_action :contact_inbox, only: [:create]
|
||||
|
||||
@@ -15,8 +15,17 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||
@conversations_count = result[:count]
|
||||
end
|
||||
|
||||
def search
|
||||
result = conversation_finder.perform
|
||||
@conversations = result[:conversations]
|
||||
@conversations_count = result[:count]
|
||||
end
|
||||
|
||||
def create
|
||||
@conversation = ::Conversation.create!(conversation_params)
|
||||
ActiveRecord::Base.transaction do
|
||||
@conversation = ::Conversation.create!(conversation_params)
|
||||
Messages::MessageBuilder.new(Current.user, @conversation, params[:message]).perform if params[:message].present?
|
||||
end
|
||||
end
|
||||
|
||||
def show; end
|
||||
@@ -26,6 +35,16 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def unmute
|
||||
@conversation.unmute!
|
||||
head :ok
|
||||
end
|
||||
|
||||
def transcript
|
||||
ConversationReplyMailer.conversation_transcript(@conversation, params[:email])&.deliver_later if params[:email].present?
|
||||
head :ok
|
||||
end
|
||||
|
||||
def toggle_status
|
||||
if params[:status]
|
||||
@conversation.status = params[:status]
|
||||
@@ -36,18 +55,18 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||
end
|
||||
|
||||
def toggle_typing_status
|
||||
if params[:typing_status] == 'on'
|
||||
case params[:typing_status]
|
||||
when 'on'
|
||||
trigger_typing_event(CONVERSATION_TYPING_ON)
|
||||
elsif params[:typing_status] == 'off'
|
||||
when 'off'
|
||||
trigger_typing_event(CONVERSATION_TYPING_OFF)
|
||||
end
|
||||
head :ok
|
||||
end
|
||||
|
||||
def update_last_seen
|
||||
@conversation.agent_last_seen_at = parsed_last_seen_at
|
||||
@conversation.agent_last_seen_at = DateTime.now.utc
|
||||
@conversation.save!
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
@@ -57,24 +76,34 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: user)
|
||||
end
|
||||
|
||||
def parsed_last_seen_at
|
||||
DateTime.strptime(params[:agent_last_seen_at].to_s, '%s')
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= current_account.conversations.find_by(display_id: params[:id])
|
||||
@conversation ||= Current.account.conversations.find_by(display_id: params[:id])
|
||||
end
|
||||
|
||||
def contact_inbox
|
||||
@contact_inbox = build_contact_inbox
|
||||
|
||||
@contact_inbox ||= ::ContactInbox.find_by!(source_id: params[:source_id])
|
||||
end
|
||||
|
||||
def build_contact_inbox
|
||||
return if params[:contact_id].blank? || params[:inbox_id].blank?
|
||||
|
||||
ContactInboxBuilder.new(
|
||||
contact_id: params[:contact_id],
|
||||
inbox_id: params[:inbox_id],
|
||||
source_id: params[:source_id]
|
||||
).perform
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
additional_attributes = params[:additional_attributes]&.permit! || {}
|
||||
{
|
||||
account_id: current_account.id,
|
||||
account_id: Current.account.id,
|
||||
inbox_id: @contact_inbox.inbox_id,
|
||||
contact_id: @contact_inbox.contact_id,
|
||||
contact_inbox_id: @contact_inbox.id
|
||||
contact_inbox_id: @contact_inbox.id,
|
||||
additional_attributes: additional_attributes
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::FacebookIndicatorsController < Api::BaseController
|
||||
class Api::V1::Accounts::FacebookIndicatorsController < Api::V1::Accounts::BaseController
|
||||
before_action :set_access_token
|
||||
around_action :handle_with_exception
|
||||
|
||||
@@ -38,7 +38,7 @@ class Api::V1::Accounts::FacebookIndicatorsController < Api::BaseController
|
||||
end
|
||||
|
||||
def inbox
|
||||
@inbox ||= current_account.inboxes.find(permitted_params[:inbox_id])
|
||||
@inbox ||= Current.account.inboxes.find(permitted_params[:inbox_id])
|
||||
end
|
||||
|
||||
def set_access_token
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::InboxMembersController < Api::BaseController
|
||||
class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_inbox, only: [:create, :show]
|
||||
before_action :current_agents_ids, only: [:create]
|
||||
|
||||
@@ -12,7 +12,7 @@ class Api::V1::Accounts::InboxMembersController < Api::BaseController
|
||||
end
|
||||
|
||||
def show
|
||||
@agents = current_account.users.where(id: @inbox.members.pluck(:user_id))
|
||||
@agents = Current.account.users.where(id: @inbox.members.select(:user_id))
|
||||
end
|
||||
|
||||
private
|
||||
@@ -22,9 +22,10 @@ class Api::V1::Accounts::InboxMembersController < Api::BaseController
|
||||
# get the list of user_ids from params
|
||||
# the missing ones are the agents which are to be deleted from the inbox
|
||||
# the new ones are the agents which are to be added to the inbox
|
||||
|
||||
agents_to_be_added_ids.each { |user_id| @inbox.add_member(user_id) }
|
||||
agents_to_be_removed_ids.each { |user_id| @inbox.remove_member(user_id) }
|
||||
ActiveRecord::Base.transaction do
|
||||
agents_to_be_added_ids.each { |user_id| @inbox.add_member(user_id) }
|
||||
agents_to_be_removed_ids.each { |user_id| @inbox.remove_member(user_id) }
|
||||
end
|
||||
end
|
||||
|
||||
def agents_to_be_added_ids
|
||||
@@ -40,6 +41,6 @@ class Api::V1::Accounts::InboxMembersController < Api::BaseController
|
||||
end
|
||||
|
||||
def fetch_inbox
|
||||
@inbox = current_account.inboxes.find(params[:inbox_id])
|
||||
@inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
class Api::V1::Accounts::InboxesController < Api::BaseController
|
||||
before_action :current_account
|
||||
class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_inbox, except: [:index, :create]
|
||||
before_action :fetch_agent_bot, only: [:set_agent_bot]
|
||||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
@inboxes = policy_scope(current_account.inboxes)
|
||||
@inboxes = policy_scope(Current.account.inboxes.order_by_name.includes(:channel, { avatar_attachment: [:blob] }))
|
||||
end
|
||||
|
||||
def assignable_agents
|
||||
@assignable_agents = (Current.account.users.where(id: @inbox.members.select(:user_id)) + Current.account.administrators).uniq
|
||||
end
|
||||
|
||||
def campaigns
|
||||
@campaigns = @inbox.campaigns
|
||||
end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
channel = web_widgets.create!(permitted_params[:channel].except(:type)) if permitted_params[:channel][:type] == 'web_widget'
|
||||
@inbox = current_account.inboxes.build(name: permitted_params[:name], channel: channel)
|
||||
channel = create_channel
|
||||
@inbox = Current.account.inboxes.build(
|
||||
name: permitted_params[:name],
|
||||
greeting_message: permitted_params[:greeting_message],
|
||||
greeting_enabled: permitted_params[:greeting_enabled],
|
||||
channel: channel
|
||||
)
|
||||
@inbox.avatar.attach(permitted_params[:avatar])
|
||||
@inbox.save!
|
||||
end
|
||||
@@ -19,7 +31,11 @@ class Api::V1::Accounts::InboxesController < Api::BaseController
|
||||
|
||||
def update
|
||||
@inbox.update(inbox_update_params.except(:channel))
|
||||
@inbox.channel.update!(inbox_update_params[:channel]) if @inbox.channel.is_a?(Channel::WebWidget) && inbox_update_params[:channel].present?
|
||||
@inbox.update_working_hours(params.permit(working_hours: Inbox::OFFISABLE_ATTRS)[:working_hours]) if params[:working_hours]
|
||||
return unless @inbox.channel.is_a?(Channel::WebWidget) && inbox_update_params[:channel].present?
|
||||
|
||||
@inbox.channel.update!(inbox_update_params[:channel])
|
||||
update_channel_feature_flags
|
||||
end
|
||||
|
||||
def set_agent_bot
|
||||
@@ -41,27 +57,50 @@ class Api::V1::Accounts::InboxesController < Api::BaseController
|
||||
private
|
||||
|
||||
def fetch_inbox
|
||||
@inbox = current_account.inboxes.find(params[:id])
|
||||
@inbox = Current.account.inboxes.find(params[:id])
|
||||
end
|
||||
|
||||
def fetch_agent_bot
|
||||
@agent_bot = AgentBot.find(params[:agent_bot]) if params[:agent_bot]
|
||||
end
|
||||
|
||||
def web_widgets
|
||||
current_account.web_widgets
|
||||
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 check_authorization
|
||||
authorize(Inbox)
|
||||
def update_channel_feature_flags
|
||||
return unless inbox_update_params[:channel].key? :selected_feature_flags
|
||||
|
||||
@inbox.channel.selected_feature_flags = inbox_update_params[:channel][:selected_feature_flags]
|
||||
@inbox.channel.save!
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :avatar, :name, channel: [:type, :website_url, :widget_color, :welcome_title, :welcome_tagline, :agent_away_message])
|
||||
params.permit(:id, :avatar, :name, :greeting_message, :greeting_enabled, channel:
|
||||
[:type, :website_url, :widget_color, :welcome_title, :welcome_tagline, :webhook_url, :email, :reply_time])
|
||||
end
|
||||
|
||||
def inbox_update_params
|
||||
params.permit(:enable_auto_assignment, :name, :avatar, channel: [:website_url, :widget_color, :welcome_title,
|
||||
:welcome_tagline, :agent_away_message])
|
||||
params.permit(:enable_auto_assignment, :name, :avatar, :greeting_message, :greeting_enabled,
|
||||
:working_hours_enabled, :out_of_office_message, :timezone,
|
||||
channel: [
|
||||
:website_url,
|
||||
:widget_color,
|
||||
:welcome_title,
|
||||
:welcome_tagline,
|
||||
:webhook_url,
|
||||
:email,
|
||||
:reply_time,
|
||||
:pre_chat_form_enabled,
|
||||
{ pre_chat_form_options: [:pre_chat_message, :require_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,31 @@
|
||||
class Api::V1::Accounts::Integrations::HooksController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_hook, only: [:update, :destroy]
|
||||
before_action :check_authorization
|
||||
|
||||
def create
|
||||
@hook = Current.account.hooks.create!(permitted_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@hook.update!(permitted_params.slice(:status, :settings))
|
||||
end
|
||||
|
||||
def destroy
|
||||
@hook.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_hook
|
||||
@hook = Current.account.hooks.find(params[:id])
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize(:hook)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.require(:hook).permit(:app_id, :inbox_id, :status, settings: {})
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_hook, only: [:update, :destroy]
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
builder = Integrations::Slack::HookBuilder.new(
|
||||
account: Current.account,
|
||||
code: params[:code],
|
||||
inbox_id: params[:inbox_id]
|
||||
)
|
||||
@hook = builder.perform
|
||||
create_chatwoot_slack_channel
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
create_chatwoot_slack_channel
|
||||
render json: @hook
|
||||
end
|
||||
|
||||
def destroy
|
||||
@hook.destroy
|
||||
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_hook
|
||||
@hook = Integrations::Hook.where(account: Current.account).find_by(app_id: 'slack')
|
||||
end
|
||||
|
||||
def create_chatwoot_slack_channel
|
||||
channel = params[:channel] || 'customer-conversations'
|
||||
builder = Integrations::Slack::ChannelBuilder.new(
|
||||
hook: @hook, channel: channel
|
||||
)
|
||||
builder.perform
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
class Api::V1::Accounts::Kbase::BaseController < Api::V1::Accounts::BaseController
|
||||
before_action :portal
|
||||
|
||||
private
|
||||
|
||||
def portal
|
||||
@portal ||= Current.account.kbase_portals.find_by(id: params[:portal_id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
class Api::V1::Accounts::Kbase::CategoriesController < Api::V1::Accounts::Kbase::BaseController
|
||||
before_action :fetch_category, except: [:index, :create]
|
||||
|
||||
def index
|
||||
@categories = @portal.categories
|
||||
end
|
||||
|
||||
def create
|
||||
@category = @portal.categories.create!(category_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@category.update!(category_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@category.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_category
|
||||
@category = @portal.categories.find(params[:id])
|
||||
end
|
||||
|
||||
def category_params
|
||||
params.require(:category).permit(
|
||||
:name, :description, :position
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
class Api::V1::Accounts::Kbase::PortalsController < Api::V1::Accounts::Kbase::BaseController
|
||||
before_action :fetch_portal, except: [:index, :create]
|
||||
|
||||
def index
|
||||
@portals = Current.account.kbase_portals
|
||||
end
|
||||
|
||||
def create
|
||||
@portal = Current.account.kbase_portals.create!(portal_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@portal.update!(portal_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@portal.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_portal
|
||||
@portal = current_account.kbase_portals.find(params[:id])
|
||||
end
|
||||
|
||||
def portal_params
|
||||
params.require(:portal).permit(
|
||||
:account_id, :color, :custom_domain, :header_text, :homepage_link, :name, :page_title, :slug
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1,10 +1,34 @@
|
||||
class Api::V1::Accounts::LabelsController < Api::BaseController
|
||||
# list all labels in account
|
||||
class Api::V1::Accounts::LabelsController < Api::V1::Accounts::BaseController
|
||||
before_action :current_account
|
||||
before_action :fetch_label, except: [:index, :create]
|
||||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
@labels = current_account.all_conversation_tags
|
||||
@labels = policy_scope(Current.account.labels)
|
||||
end
|
||||
|
||||
def most_used
|
||||
@labels = ActsAsTaggableOn::Tag.most_used(params[:count] || 10)
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@label = Current.account.labels.create!(permitted_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@label.update!(permitted_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@label.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_label
|
||||
@label = Current.account.labels.find(params[:id])
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.require(:label).permit(:title, :description, :color, :show_on_sidebar)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Api::V1::Accounts::NotificationSettingsController < Api::BaseController
|
||||
class Api::V1::Accounts::NotificationSettingsController < Api::V1::Accounts::BaseController
|
||||
before_action :set_user, :load_notification_setting
|
||||
|
||||
def show; end
|
||||
@@ -16,7 +16,7 @@ class Api::V1::Accounts::NotificationSettingsController < Api::BaseController
|
||||
end
|
||||
|
||||
def load_notification_setting
|
||||
@notification_setting = @user.notification_settings.find_by(account_id: current_account.id)
|
||||
@notification_setting = @user.notification_settings.find_by(account_id: Current.account.id)
|
||||
end
|
||||
|
||||
def notification_setting_params
|
||||
|
||||
@@ -1,11 +1,27 @@
|
||||
class Api::V1::Accounts::NotificationsController < Api::BaseController
|
||||
protect_from_forgery with: :null_session
|
||||
class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseController
|
||||
RESULTS_PER_PAGE = 15
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
before_action :fetch_notification, only: [:update]
|
||||
before_action :set_primary_actor, only: [:read_all]
|
||||
before_action :set_current_page, only: [:index]
|
||||
|
||||
def index
|
||||
@notifications = current_user.notifications.where(account_id: current_account.id)
|
||||
render json: @notifications
|
||||
@unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count
|
||||
@count = notifications.count
|
||||
@notifications = notifications.page(@current_page).per(RESULTS_PER_PAGE)
|
||||
end
|
||||
|
||||
def read_all
|
||||
# rubocop:disable Rails/SkipsModelValidations
|
||||
if @primary_actor
|
||||
current_user.notifications.where(account_id: current_account.id, primary_actor: @primary_actor, read_at: nil)
|
||||
.update_all(read_at: DateTime.now.utc)
|
||||
else
|
||||
current_user.notifications.where(account_id: current_account.id, read_at: nil).update_all(read_at: DateTime.now.utc)
|
||||
end
|
||||
# rubocop:enable Rails/SkipsModelValidations
|
||||
head :ok
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -13,9 +29,29 @@ class Api::V1::Accounts::NotificationsController < Api::BaseController
|
||||
render json: @notification
|
||||
end
|
||||
|
||||
def unread_count
|
||||
@unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count
|
||||
render json: @unread_count
|
||||
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
|
||||
|
||||
def set_current_page
|
||||
@current_page = params[:page] || 1
|
||||
end
|
||||
|
||||
def notifications
|
||||
@notifications ||= current_user.notifications.where(account_id: current_account.id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
class Api::V1::Accounts::SubscriptionsController < Api::BaseController
|
||||
skip_before_action :check_subscription
|
||||
|
||||
before_action :check_billing_enabled
|
||||
|
||||
def index
|
||||
render json: current_account.subscription_data
|
||||
end
|
||||
|
||||
def status
|
||||
render json: current_account.subscription.summary
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
class Api::V1::Accounts::TeamMembersController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_team
|
||||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
@team_members = @team.team_members.map(&:user)
|
||||
end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
@team_members = params[:user_ids].map { |user_id| @team.add_member(user_id) }
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
ActiveRecord::Base.transaction do
|
||||
members_to_be_added_ids.each { |user_id| @team.add_member(user_id) }
|
||||
members_to_be_removed_ids.each { |user_id| @team.remove_member(user_id) }
|
||||
end
|
||||
@team_members = @team.members
|
||||
render action: 'create'
|
||||
end
|
||||
|
||||
def destroy
|
||||
ActiveRecord::Base.transaction do
|
||||
params[:user_ids].map { |user_id| @team.remove_member(user_id) }
|
||||
end
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def members_to_be_added_ids
|
||||
params[:user_ids] - current_members_ids
|
||||
end
|
||||
|
||||
def members_to_be_removed_ids
|
||||
current_members_ids - params[:user_ids]
|
||||
end
|
||||
|
||||
def current_members_ids
|
||||
@current_members_ids ||= @team.members.pluck(:id)
|
||||
end
|
||||
|
||||
def fetch_team
|
||||
@team = Current.account.teams.find(params[:team_id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
class Api::V1::Accounts::TeamsController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_team, only: [:show, :update, :destroy]
|
||||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
@teams = Current.account.teams
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@team = Current.account.teams.new(team_params)
|
||||
@team.save!
|
||||
end
|
||||
|
||||
def update
|
||||
@team.update!(team_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@team.destroy
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_team
|
||||
@team = Current.account.teams.find(params[:id])
|
||||
end
|
||||
|
||||
def team_params
|
||||
params.require(:team).permit(:name, :description, :allow_auto_assign)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
class Api::V1::Accounts::Twitter::AuthorizationsController < Api::V1::Accounts::BaseController
|
||||
include TwitterConcern
|
||||
|
||||
before_action :check_authorization
|
||||
|
||||
def create
|
||||
@response = twitter_client.request_oauth_token(url: twitter_callback_url)
|
||||
if @response.status == '200'
|
||||
::Redis::Alfred.setex(oauth_token, Current.account.id)
|
||||
render json: { success: true, url: oauth_authorize_endpoint(oauth_token) }
|
||||
else
|
||||
render json: { success: false }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def oauth_token
|
||||
parsed_body['oauth_token']
|
||||
end
|
||||
|
||||
def oauth_authorize_endpoint(oauth_token)
|
||||
"#{twitter_api_base_url}/oauth/authorize?oauth_token=#{oauth_token}"
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
|
||||
end
|
||||
end
|
||||
@@ -1,14 +1,13 @@
|
||||
class Api::V1::Accounts::WebhooksController < Api::BaseController
|
||||
before_action :current_account
|
||||
class Api::V1::Accounts::WebhooksController < Api::V1::Accounts::BaseController
|
||||
before_action :check_authorization
|
||||
before_action :fetch_webhook, only: [:update, :destroy]
|
||||
|
||||
def index
|
||||
@webhooks = current_account.webhooks
|
||||
@webhooks = Current.account.webhooks
|
||||
end
|
||||
|
||||
def create
|
||||
@webhook = current_account.webhooks.new(webhook_params)
|
||||
@webhook = Current.account.webhooks.new(webhook_params)
|
||||
@webhook.save!
|
||||
end
|
||||
|
||||
@@ -28,10 +27,6 @@ class Api::V1::Accounts::WebhooksController < Api::BaseController
|
||||
end
|
||||
|
||||
def fetch_webhook
|
||||
@webhook = current_account.webhooks.find(params[:id])
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize(Webhook)
|
||||
@webhook = Current.account.webhooks.find(params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
class Api::V1::Accounts::WorkingHoursController < Api::V1::Accounts::BaseController
|
||||
before_action :check_authorization
|
||||
before_action :fetch_webhook, only: [:update]
|
||||
|
||||
def update
|
||||
@working_hour.update!(working_hour_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def working_hour_params
|
||||
params.require(:working_hour).permit(:inbox_id, :open_hour, :open_minutes, :close_hour, :close_minutes, :closed_all_day)
|
||||
end
|
||||
|
||||
def fetch_working_hour
|
||||
@working_hour = Current.account.working_hours.find(params[:id])
|
||||
end
|
||||
end
|
||||
+10
-11
@@ -1,8 +1,8 @@
|
||||
class Api::V1::Accounts::AccountsController < Api::BaseController
|
||||
class Api::V1::AccountsController < Api::BaseController
|
||||
include AuthHelper
|
||||
|
||||
skip_before_action :verify_authenticity_token, only: [:create]
|
||||
skip_before_action :authenticate_user!, :set_current_user, :check_subscription, :handle_with_exception,
|
||||
skip_before_action :authenticate_user!, :set_current_user, :handle_with_exception,
|
||||
only: [:create], raise: false
|
||||
before_action :check_signup_enabled, only: [:create]
|
||||
before_action :fetch_account, except: [:create]
|
||||
@@ -14,25 +14,28 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
||||
with: :render_error_response
|
||||
|
||||
def create
|
||||
@user = AccountBuilder.new(
|
||||
@user, @account = AccountBuilder.new(
|
||||
account_name: account_params[:account_name],
|
||||
user_full_name: account_params[:user_full_name],
|
||||
email: account_params[:email],
|
||||
confirmed: confirmed?
|
||||
confirmed: confirmed?,
|
||||
user: current_user
|
||||
).perform
|
||||
if @user
|
||||
send_auth_headers(@user)
|
||||
render partial: 'devise/auth.json', locals: { resource: @user }
|
||||
render 'api/v1/accounts/create.json', locals: { resource: @user }
|
||||
else
|
||||
render_error_response(CustomExceptions::Account::SignupFailed.new({}))
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@latest_chatwoot_version = ::Redis::Alfred.get(::Redis::Alfred::LATEST_CHATWOOT_VERSION)
|
||||
render 'api/v1/accounts/show.json'
|
||||
end
|
||||
|
||||
def update
|
||||
@account.update!(account_params.slice(:name, :locale, :domain, :support_email, :domain_emails_enabled))
|
||||
@account.update!(account_params.slice(:name, :locale, :domain, :support_email, :auto_resolve_duration))
|
||||
end
|
||||
|
||||
def update_active_at
|
||||
@@ -43,10 +46,6 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
authorize(Account)
|
||||
end
|
||||
|
||||
def confirmed?
|
||||
super_admin? && params[:confirmed]
|
||||
end
|
||||
@@ -57,7 +56,7 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
||||
end
|
||||
|
||||
def account_params
|
||||
params.permit(:account_name, :email, :name, :locale, :domain, :support_email, :domain_emails_enabled)
|
||||
params.permit(:account_name, :email, :name, :locale, :domain, :support_email, :auto_resolve_duration, :user_full_name)
|
||||
end
|
||||
|
||||
def check_signup_enabled
|
||||
@@ -1,6 +1,5 @@
|
||||
class Api::V1::AgentBotsController < Api::BaseController
|
||||
skip_before_action :authenticate_user!
|
||||
skip_before_action :check_subscription
|
||||
|
||||
def index
|
||||
render json: AgentBot.all
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
class Api::V1::Integrations::WebhooksController < ApplicationController
|
||||
def create
|
||||
builder = Integrations::Slack::IncomingMessageBuilder.new(params)
|
||||
response = builder.perform
|
||||
render json: response
|
||||
end
|
||||
end
|
||||
@@ -16,6 +16,15 @@ 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,
|
||||
ui_settings: {}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
class Api::V1::WebhooksController < ApplicationController
|
||||
skip_before_action :authenticate_user!, raise: false
|
||||
skip_before_action :set_current_user
|
||||
skip_before_action :check_subscription
|
||||
|
||||
before_action :login_from_basic_auth, only: [:chargebee]
|
||||
before_action :check_billing_enabled, only: [:chargebee]
|
||||
|
||||
def chargebee
|
||||
chargebee_consumer.consume
|
||||
head :ok
|
||||
rescue StandardError => e
|
||||
Raven.capture_exception(e)
|
||||
head :ok
|
||||
end
|
||||
|
||||
def twitter_crc
|
||||
render json: { response_token: "sha256=#{twitter_client.generate_crc(params[:crc_token])}" }
|
||||
@@ -34,16 +22,6 @@ class Api::V1::WebhooksController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def login_from_basic_auth
|
||||
authenticate_or_request_with_http_basic do |username, password|
|
||||
username == ENV['CHARGEBEE_WEBHOOK_USERNAME'] && password == ENV['CHARGEBEE_WEBHOOK_PASSWORD']
|
||||
end
|
||||
end
|
||||
|
||||
def chargebee_consumer
|
||||
@chargebee_consumer ||= ::Webhooks::Chargebee.new(params)
|
||||
end
|
||||
|
||||
def twitter_consumer
|
||||
@twitter_consumer ||= ::Webhooks::Twitter.new(params)
|
||||
end
|
||||
|
||||
@@ -1,30 +1,107 @@
|
||||
class Api::V1::Widget::BaseController < ApplicationController
|
||||
include SwitchLocale
|
||||
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
|
||||
private
|
||||
|
||||
def conversations
|
||||
if @contact_inbox.hmac_verified?
|
||||
verified_contact_inbox_ids = @contact.contact_inboxes.where(inbox_id: auth_token_params[:inbox_id], hmac_verified: true).map(&:id)
|
||||
@conversations = @contact.conversations.where(contact_inbox_id: verified_contact_inbox_ids)
|
||||
else
|
||||
@conversations = @contact_inbox.conversations.where(inbox_id: auth_token_params[:inbox_id])
|
||||
end
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= @contact_inbox.conversations.where(
|
||||
inbox_id: auth_token_params[:inbox_id]
|
||||
).last
|
||||
@conversation ||= conversations.last
|
||||
end
|
||||
|
||||
def auth_token_params
|
||||
@auth_token_params ||= ::Widget::TokenService.new(token: request.headers[header_name]).decode_token
|
||||
end
|
||||
|
||||
def header_name
|
||||
'X-Auth-Token'
|
||||
@auth_token_params ||= ::Widget::TokenService.new(token: request.headers['X-Auth-Token']).decode_token
|
||||
end
|
||||
|
||||
def set_web_widget
|
||||
@web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token])
|
||||
@account = @web_widget.account
|
||||
switch_locale @account
|
||||
@current_account = @web_widget.account
|
||||
end
|
||||
|
||||
def set_contact
|
||||
@contact_inbox = @web_widget.inbox.contact_inboxes.find_by(
|
||||
source_id: auth_token_params[:source_id]
|
||||
)
|
||||
@contact = @contact_inbox.contact
|
||||
@contact = @contact_inbox&.contact
|
||||
raise ActiveRecord::RecordNotFound unless @contact
|
||||
end
|
||||
|
||||
def create_conversation
|
||||
::Conversation.create!(conversation_params)
|
||||
end
|
||||
|
||||
def inbox
|
||||
@inbox ||= ::Inbox.find_by(id: auth_token_params[:inbox_id])
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
# FIXME: typo referrer in additional attributes, will probably require a migration.
|
||||
{
|
||||
account_id: inbox.account_id,
|
||||
inbox_id: inbox.id,
|
||||
contact_id: @contact.id,
|
||||
contact_inbox_id: @contact_inbox.id,
|
||||
additional_attributes: {
|
||||
browser: browser_params,
|
||||
referer: permitted_params[:message][:referer_url],
|
||||
initiated_at: timestamp_params
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def update_contact(email)
|
||||
contact_with_email = @current_account.contacts.find_by(email: email)
|
||||
if contact_with_email
|
||||
@contact = ::ContactMergeAction.new(
|
||||
account: @current_account,
|
||||
base_contact: contact_with_email,
|
||||
mergee_contact: @contact
|
||||
).perform
|
||||
else
|
||||
@contact.update!(email: email, name: contact_name)
|
||||
end
|
||||
end
|
||||
|
||||
def contact_email
|
||||
permitted_params[:contact][:email].downcase
|
||||
end
|
||||
|
||||
def contact_name
|
||||
params[:contact][:name] || contact_email.split('@')[0]
|
||||
end
|
||||
|
||||
def browser_params
|
||||
{
|
||||
browser_name: browser.name,
|
||||
browser_version: browser.full_version,
|
||||
device_name: browser.device.name,
|
||||
platform_name: browser.platform.name,
|
||||
platform_version: browser.platform.version
|
||||
}
|
||||
end
|
||||
|
||||
def timestamp_params
|
||||
{ timestamp: permitted_params[:message][:timestamp] }
|
||||
end
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
sender: @contact,
|
||||
content: permitted_params[:message][:content],
|
||||
inbox_id: conversation.inbox_id,
|
||||
echo_id: permitted_params[:message][:echo_id],
|
||||
message_type: :incoming
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
class Api::V1::Widget::CampaignsController < Api::V1::Widget::BaseController
|
||||
skip_before_action :set_contact
|
||||
|
||||
def index
|
||||
@campaigns = @web_widget.inbox.campaigns.where(enabled: true)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def permitted_params
|
||||
params.permit(:website_token)
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,7 @@
|
||||
class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
before_action :process_hmac
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
contact_identify_action = ContactIdentifyAction.new(
|
||||
@@ -12,7 +13,22 @@ class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
|
||||
|
||||
private
|
||||
|
||||
def process_hmac
|
||||
return if params[:identifier_hash].blank?
|
||||
raise StandardError, 'HMAC failed: Invalid Identifier Hash Provided' unless valid_hmac?
|
||||
|
||||
@contact_inbox.update(hmac_verified: true)
|
||||
end
|
||||
|
||||
def valid_hmac?
|
||||
params[:identifier_hash] == OpenSSL::HMAC.hexdigest(
|
||||
'sha256',
|
||||
@web_widget.hmac_token,
|
||||
params[:identifier].to_s
|
||||
)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:website_token, :identifier, :email, :name, :avatar_url)
|
||||
params.permit(:website_token, :identifier, :identifier_hash, :email, :name, :avatar_url, custom_attributes: {})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +1,43 @@
|
||||
class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||
include Events::Types
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
|
||||
def index
|
||||
@conversation = conversation
|
||||
end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
update_contact(contact_email) if @contact.email.blank? && contact_email.present?
|
||||
@conversation = create_conversation
|
||||
conversation.messages.create(message_params)
|
||||
end
|
||||
end
|
||||
|
||||
def update_last_seen
|
||||
head :ok && return if conversation.nil?
|
||||
|
||||
conversation.contact_last_seen_at = DateTime.now.utc
|
||||
conversation.save!
|
||||
head :ok
|
||||
end
|
||||
|
||||
def transcript
|
||||
if permitted_params[:email].present? && conversation.present?
|
||||
ConversationReplyMailer.conversation_transcript(
|
||||
conversation,
|
||||
permitted_params[:email]
|
||||
)&.deliver_later
|
||||
end
|
||||
head :ok
|
||||
end
|
||||
|
||||
def toggle_typing
|
||||
head :ok && return if conversation.nil?
|
||||
|
||||
if permitted_params[:typing_status] == 'on'
|
||||
case permitted_params[:typing_status]
|
||||
when 'on'
|
||||
trigger_typing_event(CONVERSATION_TYPING_ON)
|
||||
elsif permitted_params[:typing_status] == 'off'
|
||||
when 'off'
|
||||
trigger_typing_event(CONVERSATION_TYPING_OFF)
|
||||
end
|
||||
|
||||
@@ -26,6 +51,6 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :typing_status, :website_token)
|
||||
params.permit(:id, :typing_status, :website_token, :email, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
class Api::V1::Widget::EventsController < Api::V1::Widget::BaseController
|
||||
include Events::Types
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
|
||||
def create
|
||||
Rails.configuration.dispatcher.dispatch(permitted_params[:name], Time.zone.now, contact_inbox: @contact_inbox)
|
||||
Rails.configuration.dispatcher.dispatch(permitted_params[:name], Time.zone.now, contact_inbox: @contact_inbox,
|
||||
event_info: permitted_params[:event_info].to_h.merge(event_info))
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def event_info
|
||||
{
|
||||
widget_language: params[:locale],
|
||||
browser_language: browser.accept_language.first&.code,
|
||||
browser: browser_params
|
||||
}
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:name, :website_token)
|
||||
params.permit(:name, :website_token, event_info: {})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Api::V1::Widget::InboxMembersController < Api::V1::Widget::BaseController
|
||||
before_action :set_web_widget
|
||||
skip_before_action :set_contact
|
||||
|
||||
def index
|
||||
@inbox_members = @web_widget.inbox.inbox_members.includes(:user)
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
class Api::V1::Widget::LabelsController < Api::V1::Widget::BaseController
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
|
||||
def create
|
||||
conversation.label_list.add(permitted_params[:label])
|
||||
conversation.save!
|
||||
if conversation.present? && label_defined_in_account?
|
||||
conversation.label_list.add(permitted_params[:label])
|
||||
conversation.save!
|
||||
end
|
||||
|
||||
head :no_content
|
||||
end
|
||||
|
||||
def destroy
|
||||
conversation.label_list.remove(permitted_params[:id])
|
||||
conversation.save!
|
||||
if conversation.present?
|
||||
conversation.label_list.remove(permitted_params[:id])
|
||||
conversation.save!
|
||||
end
|
||||
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def label_defined_in_account?
|
||||
label = @current_account.labels&.find_by(title: permitted_params[:label])
|
||||
label.present?
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :label, :website_token)
|
||||
end
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
before_action :set_web_widget
|
||||
before_action :set_contact
|
||||
before_action :set_conversation, only: [:create]
|
||||
before_action :set_message, only: [:update]
|
||||
|
||||
@@ -41,51 +39,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
@conversation = ::Conversation.create!(conversation_params) if conversation.nil?
|
||||
end
|
||||
|
||||
def message_params
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
contact_id: @contact.id,
|
||||
content: permitted_params[:message][:content],
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :incoming
|
||||
}
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: inbox.account_id,
|
||||
inbox_id: inbox.id,
|
||||
contact_id: @contact.id,
|
||||
contact_inbox_id: @contact_inbox.id,
|
||||
additional_attributes: {
|
||||
browser: browser_params,
|
||||
referer: permitted_params[:message][:referer_url],
|
||||
initiated_at: timestamp_params
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def browser_params
|
||||
{
|
||||
browser_name: browser.name,
|
||||
browser_version: browser.full_version,
|
||||
device_name: browser.device.name,
|
||||
platform_name: browser.platform.name,
|
||||
platform_version: browser.platform.version
|
||||
}
|
||||
end
|
||||
|
||||
def timestamp_params
|
||||
{
|
||||
timestamp: permitted_params[:message][:timestamp]
|
||||
}
|
||||
end
|
||||
|
||||
def inbox
|
||||
@inbox ||= ::Inbox.find_by(id: auth_token_params[:inbox_id])
|
||||
@conversation = create_conversation if conversation.nil?
|
||||
end
|
||||
|
||||
def message_finder_params
|
||||
@@ -99,36 +53,12 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
@message_finder ||= MessageFinder.new(conversation, message_finder_params)
|
||||
end
|
||||
|
||||
def update_contact(email)
|
||||
contact_with_email = @account.contacts.find_by(email: email)
|
||||
if contact_with_email
|
||||
@contact = ::ContactMergeAction.new(
|
||||
account: @account,
|
||||
base_contact: contact_with_email,
|
||||
mergee_contact: @contact
|
||||
).perform
|
||||
else
|
||||
@contact.update!(
|
||||
email: email,
|
||||
name: contact_name
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def contact_email
|
||||
permitted_params[:contact][:email].downcase
|
||||
end
|
||||
|
||||
def contact_name
|
||||
contact_email.split('@')[0]
|
||||
end
|
||||
|
||||
def message_update_params
|
||||
params.permit(message: [submitted_values: [:name, :title, :value]])
|
||||
params.permit(message: [{ submitted_values: [:name, :title, :value] }])
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :before, :website_token, contact: [:email], message: [:content, :referer_url, :timestamp])
|
||||
params.permit(:id, :before, :website_token, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id])
|
||||
end
|
||||
|
||||
def set_message
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Api::V2::Accounts::ReportsController < Api::BaseController
|
||||
class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||
def account
|
||||
builder = V2::ReportBuilder.new(current_account, account_report_params)
|
||||
builder = V2::ReportBuilder.new(Current.account, account_report_params)
|
||||
data = builder.build
|
||||
render json: data
|
||||
end
|
||||
@@ -9,6 +9,18 @@ class Api::V2::Accounts::ReportsController < Api::BaseController
|
||||
render json: account_summary_metrics
|
||||
end
|
||||
|
||||
def agents
|
||||
response.headers['Content-Type'] = 'text/csv'
|
||||
response.headers['Content-Disposition'] = 'attachment; filename=agents_report.csv'
|
||||
render layout: false, template: 'api/v2/accounts/reports/agents.csv.erb', format: 'csv'
|
||||
end
|
||||
|
||||
def inboxes
|
||||
response.headers['Content-Type'] = 'text/csv'
|
||||
response.headers['Content-Disposition'] = 'attachment; filename=inboxes_report.csv'
|
||||
render layout: false, template: 'api/v2/accounts/reports/inboxes.csv.erb', format: 'csv'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def account_summary_params
|
||||
@@ -29,7 +41,7 @@ class Api::V2::Accounts::ReportsController < Api::BaseController
|
||||
end
|
||||
|
||||
def account_summary_metrics
|
||||
builder = V2::ReportBuilder.new(current_account, account_summary_params)
|
||||
builder = V2::ReportBuilder.new(Current.account, account_summary_params)
|
||||
builder.summary
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
class ApiController < ApplicationController
|
||||
skip_before_action :set_current_user, only: [:index]
|
||||
skip_before_action :check_subscription, only: [:index]
|
||||
|
||||
def index
|
||||
render json: { version: Chatwoot.config[:version], timestamp: Time.now.utc.to_formatted_s(:db) }
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
include DeviseTokenAuth::Concerns::SetUserByToken
|
||||
include Pundit
|
||||
include SwitchLocale
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
before_action :set_current_user, unless: :devise_controller?
|
||||
before_action :check_subscription, unless: :devise_controller?
|
||||
around_action :switch_locale
|
||||
around_action :handle_with_exception, unless: :devise_controller?
|
||||
|
||||
# after_action :verify_authorized
|
||||
@@ -13,40 +14,6 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
private
|
||||
|
||||
def current_account
|
||||
@current_account ||= find_current_account
|
||||
Current.account = @current_account
|
||||
end
|
||||
|
||||
def find_current_account
|
||||
account = Account.find(params[:account_id])
|
||||
if current_user
|
||||
account_accessible_for_user?(account)
|
||||
elsif @resource&.is_a?(AgentBot)
|
||||
account_accessible_for_bot?(account)
|
||||
end
|
||||
switch_locale account
|
||||
account
|
||||
end
|
||||
|
||||
def switch_locale(account)
|
||||
# priority is for locale set in query string (mostly for widget/from js sdk)
|
||||
locale ||= (I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil)
|
||||
# if local is not set in param, lets try account
|
||||
locale ||= (I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil)
|
||||
I18n.locale = locale || I18n.default_locale
|
||||
end
|
||||
|
||||
def account_accessible_for_user?(account)
|
||||
@current_account_user = account.account_users.find_by(user_id: current_user.id)
|
||||
Current.account_user = @current_account_user
|
||||
render_unauthorized('You are not authorized to access this account') unless @current_account_user
|
||||
end
|
||||
|
||||
def account_accessible_for_bot?(account)
|
||||
render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
|
||||
end
|
||||
|
||||
def handle_with_exception
|
||||
yield
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
@@ -56,7 +23,7 @@ class ApplicationController < ActionController::Base
|
||||
render_unauthorized('You are not authorized to do this action')
|
||||
ensure
|
||||
# to address the thread variable leak issues in Puma/Thin webserver
|
||||
Current.user = nil
|
||||
Current.reset
|
||||
end
|
||||
|
||||
def set_current_user
|
||||
@@ -65,7 +32,7 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def current_subscription
|
||||
@subscription ||= current_account.subscription
|
||||
@subscription ||= Current.account.subscription
|
||||
end
|
||||
|
||||
def render_unauthorized(message)
|
||||
@@ -94,18 +61,6 @@ class ApplicationController < ActionController::Base
|
||||
render json: exception.to_hash, status: exception.http_status
|
||||
end
|
||||
|
||||
def check_subscription
|
||||
# This block is left over from the initial version of chatwoot
|
||||
# We might reuse this later in the hosted version of chatwoot.
|
||||
return if !ENV['BILLING_ENABLED'] || !current_user
|
||||
|
||||
if current_subscription.trial? && current_subscription.expiry < Date.current
|
||||
render json: { error: 'Trial Expired' }, status: :trial_expired
|
||||
elsif current_subscription.cancelled?
|
||||
render json: { error: 'Account Suspended' }, status: :account_suspended
|
||||
end
|
||||
end
|
||||
|
||||
def pundit_user
|
||||
{
|
||||
user: Current.user,
|
||||
|
||||
@@ -14,6 +14,7 @@ module AccessTokenAuthHelper
|
||||
render_unauthorized('Invalid Access Token') && return if @access_token.blank?
|
||||
|
||||
@resource = @access_token.owner
|
||||
Current.user = @resource if current_user.is_a?(User)
|
||||
end
|
||||
|
||||
def super_admin?
|
||||
@@ -21,7 +22,7 @@ module AccessTokenAuthHelper
|
||||
end
|
||||
|
||||
def validate_bot_access_token!
|
||||
return if current_user.is_a?(User)
|
||||
return if Current.user.is_a?(User)
|
||||
return if super_admin?
|
||||
return if agent_bot_accessible?
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
module LabelConcern
|
||||
def create
|
||||
model.update_labels(permitted_params[:labels])
|
||||
@labels = model.label_list
|
||||
end
|
||||
|
||||
def index
|
||||
@labels = model.label_list
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
module SwitchLocale
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def switch_locale(&action)
|
||||
# priority is for locale set in query string (mostly for widget/from js sdk)
|
||||
locale ||= locale_from_params
|
||||
# if locale is not set in account, let's use DEFAULT_LOCALE env variable
|
||||
locale ||= locale_from_env_variable
|
||||
set_locale(locale, &action)
|
||||
end
|
||||
|
||||
def switch_locale_using_account_locale(&action)
|
||||
locale = locale_from_account(@current_account)
|
||||
set_locale(locale, &action)
|
||||
end
|
||||
|
||||
def set_locale(locale, &action)
|
||||
# if locale is empty, use default_locale
|
||||
locale ||= I18n.default_locale
|
||||
# Ensure locale won't bleed into other requests
|
||||
# https://guides.rubyonrails.org/i18n.html#managing-the-locale-across-requests
|
||||
I18n.with_locale(locale, &action)
|
||||
end
|
||||
|
||||
def locale_from_params
|
||||
I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil
|
||||
end
|
||||
|
||||
def locale_from_account(account)
|
||||
return unless account
|
||||
|
||||
I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil
|
||||
end
|
||||
|
||||
def locale_from_env_variable
|
||||
return unless ENV.fetch('DEFAULT_LOCALE', nil)
|
||||
|
||||
I18n.available_locales.map(&:to_s).include?(ENV.fetch('DEFAULT_LOCALE')) ? ENV.fetch('DEFAULT_LOCALE') : nil
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
module TwitterConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def parsed_body
|
||||
@parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body)
|
||||
end
|
||||
|
||||
def host
|
||||
ENV.fetch('FRONTEND_URL', '')
|
||||
end
|
||||
|
||||
def twitter_client
|
||||
Twitty::Facade.new do |config|
|
||||
config.consumer_key = ENV.fetch('TWITTER_CONSUMER_KEY', nil)
|
||||
config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
|
||||
config.base_url = twitter_api_base_url
|
||||
config.environment = ENV.fetch('TWITTER_ENVIRONMENT', '')
|
||||
end
|
||||
end
|
||||
|
||||
def twitter_api_base_url
|
||||
'https://api.twitter.com'
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,9 @@
|
||||
class DashboardController < ActionController::Base
|
||||
include SwitchLocale
|
||||
|
||||
before_action :set_global_config
|
||||
around_action :switch_locale
|
||||
before_action :ensure_installation_onboarding, only: [:index]
|
||||
|
||||
layout 'vueapp'
|
||||
|
||||
@@ -15,7 +19,15 @@ class DashboardController < ActionController::Base
|
||||
'WIDGET_BRAND_URL',
|
||||
'TERMS_URL',
|
||||
'PRIVACY_URL',
|
||||
'DISPLAY_MANIFEST'
|
||||
'DISPLAY_MANIFEST',
|
||||
'CREATE_NEW_ACCOUNT_FROM_DASHBOARD',
|
||||
'CHATWOOT_INBOX_TOKEN'
|
||||
).merge(
|
||||
APP_VERSION: Chatwoot.config[:version]
|
||||
)
|
||||
end
|
||||
|
||||
def ensure_installation_onboarding
|
||||
redirect_to '/installation/onboarding' if ::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,27 +4,39 @@ class DeviseOverrides::ConfirmationsController < Devise::ConfirmationsController
|
||||
|
||||
def create
|
||||
@confirmable = User.find_by(confirmation_token: params[:confirmation_token])
|
||||
if @confirmable
|
||||
if @confirmable.confirm || (@confirmable.confirmed_at && @confirmable.reset_password_token)
|
||||
# confirmed now or already confirmed but quit before setting a password
|
||||
render json: { "message": 'Success', "redirect_url": create_reset_token_link(@confirmable) }, status: :ok
|
||||
elsif @confirmable.confirmed_at
|
||||
render json: { "message": 'Already confirmed', "redirect_url": '/' }, status: 422
|
||||
else
|
||||
render json: { "message": 'Failure', "redirect_url": '/' }, status: 422
|
||||
end
|
||||
|
||||
if confirm
|
||||
render_confirmation_success
|
||||
else
|
||||
render json: { "message": 'Invalid token', "redirect_url": '/' }, status: 422
|
||||
render_confirmation_error
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def confirm
|
||||
@confirmable&.confirm || (@confirmable&.confirmed_at && @confirmable&.reset_password_token)
|
||||
end
|
||||
|
||||
def render_confirmation_success
|
||||
render json: { "message": 'Success', "redirect_url": create_reset_token_link(@confirmable) }, status: :ok
|
||||
end
|
||||
|
||||
def render_confirmation_error
|
||||
if @confirmable.blank?
|
||||
render json: { "message": 'Invalid token', "redirect_url": '/' }, status: 422
|
||||
elsif @confirmable.confirmed_at
|
||||
render json: { "message": 'Already confirmed', "redirect_url": '/' }, status: 422
|
||||
else
|
||||
render json: { "message": 'Failure', "redirect_url": '/' }, status: 422
|
||||
end
|
||||
end
|
||||
|
||||
def create_reset_token_link(user)
|
||||
raw, enc = Devise.token_generator.generate(user.class, :reset_password_token)
|
||||
user.reset_password_token = enc
|
||||
user.reset_password_sent_at = Time.now.utc
|
||||
user.save(validate: false)
|
||||
'/app/auth/password/edit?config=default&redirect_url=&reset_password_token=' + raw
|
||||
"/app/auth/password/edit?config=default&redirect_url=&reset_password_token=#{raw}"
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user