Compare commits
387 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7b13e6efc2 | ||
|
3f59238207 | ||
|
eff9416469 | ||
|
6fbb3841a6 | ||
|
9ca02a00a6 | ||
|
3e8e9c8ae4 | ||
|
7bc1805827 | ||
|
e27f792c24 | ||
|
98fab24bea | ||
|
f566c47dda | ||
|
0190aac240 | ||
|
cc382c5006 | ||
|
946a166791 | ||
|
31cd649041 | ||
|
1585b0c6cc | ||
|
15b43f555d | ||
|
d8ec832806 | ||
|
bab5a18232 | ||
|
a20cf3b64e | ||
|
356df7ae6b | ||
|
8f03fdce7f | ||
|
1fc6cb4997 | ||
|
eb832e88f4 | ||
|
b16b69350e | ||
|
da6fa029f6 | ||
|
94ad0706f5 | ||
|
bf8c2c4348 | ||
|
aa58cca040 | ||
|
5cc7cd8518 | ||
|
ff142eb64d | ||
|
500e28442f | ||
|
5bd3715a4c | ||
|
3d13f6ea0c | ||
|
6eefccdacc | ||
|
29a22691d2 | ||
|
d55f207274 | ||
|
cf6fe4f8cb | ||
|
4367443287 | ||
|
8d2b3ada80 | ||
|
f3be605286 | ||
|
aebebdc5d1 | ||
|
05e4728de7 | ||
|
b51945f096 | ||
|
1f2abd8d67 | ||
|
1d9f9352a6 | ||
|
53e42bf91e | ||
|
94d0e012de | ||
|
8fd931dc12 | ||
|
74d10b9b9d | ||
|
2356580cee | ||
|
1840a352f5 | ||
|
c93d0978f2 | ||
|
df4f4e94b3 | ||
|
51b2f789bd | ||
|
947887f261 | ||
|
6f34fdb616 | ||
|
8518d005fd | ||
|
bb911043de | ||
|
da0333f1cb | ||
|
d8a0ee1956 | ||
|
91c71471ab | ||
|
98eacb2238 | ||
|
80c13bf0ef | ||
|
e17c2e5da5 | ||
|
4a618908e8 | ||
|
a208e7d655 | ||
|
c1b9ae7fc2 | ||
|
dc8a6244fc | ||
|
0f52e42c2d | ||
|
85af2405cf | ||
|
47ace633dc | ||
|
85d5518b6b | ||
|
5104bd7988 | ||
|
3e425b51fd | ||
|
37dbfa4cd7 | ||
|
0d23c81662 | ||
|
b436b31d5a | ||
|
72133fbed6 | ||
|
abbdacedc5 | ||
|
ddd3251912 | ||
|
605e2a417c | ||
|
f8fe394e7a | ||
|
2a545e0fb1 | ||
|
ce812466c7 | ||
|
47bf7a8047 | ||
|
85d405c810 | ||
|
f596a413ef | ||
|
9e53fe5c29 | ||
|
3690f04e4a | ||
|
f3e8bc9f8f | ||
|
dcf0530218 | ||
|
47338bc13d | ||
|
6fb9726b99 | ||
|
8015fd7600 | ||
|
4919b89ab8 | ||
|
2925372ff4 | ||
|
778430b54a | ||
|
5282ba862a | ||
|
0464602978 | ||
|
9b03cf0ddd | ||
|
cdff1da901 | ||
|
1a065fb146 | ||
|
022008a2a6 | ||
|
a3715598cc | ||
|
1be48dd805 | ||
|
6384041d17 | ||
|
140e73bc82 | ||
|
e3fae6f52c | ||
|
65d8c73bae | ||
|
177dd8bb53 | ||
|
380b20eed6 | ||
|
c207b4bb33 | ||
|
b87eb8ea14 | ||
|
8902e265b4 | ||
|
b8ea28d6d0 | ||
|
f741673638 | ||
|
0a0b9a271a | ||
|
7d2b4186c3 | ||
|
90689190a3 | ||
|
8acadeea76 | ||
|
75c6513c67 | ||
|
73540ffe6b | ||
|
92bb166246 | ||
|
8cf8ce4ac0 | ||
|
0f1b1d78b1 | ||
|
d3bbef27e7 | ||
|
f0634ba876 | ||
|
1d68fe1a60 | ||
|
6bd6dcf6df | ||
|
28d2920472 | ||
|
34bfea8bbf | ||
|
2d91944285 | ||
|
0026ba2751 | ||
|
b623dd12c1 | ||
|
722d152082 | ||
|
7623766241 | ||
|
e34c5a3503 | ||
|
004672aa6c | ||
|
ad4a28f4f6 | ||
|
d8ae3efec3 | ||
|
cd81a1c52a | ||
|
dcf73ddeff | ||
|
d81b706f12 | ||
|
30fa5fe1a4 | ||
|
7a7bfa5170 | ||
|
e969c78645 | ||
|
7adac1bc51 | ||
|
e859d6f259 | ||
|
a0880edc6e | ||
|
61fcdbbf7e | ||
|
43af695ba1 | ||
|
facd90e7a6 | ||
|
6201f96b8a | ||
|
c26cea262b | ||
|
1f1d6bf2a0 | ||
|
4c06d1cb24 | ||
|
2985d08951 | ||
|
66ca7157db | ||
|
4addf051d4 | ||
|
ab914ce6d5 | ||
|
6a4b224397 | ||
|
6adbd114c1 | ||
|
037f96c5ae | ||
|
f54dca06a9 | ||
|
370fa70924 | ||
|
5be1214c26 | ||
|
f7a30e2fae | ||
|
3f815b2052 | ||
|
defe4f9bc3 | ||
|
943775fd90 | ||
|
42844df966 | ||
|
b0fe58dc69 | ||
|
e07b57852e | ||
|
7c7c18fdea | ||
|
a84664026e | ||
|
02a0fd5b64 | ||
|
6505a42be0 | ||
|
e674608d10 | ||
|
c7af8cbc90 | ||
|
9475fbae78 | ||
|
00e61d6807 | ||
|
19084d3c6c | ||
|
e014bf8ed0 | ||
|
f6e2309e70 | ||
|
9d2154c4ab | ||
|
1dfd27a028 | ||
|
b97ebaf620 | ||
|
8ee2eb5d2e | ||
|
20b647020b | ||
|
3eedad2737 | ||
|
ce7c0def88 | ||
|
dab8fc4584 | ||
|
8a597f0138 | ||
|
3363f2f4d6 | ||
|
c7f2d6af55 | ||
|
e878ddb7c0 | ||
|
336f0b0823 | ||
|
3ea3f24a02 | ||
|
d567a382e3 | ||
|
18fe77084f | ||
|
dc253ea234 | ||
|
9304114b57 | ||
|
1fd5251376 | ||
|
edddc7c791 | ||
|
10768aa204 | ||
|
e98559c3ff | ||
|
2212dc4aaa | ||
|
e1fdac3e9a | ||
|
1162f61ca3 | ||
|
39ea5c0e2e | ||
|
509b0cfafc | ||
|
fda5c699c2 | ||
|
cb7ee4698f | ||
|
d010e270e6 | ||
|
d1e08bd38c | ||
|
dbccdcc1b1 | ||
|
5c63523972 | ||
|
de4681b2be | ||
|
a132332b86 | ||
|
b25e42a77f | ||
|
5236a62861 | ||
|
0f155829b7 | ||
|
84dda45df9 | ||
|
9c7505489f | ||
|
75cad1d9d6 | ||
|
2cc3111a77 | ||
|
bf811e4d4a | ||
|
d6774d2ca3 | ||
|
bd669e3907 | ||
|
1a4860a57a | ||
|
41fa53253c | ||
|
c00ead8a72 | ||
|
e49dc6a06e | ||
|
0e12a8dab9 | ||
|
3652a39de0 | ||
|
79335e46fd | ||
|
7c6e02aaf3 | ||
|
7f55430652 | ||
|
8235623362 | ||
|
83435c49ea | ||
|
93de41b39b | ||
|
b1d4b74a44 | ||
|
bfdf47bc98 | ||
|
33f669a5f8 | ||
|
3576fa0d59 | ||
|
1dcfb90202 | ||
|
22cf18e16f | ||
|
0ebe7d6d23 | ||
|
23081bb299 | ||
|
4c7fe48c40 | ||
|
499cc7b803 | ||
|
7db98aa70e | ||
|
e031fd60ad | ||
|
bc4fad9e22 | ||
|
5ac4d677e9 | ||
|
b42bdd80e8 | ||
|
76fa9d2488 | ||
|
dfc43a6d3d | ||
|
67bc58dd60 | ||
|
2d39560dc1 | ||
|
c49ff7395e | ||
|
e0ada97770 | ||
|
3a2003ba86 | ||
|
9a81be0d37 | ||
|
5e2c5e95b6 | ||
|
34a93ccf57 | ||
|
922fb74197 | ||
|
7bf2d6cb06 | ||
|
11e5c965c3 | ||
|
34157d118c | ||
|
7b92950f1c | ||
|
97d7028c31 | ||
|
a7f2961621 | ||
|
00dda99789 | ||
|
2e27ce3b61 | ||
|
2c10c5a069 | ||
|
bd4dd4c4a0 | ||
|
7d33b60f3f | ||
|
aecce5694b | ||
|
0e4ca51951 | ||
|
dde043f6cd | ||
|
c778a60e4f | ||
|
c347327d54 | ||
|
fd328cf6e8 | ||
|
7b473d7514 | ||
|
dff576b75d | ||
|
52ae83d008 | ||
|
5aacd9d4c7 | ||
|
d24d3fa283 | ||
|
c8a226f61c | ||
|
7a281c477a | ||
|
91c789ec63 | ||
|
9ead3d1cdb | ||
|
402c19a924 | ||
|
b5e8994844 | ||
|
4bd327a0c5 | ||
|
184325077e | ||
|
8236f942ff | ||
|
8963f8c3c2 | ||
|
5e41c26203 | ||
|
45837c533e | ||
|
3fa8512474 | ||
|
0e20de9f89 | ||
|
24d645b7d0 | ||
|
7b23f79d41 | ||
|
3b4095cf1b | ||
|
28cbfb9f10 | ||
|
189a06d2a2 | ||
|
450441fc11 | ||
|
b619362a36 | ||
|
425d02287a | ||
|
2e429c0c25 | ||
|
e0e12b0fee | ||
|
62ca37884a | ||
|
f9180823bc | ||
|
4b0c667c09 | ||
|
1b732cad61 | ||
|
ecef03bb15 | ||
|
9642601126 | ||
|
3836d293a1 | ||
|
0734e1fe33 | ||
|
44cb08297c | ||
|
bd21afb5ed | ||
|
ef80ad17b3 | ||
|
9ea4f37e78 | ||
|
c48772fd3f | ||
|
860e257a68 | ||
|
902d9e34b4 | ||
|
2d97c898f2 | ||
|
f6a93fc150 | ||
|
019f3377bb | ||
|
4b11675bdc | ||
|
2531c5953b | ||
|
c6db416ff7 | ||
|
b00cb2aed3 | ||
|
7c67cb5997 | ||
|
a098d08d12 | ||
|
6267759607 | ||
|
bc39ad37c4 | ||
|
a6ba004bf5 | ||
|
b89ab7e69d | ||
|
33d7338779 | ||
|
cf4fe6cab8 | ||
|
2241a15ee9 | ||
|
bca334cd28 | ||
|
3e3ec9b2c8 | ||
|
a8736aab7a | ||
|
71b266377c | ||
|
08dce5e607 | ||
|
2469fd1cdc | ||
|
531c1bb245 | ||
|
58f5040ee8 | ||
|
838f51770b | ||
|
c52090dbfe | ||
|
807c192fcf | ||
|
3b59f9c6c2 | ||
|
135bdd149e | ||
|
3572138b16 | ||
|
9f69aa3cb1 | ||
|
f5c3d20e9c | ||
|
1ec7c87001 | ||
|
8e4d1cba00 | ||
|
676ba50601 | ||
|
bbc3db8b20 | ||
|
f937cad68f | ||
|
be83d450eb | ||
|
1fd18a61bd | ||
|
5d9f479538 | ||
|
3ce9ca4c99 | ||
|
2ca1f0737a | ||
|
19ecde8fe7 | ||
|
7ee5fc5d68 | ||
|
4289ed1d13 | ||
|
256e3adc1d | ||
|
152b4d54e8 | ||
|
ea2ef16ea4 | ||
|
1d3e0a5060 | ||
|
bf575a1f5e | ||
|
860ffc0560 | ||
|
7eb4abe20a | ||
|
1baa75f79f | ||
|
1d436a4322 | ||
|
8fd174298d | ||
|
9afd7dadbf | ||
|
8e84177305 | ||
|
a28ce13b3e | ||
|
e1b42e9aa0 |
2
.babelrc
2
.babelrc
@@ -14,7 +14,7 @@
|
|||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"syntax-dynamic-import",
|
"syntax-dynamic-import",
|
||||||
"transform-object-rest-spread",
|
["transform-object-rest-spread", { "useBuiltIns": true }],
|
||||||
"transform-class-properties",
|
"transform-class-properties",
|
||||||
[
|
[
|
||||||
"react-intl",
|
"react-intl",
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
.env.*
|
.env.*
|
||||||
public/system
|
public/system
|
||||||
public/assets
|
public/assets
|
||||||
|
public/packs
|
||||||
node_modules
|
node_modules
|
||||||
storybook
|
storybook
|
||||||
neo4j
|
neo4j
|
||||||
|
111
.env.nanobox
Normal file
111
.env.nanobox
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Service dependencies
|
||||||
|
# You may set REDIS_URL instead for more advanced options
|
||||||
|
REDIS_HOST=$DATA_REDIS_HOST
|
||||||
|
REDIS_PORT=6379
|
||||||
|
# REDIS_DB=0
|
||||||
|
|
||||||
|
# You may set DATABASE_URL instead for more advanced options
|
||||||
|
DB_HOST=$DATA_DB_HOST
|
||||||
|
DB_USER=$DATA_DB_USER
|
||||||
|
DB_NAME=gonano
|
||||||
|
DB_PASS=$DATA_DB_PASS
|
||||||
|
DB_PORT=5432
|
||||||
|
|
||||||
|
DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
|
||||||
|
|
||||||
|
# Federation
|
||||||
|
# Note: Changing LOCAL_DOMAIN or LOCAL_HTTPS at a later time will cause unwanted side effects.
|
||||||
|
# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
|
||||||
|
LOCAL_DOMAIN=${APP_NAME}.nanoapp.io
|
||||||
|
LOCAL_HTTPS=false
|
||||||
|
|
||||||
|
# Use this only if you need to run mastodon on a different domain than the one used for federation.
|
||||||
|
# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
|
||||||
|
# DO *NOT* USE THIS UNLESS YOU KNOW *EXACTLY* WHAT YOU ARE DOING.
|
||||||
|
# WEB_DOMAIN=mastodon.example.com
|
||||||
|
|
||||||
|
# Use this if you want to have several aliases handler@example1.com
|
||||||
|
# handler@example2.com etc. for the same user. LOCAL_DOMAIN should not
|
||||||
|
# be added. Comma separated values
|
||||||
|
# ALTERNATE_DOMAINS=example1.com,example2.com
|
||||||
|
|
||||||
|
# Application secrets
|
||||||
|
# Generate each with the `rake secret` task (`nanobox run bundle exec rake secret`)
|
||||||
|
PAPERCLIP_SECRET=$PAPERCLIP_SECRET
|
||||||
|
SECRET_KEY_BASE=$SECRET_KEY_BASE
|
||||||
|
OTP_SECRET=$OTP_SECRET
|
||||||
|
|
||||||
|
# Registrations
|
||||||
|
# Single user mode will disable registrations and redirect frontpage to the first profile
|
||||||
|
# SINGLE_USER_MODE=true
|
||||||
|
# Prevent registrations with following e-mail domains
|
||||||
|
# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
|
||||||
|
# Only allow registrations with the following e-mail domains
|
||||||
|
# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc
|
||||||
|
|
||||||
|
# Optionally change default language
|
||||||
|
# DEFAULT_LOCALE=de
|
||||||
|
|
||||||
|
# E-mail configuration
|
||||||
|
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
|
||||||
|
# If you want to use an SMTP server without authentication (e.g local Postfix relay)
|
||||||
|
# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and
|
||||||
|
# *comment* SMTP_LOGIN and SMTP_PASSWORD (leaving them blank is not enough).
|
||||||
|
SMTP_SERVER=$SMTP_SERVER
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_LOGIN=$SMTP_LOGIN
|
||||||
|
SMTP_PASSWORD=$SMTP_PASSWORD
|
||||||
|
SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||||
|
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
|
||||||
|
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
|
||||||
|
#SMTP_AUTH_METHOD=plain
|
||||||
|
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||||
|
#SMTP_OPENSSL_VERIFY_MODE=peer
|
||||||
|
#SMTP_ENABLE_STARTTLS_AUTO=true
|
||||||
|
|
||||||
|
|
||||||
|
# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
|
||||||
|
# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
|
||||||
|
# PAPERCLIP_ROOT_URL=/system
|
||||||
|
|
||||||
|
# Optional asset host for multi-server setups
|
||||||
|
# CDN_HOST=assets.example.com
|
||||||
|
|
||||||
|
# S3 (optional)
|
||||||
|
# S3_ENABLED=true
|
||||||
|
# S3_BUCKET=
|
||||||
|
# AWS_ACCESS_KEY_ID=
|
||||||
|
# AWS_SECRET_ACCESS_KEY=
|
||||||
|
# S3_REGION=
|
||||||
|
# S3_PROTOCOL=http
|
||||||
|
# S3_HOSTNAME=192.168.1.123:9000
|
||||||
|
|
||||||
|
# S3 (Minio Config (optional) Please check Minio instance for details)
|
||||||
|
# S3_ENABLED=true
|
||||||
|
# S3_BUCKET=
|
||||||
|
# AWS_ACCESS_KEY_ID=
|
||||||
|
# AWS_SECRET_ACCESS_KEY=
|
||||||
|
# S3_REGION=
|
||||||
|
# S3_PROTOCOL=https
|
||||||
|
# S3_HOSTNAME=
|
||||||
|
# S3_ENDPOINT=
|
||||||
|
# S3_SIGNATURE_VERSION=
|
||||||
|
|
||||||
|
# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
|
||||||
|
# S3_CLOUDFRONT_HOST=
|
||||||
|
|
||||||
|
# Streaming API integration
|
||||||
|
# STREAMING_API_BASE_URL=
|
||||||
|
|
||||||
|
# Advanced settings
|
||||||
|
# If you need to use pgBouncer, you need to disable prepared statements:
|
||||||
|
# PREPARED_STATEMENTS=false
|
||||||
|
|
||||||
|
# Cluster number setting for streaming API server.
|
||||||
|
# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
|
||||||
|
STREAMING_CLUSTER_NUM=1
|
||||||
|
|
||||||
|
# Docker mastodon user
|
||||||
|
# If you use Docker, you may want to assign UID/GID manually.
|
||||||
|
# UID=1000
|
||||||
|
# GID=1000
|
@@ -65,7 +65,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
|
|||||||
# PAPERCLIP_ROOT_URL=/system
|
# PAPERCLIP_ROOT_URL=/system
|
||||||
|
|
||||||
# Optional asset host for multi-server setups
|
# Optional asset host for multi-server setups
|
||||||
# CDN_HOST=assets.example.com
|
# CDN_HOST=https://assets.example.com
|
||||||
|
|
||||||
# S3 (optional)
|
# S3 (optional)
|
||||||
# S3_ENABLED=true
|
# S3_ENABLED=true
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
---
|
---
|
||||||
|
root: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
browser: true
|
browser: true
|
||||||
node: false
|
node: true
|
||||||
es6: true
|
es6: true
|
||||||
|
|
||||||
parser: babel-eslint
|
parser: babel-eslint
|
||||||
@@ -21,22 +23,10 @@ parserOptions:
|
|||||||
|
|
||||||
rules:
|
rules:
|
||||||
|
|
||||||
no-cond-assign: error
|
|
||||||
no-console: warn
|
|
||||||
no-irregular-whitespace: error
|
|
||||||
no-unreachable: error
|
|
||||||
valid-typeof: error
|
|
||||||
consistent-return: error
|
|
||||||
dot-notation: error
|
|
||||||
eqeqeq: error
|
|
||||||
no-fallthrough: error
|
|
||||||
no-unused-expressions: error
|
|
||||||
strict: off
|
|
||||||
no-catch-shadow: error
|
|
||||||
indent:
|
|
||||||
- warn
|
|
||||||
- 2
|
|
||||||
brace-style: warn
|
brace-style: warn
|
||||||
|
comma-dangle:
|
||||||
|
- error
|
||||||
|
- always-multiline
|
||||||
comma-spacing:
|
comma-spacing:
|
||||||
- warn
|
- warn
|
||||||
- before: false
|
- before: false
|
||||||
@@ -44,22 +34,70 @@ rules:
|
|||||||
comma-style:
|
comma-style:
|
||||||
- warn
|
- warn
|
||||||
- last
|
- last
|
||||||
|
consistent-return: error
|
||||||
|
dot-notation: error
|
||||||
|
eqeqeq: error
|
||||||
|
indent:
|
||||||
|
- warn
|
||||||
|
- 2
|
||||||
|
jsx-quotes:
|
||||||
|
- error
|
||||||
|
- prefer-single
|
||||||
|
no-catch-shadow: error
|
||||||
|
no-cond-assign: error
|
||||||
|
no-console:
|
||||||
|
- warn
|
||||||
|
- allow:
|
||||||
|
- error
|
||||||
|
no-fallthrough: error
|
||||||
|
no-irregular-whitespace: error
|
||||||
no-mixed-spaces-and-tabs: warn
|
no-mixed-spaces-and-tabs: warn
|
||||||
no-nested-ternary: warn
|
no-nested-ternary: warn
|
||||||
no-trailing-spaces: warn
|
no-trailing-spaces: warn
|
||||||
semi: error
|
no-undef: error
|
||||||
|
no-unreachable: error
|
||||||
|
no-unused-expressions: error
|
||||||
|
no-unused-vars:
|
||||||
|
- error
|
||||||
|
- vars: all
|
||||||
|
args: after-used
|
||||||
|
ignoreRestSiblings: true
|
||||||
|
object-curly-spacing:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
padded-blocks:
|
padded-blocks:
|
||||||
- error
|
- error
|
||||||
- classes: always
|
- classes: always
|
||||||
comma-dangle:
|
quotes:
|
||||||
- error
|
- error
|
||||||
- always-multiline
|
- single
|
||||||
|
semi: error
|
||||||
|
strict: off
|
||||||
|
valid-typeof: error
|
||||||
|
|
||||||
react/jsx-wrap-multilines: error
|
react/jsx-boolean-value: error
|
||||||
|
react/jsx-closing-bracket-location:
|
||||||
|
- error
|
||||||
|
- line-aligned
|
||||||
|
react/jsx-curly-spacing: error
|
||||||
|
react/jsx-equals-spacing: error
|
||||||
|
react/jsx-first-prop-new-line:
|
||||||
|
- error
|
||||||
|
- multiline-multiprop
|
||||||
|
react/jsx-indent:
|
||||||
|
- error
|
||||||
|
- 2
|
||||||
react/jsx-no-bind: error
|
react/jsx-no-bind: error
|
||||||
react/self-closing-comp: error
|
react/jsx-no-duplicate-props: error
|
||||||
react/prop-types: error
|
react/jsx-no-undef: error
|
||||||
|
react/jsx-tag-spacing: error
|
||||||
|
react/jsx-uses-react: error
|
||||||
|
react/jsx-uses-vars: error
|
||||||
|
react/jsx-wrap-multilines: error
|
||||||
react/no-multi-comp: off
|
react/no-multi-comp: off
|
||||||
|
react/no-string-refs: error
|
||||||
|
react/prop-types: error
|
||||||
|
react/self-closing-comp: error
|
||||||
|
|
||||||
jsx-a11y/accessible-emoji: warn
|
jsx-a11y/accessible-emoji: warn
|
||||||
jsx-a11y/anchor-has-content: warn
|
jsx-a11y/anchor-has-content: warn
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -20,6 +20,7 @@ coverage
|
|||||||
public/system
|
public/system
|
||||||
public/assets
|
public/assets
|
||||||
public/packs
|
public/packs
|
||||||
|
public/packs-test
|
||||||
.env
|
.env
|
||||||
.env.production
|
.env.production
|
||||||
node_modules/
|
node_modules/
|
||||||
@@ -33,6 +34,7 @@ config/deploy/*
|
|||||||
|
|
||||||
# Ignore IDE files
|
# Ignore IDE files
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
# Ignore postgres + redis volume optionally created by docker-compose
|
# Ignore postgres + redis volume optionally created by docker-compose
|
||||||
postgres
|
postgres
|
||||||
|
20
.nanoignore
Normal file
20
.nanoignore
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.DS_Store
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
.bundle/
|
||||||
|
.cache/
|
||||||
|
config/deploy/*
|
||||||
|
coverage
|
||||||
|
docs/
|
||||||
|
.env
|
||||||
|
log/*.log
|
||||||
|
neo4j/
|
||||||
|
node_modules/
|
||||||
|
public/assets/
|
||||||
|
public/system/
|
||||||
|
spec/
|
||||||
|
storybook/
|
||||||
|
tmp/
|
||||||
|
.vagrant/
|
||||||
|
vendor/bundle/
|
1
.profile
Normal file
1
.profile
Normal file
@@ -0,0 +1 @@
|
|||||||
|
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/.apt/lib/x86_64-linux-gnu:/app/.apt/usr/lib/x86_64-linux-gnu/mesa:/app/.apt/usr/lib/x86_64-linux-gnu/pulseaudio
|
93
.rubocop.yml
93
.rubocop.yml
@@ -1,26 +1,35 @@
|
|||||||
Rails:
|
AllCops:
|
||||||
Enabled: true
|
TargetRubyVersion: 2.3
|
||||||
|
Exclude:
|
||||||
|
- 'spec/**/*'
|
||||||
|
- 'db/**/*'
|
||||||
|
- 'app/views/**/*'
|
||||||
|
- 'config/**/*'
|
||||||
|
- 'bin/*'
|
||||||
|
- 'Rakefile'
|
||||||
|
- 'node_modules/**/*'
|
||||||
|
- 'Vagrantfile'
|
||||||
|
- 'vendor/**/*'
|
||||||
|
|
||||||
Style/PerlBackrefs:
|
Bundler/OrderedGems:
|
||||||
AutoCorrect: false
|
|
||||||
|
|
||||||
Style/ClassAndModuleChildren:
|
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Metrics/BlockNesting:
|
Layout/AccessModifierIndentation:
|
||||||
Max: 2
|
EnforcedStyle: indent
|
||||||
|
|
||||||
Metrics/LineLength:
|
Layout/EmptyLineAfterMagicComment:
|
||||||
AllowURI: true
|
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Metrics/MethodLength:
|
Layout/SpaceInsideHashLiteralBraces:
|
||||||
CountComments: false
|
EnforcedStyle: space
|
||||||
Max: 10
|
|
||||||
|
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 100
|
Max: 100
|
||||||
|
|
||||||
|
Metrics/BlockLength:
|
||||||
|
Exclude:
|
||||||
|
- 'lib/tasks/**/*'
|
||||||
|
|
||||||
Metrics/BlockNesting:
|
Metrics/BlockNesting:
|
||||||
Max: 3
|
Max: 3
|
||||||
|
|
||||||
@@ -31,22 +40,36 @@ Metrics/ClassLength:
|
|||||||
Metrics/CyclomaticComplexity:
|
Metrics/CyclomaticComplexity:
|
||||||
Max: 15
|
Max: 15
|
||||||
|
|
||||||
|
Metrics/LineLength:
|
||||||
|
AllowURI: true
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
Metrics/MethodLength:
|
Metrics/MethodLength:
|
||||||
|
CountComments: false
|
||||||
Max: 55
|
Max: 55
|
||||||
|
|
||||||
Metrics/ModuleLength:
|
Metrics/ModuleLength:
|
||||||
CountComments: false
|
CountComments: false
|
||||||
Max: 200
|
Max: 200
|
||||||
|
|
||||||
Metrics/PerceivedComplexity:
|
|
||||||
Max: 10
|
|
||||||
|
|
||||||
Metrics/ParameterLists:
|
Metrics/ParameterLists:
|
||||||
Max: 4
|
Max: 4
|
||||||
CountKeywordArgs: true
|
CountKeywordArgs: true
|
||||||
|
|
||||||
Style/AccessModifierIndentation:
|
Metrics/PerceivedComplexity:
|
||||||
EnforcedStyle: indent
|
Max: 10
|
||||||
|
|
||||||
|
Rails:
|
||||||
|
Enabled: true
|
||||||
|
|
||||||
|
Rails/HasAndBelongsToMany:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
Rails/SkipsModelValidations:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
Style/ClassAndModuleChildren:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
Style/CollectionMethods:
|
Style/CollectionMethods:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
@@ -62,33 +85,25 @@ Style/DoubleNegation:
|
|||||||
Style/FrozenStringLiteralComment:
|
Style/FrozenStringLiteralComment:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
||||||
Style/SpaceInsideHashLiteralBraces:
|
Style/GuardClause:
|
||||||
EnforcedStyle: space
|
|
||||||
|
|
||||||
Style/TrailingCommaInLiteral:
|
|
||||||
EnforcedStyleForMultiline: 'comma'
|
|
||||||
|
|
||||||
Style/RegexpLiteral:
|
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Style/Lambda:
|
Style/Lambda:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Rails/HasAndBelongsToMany:
|
Style/PercentLiteralDelimiters:
|
||||||
|
PreferredDelimiters:
|
||||||
|
'%i': '()'
|
||||||
|
'%w': '()'
|
||||||
|
|
||||||
|
Style/PerlBackrefs:
|
||||||
|
AutoCorrect: false
|
||||||
|
|
||||||
|
Style/RegexpLiteral:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
Bundler/OrderedGems:
|
Style/SymbolArray:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
AllCops:
|
Style/TrailingCommaInLiteral:
|
||||||
TargetRubyVersion: 2.3
|
EnforcedStyleForMultiline: 'comma'
|
||||||
Exclude:
|
|
||||||
- 'spec/**/*'
|
|
||||||
- 'db/**/*'
|
|
||||||
- 'app/views/**/*'
|
|
||||||
- 'config/**/*'
|
|
||||||
- 'bin/*'
|
|
||||||
- 'Rakefile'
|
|
||||||
- 'node_modules/**/*'
|
|
||||||
- 'Vagrantfile'
|
|
||||||
- 'vendor/**/*'
|
|
||||||
|
@@ -4,8 +4,10 @@ cache:
|
|||||||
yarn: true
|
yarn: true
|
||||||
directories:
|
directories:
|
||||||
- node_modules
|
- node_modules
|
||||||
|
- public/assets
|
||||||
|
- public/packs-test
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: false
|
sudo: required
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
@@ -50,6 +52,6 @@ before_script:
|
|||||||
- ln -s /usr/bin/x86_64-linux-gnu-g++-6 "$HOME/g++"
|
- ln -s /usr/bin/x86_64-linux-gnu-g++-6 "$HOME/g++"
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- bundle exec parallel_test spec/ --group-by filesize --type rspec
|
- travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec
|
||||||
- npm test
|
- npm test
|
||||||
- bundle exec i18n-tasks unused
|
- bundle exec i18n-tasks unused
|
||||||
|
3
Aptfile
3
Aptfile
@@ -1,2 +1,5 @@
|
|||||||
protobuf-compiler
|
protobuf-compiler
|
||||||
libprotobuf-dev
|
libprotobuf-dev
|
||||||
|
ffmpeg
|
||||||
|
libxdamage1
|
||||||
|
libxfixes3
|
||||||
|
9
Gemfile
9
Gemfile
@@ -6,7 +6,7 @@ ruby '>= 2.3.0', '< 2.5.0'
|
|||||||
gem 'pkg-config', '~> 1.2'
|
gem 'pkg-config', '~> 1.2'
|
||||||
|
|
||||||
gem 'puma', '~> 3.8'
|
gem 'puma', '~> 3.8'
|
||||||
gem 'rails', '~> 5.0'
|
gem 'rails', '~> 5.1.0'
|
||||||
gem 'uglifier', '~> 3.2'
|
gem 'uglifier', '~> 3.2'
|
||||||
|
|
||||||
gem 'hamlit-rails', '~> 0.2'
|
gem 'hamlit-rails', '~> 0.2'
|
||||||
@@ -38,6 +38,7 @@ gem 'nokogiri', '~> 1.7'
|
|||||||
gem 'oj', '~> 3.0'
|
gem 'oj', '~> 3.0'
|
||||||
gem 'ostatus2', '~> 2.0'
|
gem 'ostatus2', '~> 2.0'
|
||||||
gem 'ox', '~> 2.5'
|
gem 'ox', '~> 2.5'
|
||||||
|
gem 'pundit', '~> 1.1'
|
||||||
gem 'rabl', '~> 0.13'
|
gem 'rabl', '~> 0.13'
|
||||||
gem 'rack-attack', '~> 5.0'
|
gem 'rack-attack', '~> 5.0'
|
||||||
gem 'rack-cors', '~> 0.4', require: 'rack/cors'
|
gem 'rack-cors', '~> 0.4', require: 'rack/cors'
|
||||||
@@ -51,13 +52,14 @@ gem 'sanitize', '~> 4.4'
|
|||||||
gem 'sidekiq', '~> 5.0'
|
gem 'sidekiq', '~> 5.0'
|
||||||
gem 'sidekiq-scheduler', '~> 2.1'
|
gem 'sidekiq-scheduler', '~> 2.1'
|
||||||
gem 'sidekiq-unique-jobs', '~> 5.0'
|
gem 'sidekiq-unique-jobs', '~> 5.0'
|
||||||
|
gem 'sidekiq-bulk', '~>0.1.1'
|
||||||
gem 'simple-navigation', '~> 4.0'
|
gem 'simple-navigation', '~> 4.0'
|
||||||
gem 'simple_form', '~> 3.4'
|
gem 'simple_form', '~> 3.4'
|
||||||
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
||||||
gem 'statsd-instrument', '~> 2.1'
|
gem 'statsd-instrument', '~> 2.1'
|
||||||
gem 'twitter-text', '~> 1.14'
|
gem 'twitter-text', '~> 1.14'
|
||||||
gem 'tzinfo-data', '~> 1.2017'
|
gem 'tzinfo-data', '~> 1.2017'
|
||||||
gem 'webpacker', '~> 1.2'
|
gem 'webpacker', '~> 2.0'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'fabrication', '~> 2.16'
|
gem 'fabrication', '~> 2.16'
|
||||||
@@ -69,6 +71,7 @@ end
|
|||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 2.14'
|
gem 'capybara', '~> 2.14'
|
||||||
|
gem 'climate_control', '~> 0.2'
|
||||||
gem 'faker', '~> 1.7'
|
gem 'faker', '~> 1.7'
|
||||||
gem 'microformats2', '~> 3.0'
|
gem 'microformats2', '~> 3.0'
|
||||||
gem 'rails-controller-testing', '~> 1.0'
|
gem 'rails-controller-testing', '~> 1.0'
|
||||||
@@ -86,7 +89,7 @@ group :development do
|
|||||||
gem 'bullet', '~> 5.5'
|
gem 'bullet', '~> 5.5'
|
||||||
gem 'letter_opener', '~> 1.4'
|
gem 'letter_opener', '~> 1.4'
|
||||||
gem 'letter_opener_web', '~> 1.3'
|
gem 'letter_opener_web', '~> 1.3'
|
||||||
gem 'rubocop', '~> 0.48', require: false
|
gem 'rubocop', require: false
|
||||||
gem 'brakeman', '~> 3.6', require: false
|
gem 'brakeman', '~> 3.6', require: false
|
||||||
gem 'bundler-audit', '~> 0.5', require: false
|
gem 'bundler-audit', '~> 0.5', require: false
|
||||||
gem 'scss_lint', '~> 0.53', require: false
|
gem 'scss_lint', '~> 0.53', require: false
|
||||||
|
152
Gemfile.lock
152
Gemfile.lock
@@ -1,40 +1,40 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (5.0.3)
|
actioncable (5.1.1)
|
||||||
actionpack (= 5.0.3)
|
actionpack (= 5.1.1)
|
||||||
nio4r (>= 1.2, < 3.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (~> 0.6.1)
|
websocket-driver (~> 0.6.1)
|
||||||
actionmailer (5.0.3)
|
actionmailer (5.1.1)
|
||||||
actionpack (= 5.0.3)
|
actionpack (= 5.1.1)
|
||||||
actionview (= 5.0.3)
|
actionview (= 5.1.1)
|
||||||
activejob (= 5.0.3)
|
activejob (= 5.1.1)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (5.0.3)
|
actionpack (5.1.1)
|
||||||
actionview (= 5.0.3)
|
actionview (= 5.1.1)
|
||||||
activesupport (= 5.0.3)
|
activesupport (= 5.1.1)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
rack-test (~> 0.6.3)
|
rack-test (~> 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||||
actionview (5.0.3)
|
actionview (5.1.1)
|
||||||
activesupport (= 5.0.3)
|
activesupport (= 5.1.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubis (~> 2.7.0)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||||
active_record_query_trace (1.5.4)
|
active_record_query_trace (1.5.4)
|
||||||
activejob (5.0.3)
|
activejob (5.1.1)
|
||||||
activesupport (= 5.0.3)
|
activesupport (= 5.1.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (5.0.3)
|
activemodel (5.1.1)
|
||||||
activesupport (= 5.0.3)
|
activesupport (= 5.1.1)
|
||||||
activerecord (5.0.3)
|
activerecord (5.1.1)
|
||||||
activemodel (= 5.0.3)
|
activemodel (= 5.1.1)
|
||||||
activesupport (= 5.0.3)
|
activesupport (= 5.1.1)
|
||||||
arel (~> 7.0)
|
arel (~> 8.0)
|
||||||
activesupport (5.0.3)
|
activesupport (5.1.1)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
@@ -43,22 +43,22 @@ GEM
|
|||||||
public_suffix (~> 2.0, >= 2.0.2)
|
public_suffix (~> 2.0, >= 2.0.2)
|
||||||
airbrussh (1.2.0)
|
airbrussh (1.2.0)
|
||||||
sshkit (>= 1.6.1, != 1.7.0)
|
sshkit (>= 1.6.1, != 1.7.0)
|
||||||
annotate (2.7.1)
|
annotate (2.7.2)
|
||||||
activerecord (>= 3.2, < 6.0)
|
activerecord (>= 3.2, < 6.0)
|
||||||
rake (>= 10.4, < 12.0)
|
rake (>= 10.4, < 13.0)
|
||||||
arel (7.1.4)
|
arel (8.0.0)
|
||||||
ast (2.3.0)
|
ast (2.3.0)
|
||||||
attr_encrypted (3.0.3)
|
attr_encrypted (3.0.3)
|
||||||
encryptor (~> 3.0.0)
|
encryptor (~> 3.0.0)
|
||||||
av (0.9.0)
|
av (0.9.0)
|
||||||
cocaine (~> 0.5.3)
|
cocaine (~> 0.5.3)
|
||||||
aws-sdk (2.9.21)
|
aws-sdk (2.9.37)
|
||||||
aws-sdk-resources (= 2.9.21)
|
aws-sdk-resources (= 2.9.37)
|
||||||
aws-sdk-core (2.9.21)
|
aws-sdk-core (2.9.37)
|
||||||
aws-sigv4 (~> 1.0)
|
aws-sigv4 (~> 1.0)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-resources (2.9.21)
|
aws-sdk-resources (2.9.37)
|
||||||
aws-sdk-core (= 2.9.21)
|
aws-sdk-core (= 2.9.37)
|
||||||
aws-sigv4 (1.0.0)
|
aws-sigv4 (1.0.0)
|
||||||
bcrypt (3.1.11)
|
bcrypt (3.1.11)
|
||||||
better_errors (2.1.1)
|
better_errors (2.1.1)
|
||||||
@@ -67,9 +67,9 @@ GEM
|
|||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
binding_of_caller (0.7.2)
|
binding_of_caller (0.7.2)
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
bootsnap (0.2.14)
|
bootsnap (1.0.0)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
brakeman (3.6.1)
|
brakeman (3.6.2)
|
||||||
builder (3.2.3)
|
builder (3.2.3)
|
||||||
bullet (5.5.1)
|
bullet (5.5.1)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
@@ -85,7 +85,7 @@ GEM
|
|||||||
capistrano-bundler (1.2.0)
|
capistrano-bundler (1.2.0)
|
||||||
capistrano (~> 3.1)
|
capistrano (~> 3.1)
|
||||||
sshkit (~> 1.2)
|
sshkit (~> 1.2)
|
||||||
capistrano-rails (1.2.3)
|
capistrano-rails (1.3.0)
|
||||||
capistrano (~> 3.1)
|
capistrano (~> 3.1)
|
||||||
capistrano-bundler (~> 1.1)
|
capistrano-bundler (~> 1.1)
|
||||||
capistrano-rbenv (2.1.1)
|
capistrano-rbenv (2.1.1)
|
||||||
@@ -93,7 +93,7 @@ GEM
|
|||||||
sshkit (~> 1.3)
|
sshkit (~> 1.3)
|
||||||
capistrano-yarn (2.0.2)
|
capistrano-yarn (2.0.2)
|
||||||
capistrano (~> 3.0)
|
capistrano (~> 3.0)
|
||||||
capybara (2.14.0)
|
capybara (2.14.2)
|
||||||
addressable
|
addressable
|
||||||
mime-types (>= 1.16)
|
mime-types (>= 1.16)
|
||||||
nokogiri (>= 1.3.3)
|
nokogiri (>= 1.3.3)
|
||||||
@@ -130,7 +130,7 @@ GEM
|
|||||||
docile (1.1.5)
|
docile (1.1.5)
|
||||||
domain_name (0.5.20170404)
|
domain_name (0.5.20170404)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
doorkeeper (4.2.5)
|
doorkeeper (4.2.6)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
dotenv (2.2.1)
|
dotenv (2.2.1)
|
||||||
dotenv-rails (2.2.1)
|
dotenv-rails (2.2.1)
|
||||||
@@ -141,6 +141,7 @@ GEM
|
|||||||
thread
|
thread
|
||||||
thread_safe
|
thread_safe
|
||||||
encryptor (3.0.0)
|
encryptor (3.0.0)
|
||||||
|
erubi (1.6.0)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
et-orbi (1.0.4)
|
et-orbi (1.0.4)
|
||||||
tzinfo
|
tzinfo
|
||||||
@@ -185,7 +186,7 @@ GEM
|
|||||||
httplog (0.99.3)
|
httplog (0.99.3)
|
||||||
colorize
|
colorize
|
||||||
rack
|
rack
|
||||||
i18n (0.8.1)
|
i18n (0.8.4)
|
||||||
i18n-tasks (0.9.15)
|
i18n-tasks (0.9.15)
|
||||||
activesupport (>= 4.0.2)
|
activesupport (>= 4.0.2)
|
||||||
ast (>= 2.1.0)
|
ast (>= 2.1.0)
|
||||||
@@ -225,7 +226,7 @@ GEM
|
|||||||
railties (>= 4, < 5.2)
|
railties (>= 4, < 5.2)
|
||||||
loofah (2.0.3)
|
loofah (2.0.3)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.6.5)
|
mail (2.6.6)
|
||||||
mime-types (>= 1.16, < 4)
|
mime-types (>= 1.16, < 4)
|
||||||
method_source (0.8.2)
|
method_source (0.8.2)
|
||||||
microformats2 (3.1.0)
|
microformats2 (3.1.0)
|
||||||
@@ -235,22 +236,22 @@ GEM
|
|||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2016.0521)
|
mime-types-data (3.2016.0521)
|
||||||
mimemagic (0.3.2)
|
mimemagic (0.3.2)
|
||||||
mini_portile2 (2.1.0)
|
mini_portile2 (2.2.0)
|
||||||
minitest (5.10.2)
|
minitest (5.10.2)
|
||||||
msgpack (1.1.0)
|
msgpack (1.1.0)
|
||||||
multi_json (1.12.1)
|
multi_json (1.12.1)
|
||||||
net-scp (1.2.1)
|
net-scp (1.2.1)
|
||||||
net-ssh (>= 2.6.5)
|
net-ssh (>= 2.6.5)
|
||||||
net-ssh (4.1.0)
|
net-ssh (4.1.0)
|
||||||
nio4r (2.0.0)
|
nio4r (2.1.0)
|
||||||
nokogiri (1.7.2)
|
nokogiri (1.8.0)
|
||||||
mini_portile2 (~> 2.1.0)
|
mini_portile2 (~> 2.2.0)
|
||||||
nokogumbo (1.4.11)
|
nokogumbo (1.4.13)
|
||||||
nokogiri
|
nokogiri
|
||||||
oj (3.0.9)
|
oj (3.1.0)
|
||||||
openssl (2.0.3)
|
openssl (2.0.3)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
ostatus2 (2.0.0)
|
ostatus2 (2.0.1)
|
||||||
addressable (~> 2.4)
|
addressable (~> 2.4)
|
||||||
http (~> 2.0)
|
http (~> 2.0)
|
||||||
nokogiri (~> 1.6)
|
nokogiri (~> 1.6)
|
||||||
@@ -273,7 +274,7 @@ GEM
|
|||||||
pg (0.20.0)
|
pg (0.20.0)
|
||||||
pghero (1.7.0)
|
pghero (1.7.0)
|
||||||
activerecord
|
activerecord
|
||||||
pkg-config (1.2.0)
|
pkg-config (1.2.3)
|
||||||
powerpack (0.1.1)
|
powerpack (0.1.1)
|
||||||
pry (0.10.4)
|
pry (0.10.4)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
@@ -282,7 +283,9 @@ GEM
|
|||||||
pry-rails (0.3.6)
|
pry-rails (0.3.6)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.10.4)
|
||||||
public_suffix (2.0.5)
|
public_suffix (2.0.5)
|
||||||
puma (3.8.2)
|
puma (3.9.1)
|
||||||
|
pundit (1.1.0)
|
||||||
|
activesupport (>= 3.0.0)
|
||||||
rabl (0.13.1)
|
rabl (0.13.1)
|
||||||
activesupport (>= 2.3.14)
|
activesupport (>= 2.3.14)
|
||||||
rack (2.0.3)
|
rack (2.0.3)
|
||||||
@@ -294,17 +297,17 @@ GEM
|
|||||||
rack-test (0.6.3)
|
rack-test (0.6.3)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rack-timeout (0.4.2)
|
rack-timeout (0.4.2)
|
||||||
rails (5.0.3)
|
rails (5.1.1)
|
||||||
actioncable (= 5.0.3)
|
actioncable (= 5.1.1)
|
||||||
actionmailer (= 5.0.3)
|
actionmailer (= 5.1.1)
|
||||||
actionpack (= 5.0.3)
|
actionpack (= 5.1.1)
|
||||||
actionview (= 5.0.3)
|
actionview (= 5.1.1)
|
||||||
activejob (= 5.0.3)
|
activejob (= 5.1.1)
|
||||||
activemodel (= 5.0.3)
|
activemodel (= 5.1.1)
|
||||||
activerecord (= 5.0.3)
|
activerecord (= 5.1.1)
|
||||||
activesupport (= 5.0.3)
|
activesupport (= 5.1.1)
|
||||||
bundler (>= 1.3.0, < 2.0)
|
bundler (>= 1.3.0, < 2.0)
|
||||||
railties (= 5.0.3)
|
railties (= 5.1.1)
|
||||||
sprockets-rails (>= 2.0.0)
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-controller-testing (1.0.2)
|
rails-controller-testing (1.0.2)
|
||||||
actionpack (~> 5.x, >= 5.0.1)
|
actionpack (~> 5.x, >= 5.0.1)
|
||||||
@@ -320,15 +323,15 @@ GEM
|
|||||||
railties (~> 5.0)
|
railties (~> 5.0)
|
||||||
rails-settings-cached (0.6.5)
|
rails-settings-cached (0.6.5)
|
||||||
rails (>= 4.2.0)
|
rails (>= 4.2.0)
|
||||||
railties (5.0.3)
|
railties (5.1.1)
|
||||||
actionpack (= 5.0.3)
|
actionpack (= 5.1.1)
|
||||||
activesupport (= 5.0.3)
|
activesupport (= 5.1.1)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
rainbow (2.2.2)
|
rainbow (2.2.2)
|
||||||
rake
|
rake
|
||||||
rake (11.3.0)
|
rake (12.0.0)
|
||||||
redis (3.3.3)
|
redis (3.3.3)
|
||||||
redis-actionpack (5.0.1)
|
redis-actionpack (5.0.1)
|
||||||
actionpack (>= 4.0, < 6)
|
actionpack (>= 4.0, < 6)
|
||||||
@@ -374,7 +377,8 @@ GEM
|
|||||||
rspec-core (~> 3.0, >= 3.0.0)
|
rspec-core (~> 3.0, >= 3.0.0)
|
||||||
sidekiq (>= 2.4.0)
|
sidekiq (>= 2.4.0)
|
||||||
rspec-support (3.6.0)
|
rspec-support (3.6.0)
|
||||||
rubocop (0.48.1)
|
rubocop (0.49.1)
|
||||||
|
parallel (~> 1.10)
|
||||||
parser (>= 2.3.3.1, < 3.0)
|
parser (>= 2.3.3.1, < 3.0)
|
||||||
powerpack (~> 0.1)
|
powerpack (~> 0.1)
|
||||||
rainbow (>= 1.99.1, < 3.0)
|
rainbow (>= 1.99.1, < 3.0)
|
||||||
@@ -382,10 +386,10 @@ GEM
|
|||||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||||
ruby-oembed (0.12.0)
|
ruby-oembed (0.12.0)
|
||||||
ruby-progressbar (1.8.1)
|
ruby-progressbar (1.8.1)
|
||||||
rufus-scheduler (3.4.0)
|
rufus-scheduler (3.4.2)
|
||||||
et-orbi (~> 1.0)
|
et-orbi (~> 1.0)
|
||||||
safe_yaml (1.0.4)
|
safe_yaml (1.0.4)
|
||||||
sanitize (4.4.0)
|
sanitize (4.5.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.4.4)
|
nokogiri (>= 1.4.4)
|
||||||
nokogumbo (~> 1.4.1)
|
nokogumbo (~> 1.4.1)
|
||||||
@@ -393,12 +397,15 @@ GEM
|
|||||||
scss_lint (0.53.0)
|
scss_lint (0.53.0)
|
||||||
rake (>= 0.9, < 13)
|
rake (>= 0.9, < 13)
|
||||||
sass (~> 3.4.20)
|
sass (~> 3.4.20)
|
||||||
sidekiq (5.0.0)
|
sidekiq (5.0.2)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
connection_pool (~> 2.2, >= 2.2.0)
|
connection_pool (~> 2.2, >= 2.2.0)
|
||||||
rack-protection (>= 1.5.0)
|
rack-protection (>= 1.5.0)
|
||||||
redis (~> 3.3, >= 3.3.3)
|
redis (~> 3.3, >= 3.3.3)
|
||||||
sidekiq-scheduler (2.1.4)
|
sidekiq-bulk (0.1.1)
|
||||||
|
activesupport
|
||||||
|
sidekiq
|
||||||
|
sidekiq-scheduler (2.1.5)
|
||||||
redis (~> 3)
|
redis (~> 3)
|
||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 3)
|
sidekiq (>= 3)
|
||||||
@@ -454,14 +461,14 @@ GEM
|
|||||||
addressable (>= 2.3.6)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff
|
hashdiff
|
||||||
webpacker (1.2)
|
webpacker (2.0)
|
||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
multi_json (~> 1.2)
|
multi_json (~> 1.2)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
websocket-driver (0.6.5)
|
websocket-driver (0.6.5)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.2)
|
websocket-extensions (0.1.2)
|
||||||
xpath (2.0.0)
|
xpath (2.1.0)
|
||||||
nokogiri (~> 1.3)
|
nokogiri (~> 1.3)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
@@ -484,6 +491,7 @@ DEPENDENCIES
|
|||||||
capistrano-yarn (~> 2.0)
|
capistrano-yarn (~> 2.0)
|
||||||
capybara (~> 2.14)
|
capybara (~> 2.14)
|
||||||
cld3 (~> 3.1)
|
cld3 (~> 3.1)
|
||||||
|
climate_control (~> 0.2)
|
||||||
devise (~> 4.2)
|
devise (~> 4.2)
|
||||||
devise-two-factor (~> 3.0)
|
devise-two-factor (~> 3.0)
|
||||||
doorkeeper (~> 4.2)
|
doorkeeper (~> 4.2)
|
||||||
@@ -518,11 +526,12 @@ DEPENDENCIES
|
|||||||
pkg-config (~> 1.2)
|
pkg-config (~> 1.2)
|
||||||
pry-rails (~> 0.3)
|
pry-rails (~> 0.3)
|
||||||
puma (~> 3.8)
|
puma (~> 3.8)
|
||||||
|
pundit (~> 1.1)
|
||||||
rabl (~> 0.13)
|
rabl (~> 0.13)
|
||||||
rack-attack (~> 5.0)
|
rack-attack (~> 5.0)
|
||||||
rack-cors (~> 0.4)
|
rack-cors (~> 0.4)
|
||||||
rack-timeout (~> 0.4)
|
rack-timeout (~> 0.4)
|
||||||
rails (~> 5.0)
|
rails (~> 5.1.0)
|
||||||
rails-controller-testing (~> 1.0)
|
rails-controller-testing (~> 1.0)
|
||||||
rails-i18n (~> 5.0)
|
rails-i18n (~> 5.0)
|
||||||
rails-settings-cached (~> 0.6)
|
rails-settings-cached (~> 0.6)
|
||||||
@@ -532,11 +541,12 @@ DEPENDENCIES
|
|||||||
rqrcode (~> 0.10)
|
rqrcode (~> 0.10)
|
||||||
rspec-rails (~> 3.6)
|
rspec-rails (~> 3.6)
|
||||||
rspec-sidekiq (~> 3.0)
|
rspec-sidekiq (~> 3.0)
|
||||||
rubocop (~> 0.48)
|
rubocop
|
||||||
ruby-oembed (~> 0.12)
|
ruby-oembed (~> 0.12)
|
||||||
sanitize (~> 4.4)
|
sanitize (~> 4.4)
|
||||||
scss_lint (~> 0.53)
|
scss_lint (~> 0.53)
|
||||||
sidekiq (~> 5.0)
|
sidekiq (~> 5.0)
|
||||||
|
sidekiq-bulk (~> 0.1.1)
|
||||||
sidekiq-scheduler (~> 2.1)
|
sidekiq-scheduler (~> 2.1)
|
||||||
sidekiq-unique-jobs (~> 5.0)
|
sidekiq-unique-jobs (~> 5.0)
|
||||||
simple-navigation (~> 4.0)
|
simple-navigation (~> 4.0)
|
||||||
@@ -548,10 +558,10 @@ DEPENDENCIES
|
|||||||
tzinfo-data (~> 1.2017)
|
tzinfo-data (~> 1.2017)
|
||||||
uglifier (~> 3.2)
|
uglifier (~> 3.2)
|
||||||
webmock (~> 3.0)
|
webmock (~> 3.0)
|
||||||
webpacker (~> 1.2)
|
webpacker (~> 2.0)
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 2.4.1p111
|
ruby 2.4.1p111
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.14.6
|
1.15.1
|
||||||
|
@@ -13,7 +13,7 @@ An alternative implementation of the GNU social project. Based on [ActivityStrea
|
|||||||
|
|
||||||
Click on the screenshot to watch a demo of the UI:
|
Click on the screenshot to watch a demo of the UI:
|
||||||
|
|
||||||
[][youtube_demo]
|
[][youtube_demo]
|
||||||
|
|
||||||
[youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU
|
[youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU
|
||||||
|
|
||||||
|
5
Vagrantfile
vendored
5
Vagrantfile
vendored
@@ -42,9 +42,12 @@ sudo apt-get install \
|
|||||||
# Install rvm
|
# Install rvm
|
||||||
read RUBY_VERSION < .ruby-version
|
read RUBY_VERSION < .ruby-version
|
||||||
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
|
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
|
||||||
curl -sSL https://get.rvm.io | bash -s stable --ruby=$RUBY_VERSION
|
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
|
||||||
source /home/vagrant/.rvm/scripts/rvm
|
source /home/vagrant/.rvm/scripts/rvm
|
||||||
|
|
||||||
|
# Install Ruby
|
||||||
|
rvm install ruby-$RUBY_VERSION
|
||||||
|
|
||||||
# Configure database
|
# Configure database
|
||||||
sudo -u postgres createuser -U postgres vagrant -s
|
sudo -u postgres createuser -U postgres vagrant -s
|
||||||
sudo -u postgres createdb -U postgres mastodon_development
|
sudo -u postgres createdb -U postgres mastodon_development
|
||||||
|
@@ -6,12 +6,12 @@ class AccountsController < ApplicationController
|
|||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
@statuses = @account.statuses.permitted_for(@account, current_account).order(id: :desc).paginate_by_max_id(20, params[:max_id], params[:since_id])
|
@statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||||
@statuses = cache_collection(@statuses, Status)
|
@statuses = cache_collection(@statuses, Status)
|
||||||
end
|
end
|
||||||
|
|
||||||
format.atom do
|
format.atom do
|
||||||
@entries = @account.stream_entries.order(id: :desc).where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
@entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||||
render xml: AtomSerializer.render(AtomSerializer.new.feed(@account, @entries.to_a))
|
render xml: AtomSerializer.render(AtomSerializer.new.feed(@account, @entries.to_a))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -2,16 +2,43 @@
|
|||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class AccountsController < BaseController
|
class AccountsController < BaseController
|
||||||
|
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload]
|
||||||
|
before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = filtered_accounts.page(params[:page])
|
@accounts = filtered_accounts.page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show; end
|
||||||
@account = Account.find(params[:id])
|
|
||||||
|
def subscribe
|
||||||
|
Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
|
||||||
|
redirect_to admin_account_path(@account.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unsubscribe
|
||||||
|
UnsubscribeService.new.call(@account)
|
||||||
|
redirect_to admin_account_path(@account.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def redownload
|
||||||
|
@account.avatar = @account.avatar_remote_url
|
||||||
|
@account.header = @account.header_remote_url
|
||||||
|
@account.save!
|
||||||
|
|
||||||
|
redirect_to admin_account_path(@account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Account.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def require_remote_account!
|
||||||
|
redirect_to admin_account_path(@account.id) if @account.local?
|
||||||
|
end
|
||||||
|
|
||||||
def filtered_accounts
|
def filtered_accounts
|
||||||
AccountFilter.new(filter_params).results
|
AccountFilter.new(filter_params).results
|
||||||
end
|
end
|
||||||
|
@@ -3,13 +3,18 @@
|
|||||||
module Admin
|
module Admin
|
||||||
class InstancesController < BaseController
|
class InstancesController < BaseController
|
||||||
def index
|
def index
|
||||||
@instances = ordered_instances.page(params[:page])
|
@instances = ordered_instances
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def paginated_instances
|
||||||
|
Account.remote.by_domain_accounts.page(params[:page])
|
||||||
|
end
|
||||||
|
helper_method :paginated_instances
|
||||||
|
|
||||||
def ordered_instances
|
def ordered_instances
|
||||||
Account.remote.by_domain_accounts
|
paginated_instances.map { |account| Instance.new(account) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Admin
|
|
||||||
class PubsubhubbubController < BaseController
|
|
||||||
def index
|
|
||||||
@subscriptions = Subscription.order(id: :desc).includes(:account).page(params[:page])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@@ -2,17 +2,34 @@
|
|||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class ReportedStatusesController < BaseController
|
class ReportedStatusesController < BaseController
|
||||||
def destroy
|
include Authorization
|
||||||
status = Status.find params[:id]
|
|
||||||
|
|
||||||
RemovalWorker.perform_async(status.id)
|
before_action :set_report
|
||||||
redirect_to admin_report_path(report)
|
before_action :set_status
|
||||||
|
|
||||||
|
def update
|
||||||
|
@status.update(status_params)
|
||||||
|
redirect_to admin_report_path(@report)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
authorize @status, :destroy?
|
||||||
|
RemovalWorker.perform_async(@status.id)
|
||||||
|
redirect_to admin_report_path(@report)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def report
|
def status_params
|
||||||
Report.find(params[:report_id])
|
params.require(:status).permit(:sensitive)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_report
|
||||||
|
@report = Report.find(params[:report_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_status
|
||||||
|
@status = @report.statuses.find(params[:id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
19
app/controllers/admin/subscriptions_controller.rb
Normal file
19
app/controllers/admin/subscriptions_controller.rb
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
class SubscriptionsController < BaseController
|
||||||
|
def index
|
||||||
|
@subscriptions = ordered_subscriptions.page(requested_page)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ordered_subscriptions
|
||||||
|
Subscription.order(id: :desc).includes(:account)
|
||||||
|
end
|
||||||
|
|
||||||
|
def requested_page
|
||||||
|
params[:page].to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@@ -1,6 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::Activitypub::ActivitiesController < ApiController
|
class Api::ActivityPub::ActivitiesController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
# before_action :set_follow, only: [:show_follow]
|
# before_action :set_follow, only: [:show_follow]
|
||||||
before_action :set_status, only: [:show_status]
|
before_action :set_status, only: [:show_status]
|
||||||
|
|
||||||
@@ -8,7 +10,7 @@ class Api::Activitypub::ActivitiesController < ApiController
|
|||||||
|
|
||||||
# Show a status in AS2 format, as either an Announce (reblog) or a Create (post) activity.
|
# Show a status in AS2 format, as either an Announce (reblog) or a Create (post) activity.
|
||||||
def show_status
|
def show_status
|
||||||
return forbidden unless @status.permitted?
|
authorize @status, :show?
|
||||||
|
|
||||||
if @status.reblog?
|
if @status.reblog?
|
||||||
render :show_status_announce
|
render :show_status_announce
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::Activitypub::NotesController < ApiController
|
class Api::ActivityPub::NotesController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
|
|
||||||
respond_to :activitystreams2
|
respond_to :activitystreams2
|
||||||
|
|
||||||
def show
|
def show
|
||||||
forbidden unless @status.permitted?
|
authorize @status, :show?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::Activitypub::OutboxController < ApiController
|
class Api::ActivityPub::OutboxController < Api::BaseController
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
|
||||||
respond_to :activitystreams2
|
respond_to :activitystreams2
|
||||||
|
@@ -1,16 +1,14 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ApiController < ApplicationController
|
class Api::BaseController < ApplicationController
|
||||||
DEFAULT_STATUSES_LIMIT = 20
|
DEFAULT_STATUSES_LIMIT = 20
|
||||||
DEFAULT_ACCOUNTS_LIMIT = 40
|
DEFAULT_ACCOUNTS_LIMIT = 40
|
||||||
|
|
||||||
protect_from_forgery with: :null_session
|
include RateLimitHeaders
|
||||||
|
|
||||||
skip_before_action :verify_authenticity_token
|
skip_before_action :verify_authenticity_token
|
||||||
skip_before_action :store_current_location
|
skip_before_action :store_current_location
|
||||||
|
|
||||||
before_action :set_rate_limit_headers
|
|
||||||
|
|
||||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||||
render json: { error: e.to_s }, status: 422
|
render json: { error: e.to_s }, status: 422
|
||||||
end
|
end
|
||||||
@@ -45,17 +43,6 @@ class ApiController < ApplicationController
|
|||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def set_rate_limit_headers
|
|
||||||
return if request.env['rack.attack.throttle_data'].nil?
|
|
||||||
|
|
||||||
now = Time.now.utc
|
|
||||||
match_data = request.env['rack.attack.throttle_data']['api']
|
|
||||||
|
|
||||||
response.headers['X-RateLimit-Limit'] = match_data[:limit].to_s
|
|
||||||
response.headers['X-RateLimit-Remaining'] = (match_data[:limit] - match_data[:count]).to_s
|
|
||||||
response.headers['X-RateLimit-Reset'] = (now + (match_data[:period] - now.to_i % match_data[:period])).iso8601(6)
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_pagination_headers(next_path = nil, prev_path = nil)
|
def set_pagination_headers(next_path = nil, prev_path = nil)
|
||||||
links = []
|
links = []
|
||||||
links << [next_path, [%w(rel next)]] if next_path
|
links << [next_path, [%w(rel next)]] if next_path
|
@@ -1,33 +1,25 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::OEmbedController < ApiController
|
class Api::OEmbedController < Api::BaseController
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@stream_entry = stream_entry_from_url(params[:url])
|
@stream_entry = find_stream_entry.stream_entry
|
||||||
@width = params[:maxwidth].present? ? params[:maxwidth].to_i : 400
|
@width = maxwidth_or_default
|
||||||
@height = params[:maxheight].present? ? params[:maxheight].to_i : nil
|
@height = maxheight_or_default
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def stream_entry_from_url(url)
|
def find_stream_entry
|
||||||
params = Rails.application.routes.recognize_path(url)
|
StreamEntryFinder.new(params[:url])
|
||||||
|
|
||||||
raise ActiveRecord::RecordNotFound unless recognized_stream_entry_url?(params)
|
|
||||||
|
|
||||||
stream_entry(params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def recognized_stream_entry_url?(params)
|
def maxwidth_or_default
|
||||||
%w(stream_entries statuses).include?(params[:controller]) && params[:action] == 'show'
|
(params[:maxwidth].presence || 400).to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
def stream_entry(params)
|
def maxheight_or_default
|
||||||
if params[:controller] == 'stream_entries'
|
params[:maxheight].present? ? params[:maxheight].to_i : nil
|
||||||
StreamEntry.find(params[:id])
|
|
||||||
else
|
|
||||||
Status.find(params[:id]).stream_entry
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::PushController < ApiController
|
class Api::PushController < Api::BaseController
|
||||||
def update
|
def update
|
||||||
response, status = process_push_request
|
response, status = process_push_request
|
||||||
render plain: response, status: status
|
render plain: response, status: status
|
||||||
|
@@ -1,14 +1,12 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::SalmonController < ApiController
|
class Api::SalmonController < Api::BaseController
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
respond_to :txt
|
respond_to :txt
|
||||||
|
|
||||||
def update
|
def update
|
||||||
payload = request.body.read
|
if verify_payload?
|
||||||
|
process_salmon
|
||||||
if !payload.nil? && verify?(payload)
|
|
||||||
SalmonWorker.perform_async(@account.id, payload.force_encoding('UTF-8'))
|
|
||||||
head 201
|
head 201
|
||||||
else
|
else
|
||||||
head 202
|
head 202
|
||||||
@@ -21,7 +19,15 @@ class Api::SalmonController < ApiController
|
|||||||
@account = Account.find(params[:id])
|
@account = Account.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify?(payload)
|
def payload
|
||||||
VerifySalmonService.new.call(payload)
|
@_payload ||= request.body.read
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_payload?
|
||||||
|
payload.present? && VerifySalmonService.new.call(payload)
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_salmon
|
||||||
|
SalmonWorker.perform_async(@account.id, payload.force_encoding('UTF-8'))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -1,22 +1,19 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::SubscriptionsController < ApiController
|
class Api::SubscriptionsController < Api::BaseController
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
respond_to :txt
|
respond_to :txt
|
||||||
|
|
||||||
def show
|
def show
|
||||||
if @account.subscription(api_subscription_url(@account.id)).valid?(params['hub.topic'])
|
if subscription.valid?(params['hub.topic'])
|
||||||
@account.update(subscription_expires_at: Time.now.utc + (params['hub.lease_seconds'] || 86_400).to_i.seconds)
|
@account.update(subscription_expires_at: future_expires)
|
||||||
render plain: HTMLEntities.new.encode(params['hub.challenge']), status: 200
|
render plain: encoded_challenge, status: 200
|
||||||
else
|
else
|
||||||
head 404
|
head 404
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
body = request.body.read
|
|
||||||
subscription = @account.subscription(api_subscription_url(@account.id))
|
|
||||||
|
|
||||||
if subscription.verify(body, request.headers['HTTP_X_HUB_SIGNATURE'])
|
if subscription.verify(body, request.headers['HTTP_X_HUB_SIGNATURE'])
|
||||||
ProcessingWorker.perform_async(@account.id, body.force_encoding('UTF-8'))
|
ProcessingWorker.perform_async(@account.id, body.force_encoding('UTF-8'))
|
||||||
end
|
end
|
||||||
@@ -26,6 +23,28 @@ class Api::SubscriptionsController < ApiController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def subscription
|
||||||
|
@_subscription ||= @account.subscription(
|
||||||
|
api_subscription_url(@account.id)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def body
|
||||||
|
@_body ||= request.body.read
|
||||||
|
end
|
||||||
|
|
||||||
|
def encoded_challenge
|
||||||
|
HTMLEntities.new.encode(params['hub.challenge'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def future_expires
|
||||||
|
Time.now.utc + lease_seconds_or_default
|
||||||
|
end
|
||||||
|
|
||||||
|
def lease_seconds_or_default
|
||||||
|
(params['hub.lease_seconds'] || 86_400).to_i.seconds
|
||||||
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
@account = Account.find(params[:id])
|
@account = Account.find(params[:id])
|
||||||
end
|
end
|
||||||
|
23
app/controllers/api/v1/accounts/credentials_controller.rb
Normal file
23
app/controllers/api/v1/accounts/credentials_controller.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Accounts::CredentialsController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :write }, only: [:update]
|
||||||
|
before_action :require_user!
|
||||||
|
|
||||||
|
def show
|
||||||
|
@account = current_account
|
||||||
|
render 'api/v1/accounts/show'
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
current_account.update!(account_params)
|
||||||
|
@account = current_account
|
||||||
|
render 'api/v1/accounts/show'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def account_params
|
||||||
|
params.permit(:display_name, :note, :avatar, :header)
|
||||||
|
end
|
||||||
|
end
|
@@ -0,0 +1,68 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read }
|
||||||
|
before_action :set_account
|
||||||
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def index
|
||||||
|
@accounts = load_accounts
|
||||||
|
render 'api/v1/accounts/index'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Account.find(params[:account_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_accounts
|
||||||
|
default_accounts.merge(paginated_follows).to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_accounts
|
||||||
|
Account.includes(:active_relationships).references(:active_relationships)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_follows
|
||||||
|
Follow.where(target_account: @account).paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
if records_continue?
|
||||||
|
api_v1_account_followers_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless @accounts.empty?
|
||||||
|
api_v1_account_followers_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@accounts.last.active_relationships.first.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@accounts.first.active_relationships.first.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.permit(:limit).merge(core_params)
|
||||||
|
end
|
||||||
|
end
|
@@ -0,0 +1,68 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read }
|
||||||
|
before_action :set_account
|
||||||
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def index
|
||||||
|
@accounts = load_accounts
|
||||||
|
render 'api/v1/accounts/index'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Account.find(params[:account_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_accounts
|
||||||
|
default_accounts.merge(paginated_follows).to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_accounts
|
||||||
|
Account.includes(:passive_relationships).references(:passive_relationships)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_follows
|
||||||
|
Follow.where(account: @account).paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
if records_continue?
|
||||||
|
api_v1_account_following_index_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless @accounts.empty?
|
||||||
|
api_v1_account_following_index_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@accounts.last.passive_relationships.first.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@accounts.first.passive_relationships.first.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.permit(:limit).merge(core_params)
|
||||||
|
end
|
||||||
|
end
|
24
app/controllers/api/v1/accounts/relationships_controller.rb
Normal file
24
app/controllers/api/v1/accounts/relationships_controller.rb
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Accounts::RelationshipsController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read }
|
||||||
|
before_action :require_user!
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def index
|
||||||
|
@accounts = Account.where(id: account_ids).select('id')
|
||||||
|
@following = Account.following_map(account_ids, current_user.account_id)
|
||||||
|
@followed_by = Account.followed_by_map(account_ids, current_user.account_id)
|
||||||
|
@blocking = Account.blocking_map(account_ids, current_user.account_id)
|
||||||
|
@muting = Account.muting_map(account_ids, current_user.account_id)
|
||||||
|
@requested = Account.requested_map(account_ids, current_user.account_id)
|
||||||
|
@domain_blocking = Account.domain_blocking_map(account_ids, current_user.account_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def account_ids
|
||||||
|
@_account_ids ||= Array(params[:id]).map(&:to_i)
|
||||||
|
end
|
||||||
|
end
|
29
app/controllers/api/v1/accounts/search_controller.rb
Normal file
29
app/controllers/api/v1/accounts/search_controller.rb
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Accounts::SearchController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read }
|
||||||
|
before_action :require_user!
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def show
|
||||||
|
@accounts = account_search
|
||||||
|
|
||||||
|
render 'api/v1/accounts/index'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def account_search
|
||||||
|
AccountSearchService.new.call(
|
||||||
|
params[:q],
|
||||||
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
|
resolving_search?,
|
||||||
|
current_account
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolving_search?
|
||||||
|
params[:resolve] == 'true'
|
||||||
|
end
|
||||||
|
end
|
92
app/controllers/api/v1/accounts/statuses_controller.rb
Normal file
92
app/controllers/api/v1/accounts/statuses_controller.rb
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read }
|
||||||
|
before_action :set_account
|
||||||
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def index
|
||||||
|
@statuses = load_statuses
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Account.find(params[:account_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_statuses
|
||||||
|
cached_account_statuses.tap do |statuses|
|
||||||
|
set_maps(statuses)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_account_statuses
|
||||||
|
cache_collection account_statuses, Status
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_statuses
|
||||||
|
default_statuses.tap do |statuses|
|
||||||
|
statuses.merge!(only_media_scope) if params[:only_media]
|
||||||
|
statuses.merge!(no_replies_scope) if params[:exclude_replies]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_statuses
|
||||||
|
permitted_account_statuses.paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted_account_statuses
|
||||||
|
@account.statuses.permitted_for(@account, current_account)
|
||||||
|
end
|
||||||
|
|
||||||
|
def only_media_scope
|
||||||
|
Status.where(id: account_media_status_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_media_status_ids
|
||||||
|
@account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
||||||
|
end
|
||||||
|
|
||||||
|
def no_replies_scope
|
||||||
|
Status.without_replies
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
if records_continue?
|
||||||
|
api_v1_account_statuses_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless @statuses.empty?
|
||||||
|
api_v1_account_statuses_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@statuses.size == limit_param(DEFAULT_STATUSES_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@statuses.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@statuses.first.id
|
||||||
|
end
|
||||||
|
end
|
@@ -1,73 +1,15 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::AccountsController < ApiController
|
class Api::V1::AccountsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock, :mute, :unmute, :update_credentials]
|
before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
|
||||||
before_action -> { doorkeeper_authorize! :follow }, only: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
|
before_action -> { doorkeeper_authorize! :follow }, only: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
|
||||||
before_action -> { doorkeeper_authorize! :write }, only: [:update_credentials]
|
before_action :require_user!, except: [:show]
|
||||||
before_action :require_user!, except: [:show, :following, :followers, :statuses]
|
before_action :set_account
|
||||||
before_action :set_account, except: [:verify_credentials, :update_credentials, :suggestions, :search]
|
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
def verify_credentials
|
|
||||||
@account = current_user.account
|
|
||||||
render :show
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_credentials
|
|
||||||
current_account.update!(account_params)
|
|
||||||
@account = current_account
|
|
||||||
render :show
|
|
||||||
end
|
|
||||||
|
|
||||||
def following
|
|
||||||
@accounts = Account.includes(:followers)
|
|
||||||
.references(:followers)
|
|
||||||
.merge(Follow.where(account: @account)
|
|
||||||
.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]))
|
|
||||||
.to_a
|
|
||||||
|
|
||||||
next_path = following_api_v1_account_url(pagination_params(max_id: @accounts.last.followers.last.id)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
|
||||||
prev_path = following_api_v1_account_url(pagination_params(since_id: @accounts.first.followers.first.id)) unless @accounts.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
|
|
||||||
render :index
|
|
||||||
end
|
|
||||||
|
|
||||||
def followers
|
|
||||||
@accounts = Account.includes(:following)
|
|
||||||
.references(:following)
|
|
||||||
.merge(Follow.where(target_account: @account)
|
|
||||||
.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
|
||||||
params[:max_id],
|
|
||||||
params[:since_id]))
|
|
||||||
.to_a
|
|
||||||
|
|
||||||
next_path = followers_api_v1_account_url(pagination_params(max_id: @accounts.last.following.last.id)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
|
||||||
prev_path = followers_api_v1_account_url(pagination_params(since_id: @accounts.first.following.first.id)) unless @accounts.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
|
|
||||||
render :index
|
|
||||||
end
|
|
||||||
|
|
||||||
def statuses
|
|
||||||
@statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
|
||||||
@statuses = @statuses.where(id: MediaAttachment.where(account: @account).where.not(status_id: nil).reorder('').select('distinct status_id')) if params[:only_media]
|
|
||||||
@statuses = @statuses.without_replies if params[:exclude_replies]
|
|
||||||
@statuses = cache_collection(@statuses, Status)
|
|
||||||
|
|
||||||
set_maps(@statuses)
|
|
||||||
|
|
||||||
next_path = statuses_api_v1_account_url(statuses_pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
|
|
||||||
prev_path = statuses_api_v1_account_url(statuses_pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def follow
|
def follow
|
||||||
FollowService.new.call(current_user.account, @account.acct)
|
FollowService.new.call(current_user.account, @account.acct)
|
||||||
set_relationship
|
set_relationship
|
||||||
@@ -111,24 +53,6 @@ class Api::V1::AccountsController < ApiController
|
|||||||
render :relationship
|
render :relationship
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationships
|
|
||||||
ids = params[:id].is_a?(Enumerable) ? params[:id].map(&:to_i) : [params[:id].to_i]
|
|
||||||
|
|
||||||
@accounts = Account.where(id: ids).select('id')
|
|
||||||
@following = Account.following_map(ids, current_user.account_id)
|
|
||||||
@followed_by = Account.followed_by_map(ids, current_user.account_id)
|
|
||||||
@blocking = Account.blocking_map(ids, current_user.account_id)
|
|
||||||
@muting = Account.muting_map(ids, current_user.account_id)
|
|
||||||
@requested = Account.requested_map(ids, current_user.account_id)
|
|
||||||
@domain_blocking = Account.domain_blocking_map(ids, current_user.account_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def search
|
|
||||||
@accounts = AccountSearchService.new.call(params[:q], limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:resolve] == 'true', current_account)
|
|
||||||
|
|
||||||
render :index
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
@@ -143,16 +67,4 @@ class Api::V1::AccountsController < ApiController
|
|||||||
@requested = Account.requested_map([@account.id], current_user.account_id)
|
@requested = Account.requested_map([@account.id], current_user.account_id)
|
||||||
@domain_blocking = Account.domain_blocking_map([@account.id], current_user.account_id)
|
@domain_blocking = Account.domain_blocking_map([@account.id], current_user.account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
|
||||||
params.permit(:limit).merge(core_params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def statuses_pagination_params(core_params)
|
|
||||||
params.permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_params
|
|
||||||
params.permit(:display_name, :note, :avatar, :header)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@@ -1,14 +1,27 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::AppsController < ApiController
|
class Api::V1::AppsController < Api::BaseController
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@app = Doorkeeper::Application.create!(name: app_params[:client_name], redirect_uri: app_params[:redirect_uris], scopes: (app_params[:scopes] || Doorkeeper.configuration.default_scopes), website: app_params[:website])
|
@app = Doorkeeper::Application.create!(application_options)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def application_options
|
||||||
|
{
|
||||||
|
name: app_params[:client_name],
|
||||||
|
redirect_uri: app_params[:redirect_uris],
|
||||||
|
scopes: app_scopes_or_default,
|
||||||
|
website: app_params[:website],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def app_scopes_or_default
|
||||||
|
app_params[:scopes] || Doorkeeper.configuration.default_scopes
|
||||||
|
end
|
||||||
|
|
||||||
def app_params
|
def app_params
|
||||||
params.permit(:client_name, :redirect_uris, :scopes, :website)
|
params.permit(:client_name, :redirect_uris, :scopes, :website)
|
||||||
end
|
end
|
||||||
|
@@ -1,26 +1,62 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::BlocksController < ApiController
|
class Api::V1::BlocksController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :follow }
|
before_action -> { doorkeeper_authorize! :follow }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = Account.includes(:blocked_by)
|
@accounts = load_accounts
|
||||||
.references(:blocked_by)
|
|
||||||
.merge(Block.where(account: current_account)
|
|
||||||
.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]))
|
|
||||||
.to_a
|
|
||||||
|
|
||||||
next_path = api_v1_blocks_url(pagination_params(max_id: @accounts.last.blocked_by_ids.last)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
|
||||||
prev_path = api_v1_blocks_url(pagination_params(since_id: @accounts.first.blocked_by_ids.first)) unless @accounts.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def load_accounts
|
||||||
|
default_accounts.merge(paginated_blocks).to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_accounts
|
||||||
|
Account.includes(:blocked_by).references(:blocked_by)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_blocks
|
||||||
|
Block.where(account: current_account).paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
if records_continue?
|
||||||
|
api_v1_blocks_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless @accounts.empty?
|
||||||
|
api_v1_blocks_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@accounts.last.blocked_by_ids.last
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@accounts.first.blocked_by_ids.first
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.permit(:limit).merge(core_params)
|
params.permit(:limit).merge(core_params)
|
||||||
end
|
end
|
||||||
|
@@ -1,18 +1,16 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::DomainBlocksController < ApiController
|
class Api::V1::DomainBlocksController < Api::BaseController
|
||||||
|
BLOCK_LIMIT = 100
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :follow }
|
before_action -> { doorkeeper_authorize! :follow }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
after_action :insert_pagination_headers, only: :show
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@blocks = AccountDomainBlock.where(account: current_account).paginate_by_max_id(limit_param(100), params[:max_id], params[:since_id])
|
@blocks = load_domain_blocks
|
||||||
|
|
||||||
next_path = api_v1_domain_blocks_url(pagination_params(max_id: @blocks.last.id)) if @blocks.size == limit_param(100)
|
|
||||||
prev_path = api_v1_domain_blocks_url(pagination_params(since_id: @blocks.first.id)) unless @blocks.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
render json: @blocks.map(&:domain)
|
render json: @blocks.map(&:domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -28,6 +26,46 @@ class Api::V1::DomainBlocksController < ApiController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def load_domain_blocks
|
||||||
|
account_domain_blocks.paginate_by_max_id(
|
||||||
|
limit_param(BLOCK_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_domain_blocks
|
||||||
|
current_account.domain_blocks
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
if records_continue?
|
||||||
|
api_v1_domain_blocks_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless @blocks.empty?
|
||||||
|
api_v1_domain_blocks_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@blocks.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@blocks.first.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@blocks.size == limit_param(BLOCK_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.permit(:limit).merge(core_params)
|
params.permit(:limit).merge(core_params)
|
||||||
end
|
end
|
||||||
|
@@ -1,25 +1,73 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::FavouritesController < ApiController
|
class Api::V1::FavouritesController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read }
|
before_action -> { doorkeeper_authorize! :read }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def index
|
def index
|
||||||
results = Favourite.where(account: current_account).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
@statuses = load_statuses
|
||||||
@statuses = cache_collection(Status.where(id: results.map(&:status_id)), Status)
|
|
||||||
|
|
||||||
set_maps(@statuses)
|
|
||||||
|
|
||||||
next_path = api_v1_favourites_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_STATUSES_LIMIT)
|
|
||||||
prev_path = api_v1_favourites_url(pagination_params(since_id: results.first.id)) unless results.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def load_statuses
|
||||||
|
cached_favourites.tap do |statuses|
|
||||||
|
set_maps(statuses)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_favourites
|
||||||
|
cache_collection(
|
||||||
|
Status.where(
|
||||||
|
id: results.map(&:status_id)
|
||||||
|
),
|
||||||
|
Status
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def results
|
||||||
|
@_results ||= account_favourites.paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_favourites
|
||||||
|
current_account.favourites
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
if records_continue?
|
||||||
|
api_v1_favourites_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless results.empty?
|
||||||
|
api_v1_favourites_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
results.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
results.first.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
results.size == limit_param(DEFAULT_STATUSES_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.permit(:limit).merge(core_params)
|
params.permit(:limit).merge(core_params)
|
||||||
end
|
end
|
||||||
|
@@ -1,34 +1,74 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::FollowRequestsController < ApiController
|
class Api::V1::FollowRequestsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :follow }
|
before_action -> { doorkeeper_authorize! :follow }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
after_action :insert_pagination_headers, only: :index
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = Account.includes(:follow_requests)
|
@accounts = load_accounts
|
||||||
.references(:follow_requests)
|
|
||||||
.merge(FollowRequest.where(target_account: current_account)
|
|
||||||
.paginate_by_max_id(DEFAULT_ACCOUNTS_LIMIT, params[:max_id], params[:since_id]))
|
|
||||||
.to_a
|
|
||||||
|
|
||||||
next_path = api_v1_follow_requests_url(pagination_params(max_id: @accounts.last.follow_requests.last.id)) if @accounts.size == DEFAULT_ACCOUNTS_LIMIT
|
|
||||||
prev_path = api_v1_follow_requests_url(pagination_params(since_id: @accounts.first.follow_requests.first.id)) unless @accounts.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorize
|
def authorize
|
||||||
AuthorizeFollowService.new.call(Account.find(params[:id]), current_account)
|
AuthorizeFollowService.new.call(account, current_account)
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject
|
def reject
|
||||||
RejectFollowService.new.call(Account.find(params[:id]), current_account)
|
RejectFollowService.new.call(account, current_account)
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def account
|
||||||
|
Account.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_accounts
|
||||||
|
default_accounts.merge(paginated_follow_requests).to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_accounts
|
||||||
|
Account.includes(:follow_requests).references(:follow_requests)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_follow_requests
|
||||||
|
FollowRequest.where(target_account: current_account).paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
if records_continue?
|
||||||
|
api_v1_follow_requests_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless @accounts.empty?
|
||||||
|
api_v1_follow_requests_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@accounts.last.follow_requests.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@accounts.first.follow_requests.first.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.permit(:limit).merge(core_params)
|
params.permit(:limit).merge(core_params)
|
||||||
end
|
end
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::FollowsController < ApiController
|
class Api::V1::FollowsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :follow }
|
before_action -> { doorkeeper_authorize! :follow }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::InstancesController < ApiController
|
class Api::V1::InstancesController < Api::BaseController
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::MediaController < ApiController
|
class Api::V1::MediaController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :write }
|
before_action -> { doorkeeper_authorize! :write }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
@@ -10,11 +10,11 @@ class Api::V1::MediaController < ApiController
|
|||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@media = MediaAttachment.create!(account: current_user.account, file: media_params[:file])
|
@media = current_account.media_attachments.create!(file: media_params[:file])
|
||||||
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
|
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
|
||||||
render json: { error: 'File type of uploaded media could not be verified' }, status: 422
|
render json: file_type_error, status: 422
|
||||||
rescue Paperclip::Error
|
rescue Paperclip::Error
|
||||||
render json: { error: 'Error processing thumbnail for uploaded media' }, status: 500
|
render json: processing_error, status: 500
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -22,4 +22,12 @@ class Api::V1::MediaController < ApiController
|
|||||||
def media_params
|
def media_params
|
||||||
params.permit(:file)
|
params.permit(:file)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def file_type_error
|
||||||
|
{ error: 'File type of uploaded media could not be verified' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def processing_error
|
||||||
|
{ error: 'Error processing thumbnail for uploaded media' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@@ -1,26 +1,62 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::MutesController < ApiController
|
class Api::V1::MutesController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :follow }
|
before_action -> { doorkeeper_authorize! :follow }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = Account.includes(:muted_by)
|
@accounts = load_accounts
|
||||||
.references(:muted_by)
|
|
||||||
.merge(Mute.where(account: current_account)
|
|
||||||
.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]))
|
|
||||||
.to_a
|
|
||||||
|
|
||||||
next_path = api_v1_mutes_url(pagination_params(max_id: @accounts.last.muted_by_ids.last)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
|
||||||
prev_path = api_v1_mutes_url(pagination_params(since_id: @accounts.first.muted_by_ids.first)) unless @accounts.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def load_accounts
|
||||||
|
default_accounts.merge(paginated_mutes).to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_accounts
|
||||||
|
Account.includes(:muted_by).references(:muted_by)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_mutes
|
||||||
|
Mute.where(account: current_account).paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
if records_continue?
|
||||||
|
api_v1_mutes_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless @accounts.empty?
|
||||||
|
api_v1_mutes_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@accounts.last.muted_by_ids.last
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@accounts.first.muted_by_ids.first
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.permit(:limit).merge(core_params)
|
params.permit(:limit).merge(core_params)
|
||||||
end
|
end
|
||||||
|
@@ -1,42 +1,83 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::NotificationsController < ApiController
|
class Api::V1::NotificationsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read }
|
before_action -> { doorkeeper_authorize! :read }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
after_action :insert_pagination_headers, only: :index
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
DEFAULT_NOTIFICATIONS_LIMIT = 15
|
DEFAULT_NOTIFICATIONS_LIMIT = 15
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@notifications = Notification.where(account: current_account).browserable(exclude_types).paginate_by_max_id(limit_param(DEFAULT_NOTIFICATIONS_LIMIT), params[:max_id], params[:since_id])
|
@notifications = load_notifications
|
||||||
@notifications = cache_collection(@notifications, Notification)
|
set_maps_for_notification_target_statuses
|
||||||
statuses = @notifications.select { |n| !n.target_status.nil? }.map(&:target_status)
|
|
||||||
|
|
||||||
set_maps(statuses)
|
|
||||||
|
|
||||||
next_path = api_v1_notifications_url(pagination_params(max_id: @notifications.last.id)) unless @notifications.empty?
|
|
||||||
prev_path = api_v1_notifications_url(pagination_params(since_id: @notifications.first.id)) unless @notifications.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@notification = Notification.where(account: current_account).find(params[:id])
|
@notification = current_account.notifications.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear
|
def clear
|
||||||
Notification.where(account: current_account).delete_all
|
current_account.notifications.delete_all
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
def dismiss
|
def dismiss
|
||||||
Notification.find_by!(account: current_account, id: params[:id]).destroy!
|
current_account.notifications.find_by!(id: params[:id]).destroy!
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def load_notifications
|
||||||
|
cache_collection paginated_notifications, Notification
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_notifications
|
||||||
|
browserable_account_notifications.paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def browserable_account_notifications
|
||||||
|
current_account.notifications.browserable(exclude_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_maps_for_notification_target_statuses
|
||||||
|
set_maps target_statuses_from_notifications
|
||||||
|
end
|
||||||
|
|
||||||
|
def target_statuses_from_notifications
|
||||||
|
@notifications.reject { |notification| notification.target_status.nil? }.map(&:target_status)
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
unless @notifications.empty?
|
||||||
|
api_v1_notifications_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless @notifications.empty?
|
||||||
|
api_v1_notifications_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@notifications.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@notifications.first.id
|
||||||
|
end
|
||||||
|
|
||||||
def exclude_types
|
def exclude_types
|
||||||
val = params.permit(exclude_types: [])[:exclude_types] || []
|
val = params.permit(exclude_types: [])[:exclude_types] || []
|
||||||
val = [val] unless val.is_a?(Enumerable)
|
val = [val] unless val.is_a?(Enumerable)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::ReportsController < ApiController
|
class Api::V1::ReportsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read }, except: [:create]
|
before_action -> { doorkeeper_authorize! :read }, except: [:create]
|
||||||
before_action -> { doorkeeper_authorize! :write }, only: [:create]
|
before_action -> { doorkeeper_authorize! :write }, only: [:create]
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
@@ -8,22 +8,32 @@ class Api::V1::ReportsController < ApiController
|
|||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@reports = Report.where(account: current_account)
|
@reports = current_account.reports
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
status_ids = report_params[:status_ids].is_a?(Enumerable) ? report_params[:status_ids] : [report_params[:status_ids]]
|
@report = current_account.reports.create!(
|
||||||
|
target_account: reported_account,
|
||||||
@report = Report.create!(account: current_account,
|
status_ids: reported_status_ids,
|
||||||
target_account: Account.find(report_params[:account_id]),
|
comment: report_params[:comment]
|
||||||
status_ids: Status.find(status_ids).pluck(:id),
|
)
|
||||||
comment: report_params[:comment])
|
|
||||||
|
|
||||||
render :show
|
render :show
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def reported_status_ids
|
||||||
|
Status.find(status_ids).pluck(:id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def status_ids
|
||||||
|
Array(report_params[:status_ids])
|
||||||
|
end
|
||||||
|
|
||||||
|
def reported_account
|
||||||
|
Account.find(report_params[:account_id])
|
||||||
|
end
|
||||||
|
|
||||||
def report_params
|
def report_params
|
||||||
params.permit(:account_id, :comment, status_ids: [])
|
params.permit(:account_id, :comment, status_ids: [])
|
||||||
end
|
end
|
||||||
|
@@ -1,9 +1,26 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::SearchController < ApiController
|
class Api::V1::SearchController < Api::BaseController
|
||||||
|
RESULTS_LIMIT = 5
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@search = OpenStruct.new(SearchService.new.call(params[:q], 5, params[:resolve] == 'true', current_account))
|
@search = OpenStruct.new(search_results)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def search_results
|
||||||
|
SearchService.new.call(
|
||||||
|
params[:q],
|
||||||
|
RESULTS_LIMIT,
|
||||||
|
resolving_search?,
|
||||||
|
current_account
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolving_search?
|
||||||
|
params[:resolve] == 'true'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -0,0 +1,82 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
before_action :authorize_if_got_token
|
||||||
|
before_action :set_status
|
||||||
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def index
|
||||||
|
@accounts = load_accounts
|
||||||
|
render 'api/v1/statuses/accounts'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_accounts
|
||||||
|
default_accounts.merge(paginated_favourites).to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_accounts
|
||||||
|
Account
|
||||||
|
.includes(:favourites)
|
||||||
|
.references(:favourites)
|
||||||
|
.where(favourites: { status_id: @status.id })
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_favourites
|
||||||
|
Favourite.paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
if records_continue?
|
||||||
|
api_v1_status_favourited_by_index_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless @accounts.empty?
|
||||||
|
api_v1_status_favourited_by_index_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@accounts.last.favourites.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@accounts.first.favourites.first.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_status
|
||||||
|
@status = Status.find(params[:status_id])
|
||||||
|
authorize @status, :show?
|
||||||
|
rescue Mastodon::NotPermittedError
|
||||||
|
# Reraise in order to get a 404 instead of a 403 error code
|
||||||
|
raise ActiveRecord::RecordNotFound
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_if_got_token
|
||||||
|
request_token = Doorkeeper::OAuth::Token.from_request(request, *Doorkeeper.configuration.access_token_methods)
|
||||||
|
doorkeeper_authorize! :read if request_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.permit(:limit).merge(core_params)
|
||||||
|
end
|
||||||
|
end
|
38
app/controllers/api/v1/statuses/favourites_controller.rb
Normal file
38
app/controllers/api/v1/statuses/favourites_controller.rb
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Statuses::FavouritesController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
before_action -> { doorkeeper_authorize! :write }
|
||||||
|
before_action :require_user!
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def create
|
||||||
|
@status = favourited_status
|
||||||
|
render 'api/v1/statuses/show'
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@status = requested_status
|
||||||
|
@favourites_map = { @status.id => false }
|
||||||
|
|
||||||
|
UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
|
||||||
|
|
||||||
|
render 'api/v1/statuses/show'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def favourited_status
|
||||||
|
service_result.status.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
def service_result
|
||||||
|
FavouriteService.new.call(current_user.account, requested_status)
|
||||||
|
end
|
||||||
|
|
||||||
|
def requested_status
|
||||||
|
Status.find(params[:status_id])
|
||||||
|
end
|
||||||
|
end
|
41
app/controllers/api/v1/statuses/mutes_controller.rb
Normal file
41
app/controllers/api/v1/statuses/mutes_controller.rb
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Statuses::MutesController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
before_action -> { doorkeeper_authorize! :write }
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_status
|
||||||
|
before_action :set_conversation
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def create
|
||||||
|
current_account.mute_conversation!(@conversation)
|
||||||
|
@mutes_map = { @conversation.id => true }
|
||||||
|
|
||||||
|
render 'api/v1/statuses/show'
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
current_account.unmute_conversation!(@conversation)
|
||||||
|
@mutes_map = { @conversation.id => false }
|
||||||
|
|
||||||
|
render 'api/v1/statuses/show'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_status
|
||||||
|
@status = Status.find(params[:status_id])
|
||||||
|
authorize @status, :show?
|
||||||
|
rescue Mastodon::NotPermittedError
|
||||||
|
# Reraise in order to get a 404 instead of a 403 error code
|
||||||
|
raise ActiveRecord::RecordNotFound
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_conversation
|
||||||
|
@conversation = @status.conversation
|
||||||
|
raise Mastodon::ValidationError if @conversation.nil?
|
||||||
|
end
|
||||||
|
end
|
@@ -0,0 +1,79 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
before_action :authorize_if_got_token
|
||||||
|
before_action :set_status
|
||||||
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def index
|
||||||
|
@accounts = load_accounts
|
||||||
|
render 'api/v1/statuses/accounts'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_accounts
|
||||||
|
default_accounts.merge(paginated_statuses).to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_accounts
|
||||||
|
Account.includes(:statuses).references(:statuses)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_statuses
|
||||||
|
Status.where(reblog_of_id: @status.id).paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
if records_continue?
|
||||||
|
api_v1_status_reblogged_by_index_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
unless @accounts.empty?
|
||||||
|
api_v1_status_reblogged_by_index_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@accounts.last.statuses.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@accounts.first.statuses.first.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_status
|
||||||
|
@status = Status.find(params[:status_id])
|
||||||
|
authorize @status, :show?
|
||||||
|
rescue Mastodon::NotPermittedError
|
||||||
|
# Reraise in order to get a 404 instead of a 403 error code
|
||||||
|
raise ActiveRecord::RecordNotFound
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_if_got_token
|
||||||
|
request_token = Doorkeeper::OAuth::Token.from_request(request, *Doorkeeper.configuration.access_token_methods)
|
||||||
|
doorkeeper_authorize! :read if request_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.permit(:limit).merge(core_params)
|
||||||
|
end
|
||||||
|
end
|
35
app/controllers/api/v1/statuses/reblogs_controller.rb
Normal file
35
app/controllers/api/v1/statuses/reblogs_controller.rb
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Statuses::ReblogsController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
before_action -> { doorkeeper_authorize! :write }
|
||||||
|
before_action :require_user!
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def create
|
||||||
|
@status = ReblogService.new.call(current_user.account, status_for_reblog)
|
||||||
|
render 'api/v1/statuses/show'
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@status = status_for_destroy.reblog
|
||||||
|
@reblogs_map = { @status.id => false }
|
||||||
|
|
||||||
|
authorize status_for_destroy, :unreblog?
|
||||||
|
RemovalWorker.perform_async(status_for_destroy.id)
|
||||||
|
|
||||||
|
render 'api/v1/statuses/show'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def status_for_reblog
|
||||||
|
Status.find params[:status_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
def status_for_destroy
|
||||||
|
current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
|
||||||
|
end
|
||||||
|
end
|
@@ -1,11 +1,12 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::StatusesController < ApiController
|
class Api::V1::StatusesController < Api::BaseController
|
||||||
before_action :authorize_if_got_token, except: [:create, :destroy, :reblog, :unreblog, :favourite, :unfavourite, :mute, :unmute]
|
include Authorization
|
||||||
before_action -> { doorkeeper_authorize! :write }, only: [:create, :destroy, :reblog, :unreblog, :favourite, :unfavourite, :mute, :unmute]
|
|
||||||
before_action :require_user!, except: [:show, :context, :card, :reblogged_by, :favourited_by]
|
before_action :authorize_if_got_token, except: [:create, :destroy]
|
||||||
before_action :set_status, only: [:show, :context, :card, :reblogged_by, :favourited_by, :mute, :unmute]
|
before_action -> { doorkeeper_authorize! :write }, only: [:create, :destroy]
|
||||||
before_action :set_conversation, only: [:mute, :unmute]
|
before_action :require_user!, except: [:show, :context, :card]
|
||||||
|
before_action :set_status, only: [:show, :context, :card]
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
@@ -31,36 +32,6 @@ class Api::V1::StatusesController < ApiController
|
|||||||
render_empty if @card.nil?
|
render_empty if @card.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
def reblogged_by
|
|
||||||
@accounts = Account.includes(:statuses)
|
|
||||||
.references(:statuses)
|
|
||||||
.merge(Status.where(reblog_of_id: @status.id)
|
|
||||||
.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]))
|
|
||||||
.to_a
|
|
||||||
|
|
||||||
next_path = reblogged_by_api_v1_status_url(pagination_params(max_id: @accounts.last.statuses.last.id)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
|
||||||
prev_path = reblogged_by_api_v1_status_url(pagination_params(since_id: @accounts.first.statuses.first.id)) unless @accounts.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
|
|
||||||
render :accounts
|
|
||||||
end
|
|
||||||
|
|
||||||
def favourited_by
|
|
||||||
@accounts = Account.includes(:favourites)
|
|
||||||
.references(:favourites)
|
|
||||||
.where(favourites: { status_id: @status.id })
|
|
||||||
.merge(Favourite.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]))
|
|
||||||
.to_a
|
|
||||||
|
|
||||||
next_path = favourited_by_api_v1_status_url(pagination_params(max_id: @accounts.last.favourites.last.id)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
|
||||||
prev_path = favourited_by_api_v1_status_url(pagination_params(since_id: @accounts.first.favourites.first.id)) unless @accounts.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
|
|
||||||
render :accounts
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@status = PostStatusService.new.call(current_user.account,
|
@status = PostStatusService.new.call(current_user.account,
|
||||||
status_params[:status],
|
status_params[:status],
|
||||||
@@ -77,65 +48,21 @@ class Api::V1::StatusesController < ApiController
|
|||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@status = Status.where(account_id: current_user.account).find(params[:id])
|
@status = Status.where(account_id: current_user.account).find(params[:id])
|
||||||
|
authorize @status, :destroy?
|
||||||
|
|
||||||
RemovalWorker.perform_async(@status.id)
|
RemovalWorker.perform_async(@status.id)
|
||||||
|
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
def reblog
|
|
||||||
@status = ReblogService.new.call(current_user.account, Status.find(params[:id]))
|
|
||||||
render :show
|
|
||||||
end
|
|
||||||
|
|
||||||
def unreblog
|
|
||||||
reblog = Status.where(account_id: current_user.account, reblog_of_id: params[:id]).first!
|
|
||||||
@status = reblog.reblog
|
|
||||||
@reblogs_map = { @status.id => false }
|
|
||||||
|
|
||||||
RemovalWorker.perform_async(reblog.id)
|
|
||||||
|
|
||||||
render :show
|
|
||||||
end
|
|
||||||
|
|
||||||
def favourite
|
|
||||||
@status = FavouriteService.new.call(current_user.account, Status.find(params[:id])).status.reload
|
|
||||||
render :show
|
|
||||||
end
|
|
||||||
|
|
||||||
def unfavourite
|
|
||||||
@status = Status.find(params[:id])
|
|
||||||
@favourites_map = { @status.id => false }
|
|
||||||
|
|
||||||
UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
|
|
||||||
|
|
||||||
render :show
|
|
||||||
end
|
|
||||||
|
|
||||||
def mute
|
|
||||||
current_account.mute_conversation!(@conversation)
|
|
||||||
|
|
||||||
@mutes_map = { @conversation.id => true }
|
|
||||||
|
|
||||||
render :show
|
|
||||||
end
|
|
||||||
|
|
||||||
def unmute
|
|
||||||
current_account.unmute_conversation!(@conversation)
|
|
||||||
|
|
||||||
@mutes_map = { @conversation.id => false }
|
|
||||||
|
|
||||||
render :show
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_status
|
def set_status
|
||||||
@status = Status.find(params[:id])
|
@status = Status.find(params[:id])
|
||||||
raise ActiveRecord::RecordNotFound unless @status.permitted?(current_account)
|
authorize @status, :show?
|
||||||
end
|
rescue Mastodon::NotPermittedError
|
||||||
|
# Reraise in order to get a 404 instead of a 403 error code
|
||||||
def set_conversation
|
raise ActiveRecord::RecordNotFound
|
||||||
@conversation = @status.conversation
|
|
||||||
raise Mastodon::ValidationError if @conversation.nil?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def status_params
|
def status_params
|
||||||
|
15
app/controllers/api/v1/streaming_controller.rb
Normal file
15
app/controllers/api/v1/streaming_controller.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::StreamingController < Api::BaseController
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def index
|
||||||
|
if Rails.configuration.x.streaming_api_base_url != request.host
|
||||||
|
uri = URI.parse(request.url)
|
||||||
|
uri.host = URI.parse(Rails.configuration.x.streaming_api_base_url).host
|
||||||
|
redirect_to uri.to_s, status: 301
|
||||||
|
else
|
||||||
|
raise ActiveRecord::RecordNotFound
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
62
app/controllers/api/v1/timelines/home_controller.rb
Normal file
62
app/controllers/api/v1/timelines/home_controller.rb
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Timelines::HomeController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read }, only: [:show]
|
||||||
|
before_action :require_user!, only: [:show]
|
||||||
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def show
|
||||||
|
@statuses = load_statuses
|
||||||
|
render 'api/v1/timelines/show'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_statuses
|
||||||
|
cached_home_statuses.tap do |statuses|
|
||||||
|
set_maps(statuses)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_home_statuses
|
||||||
|
cache_collection home_statuses, Status
|
||||||
|
end
|
||||||
|
|
||||||
|
def home_statuses
|
||||||
|
account_home_feed.get(
|
||||||
|
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_home_feed
|
||||||
|
Feed.new(:home, current_account)
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.permit(:local, :limit).merge(core_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
api_v1_timelines_home_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
api_v1_timelines_home_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@statuses.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@statuses.first.id
|
||||||
|
end
|
||||||
|
end
|
60
app/controllers/api/v1/timelines/public_controller.rb
Normal file
60
app/controllers/api/v1/timelines/public_controller.rb
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Timelines::PublicController < Api::BaseController
|
||||||
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def show
|
||||||
|
@statuses = load_statuses
|
||||||
|
render 'api/v1/timelines/show'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_statuses
|
||||||
|
cached_public_statuses.tap do |statuses|
|
||||||
|
set_maps(statuses)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_public_statuses
|
||||||
|
cache_collection public_statuses, Status
|
||||||
|
end
|
||||||
|
|
||||||
|
def public_statuses
|
||||||
|
public_timeline_statuses.paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def public_timeline_statuses
|
||||||
|
Status.as_public_timeline(current_account, params[:local])
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.permit(:local, :limit).merge(core_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
api_v1_timelines_public_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
api_v1_timelines_public_url pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@statuses.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@statuses.first.id
|
||||||
|
end
|
||||||
|
end
|
69
app/controllers/api/v1/timelines/tag_controller.rb
Normal file
69
app/controllers/api/v1/timelines/tag_controller.rb
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Timelines::TagController < Api::BaseController
|
||||||
|
before_action :load_tag
|
||||||
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def show
|
||||||
|
@statuses = load_statuses
|
||||||
|
render 'api/v1/timelines/show'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_tag
|
||||||
|
@tag = Tag.find_by(name: params[:id].downcase)
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_statuses
|
||||||
|
cached_tagged_statuses.tap do |statuses|
|
||||||
|
set_maps(statuses)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_tagged_statuses
|
||||||
|
cache_collection tagged_statuses, Status
|
||||||
|
end
|
||||||
|
|
||||||
|
def tagged_statuses
|
||||||
|
if @tag.nil?
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
tag_timeline_statuses.paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def tag_timeline_statuses
|
||||||
|
Status.as_tag_timeline(@tag, current_account, params[:local])
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.permit(:local, :limit).merge(core_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
api_v1_timelines_tag_url params[:id], pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
api_v1_timelines_tag_url params[:id], pagination_params(since_id: pagination_since_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@statuses.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@statuses.first.id
|
||||||
|
end
|
||||||
|
end
|
@@ -1,61 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Api::V1::TimelinesController < ApiController
|
|
||||||
before_action -> { doorkeeper_authorize! :read }, only: [:home]
|
|
||||||
before_action :require_user!, only: [:home]
|
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def home
|
|
||||||
@statuses = Feed.new(:home, current_account).get(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
|
||||||
@statuses = cache_collection(@statuses)
|
|
||||||
|
|
||||||
set_maps(@statuses)
|
|
||||||
|
|
||||||
next_path = api_v1_home_timeline_url(pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
|
|
||||||
prev_path = api_v1_home_timeline_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
|
|
||||||
render :index
|
|
||||||
end
|
|
||||||
|
|
||||||
def public
|
|
||||||
@statuses = Status.as_public_timeline(current_account, params[:local]).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
|
||||||
@statuses = cache_collection(@statuses)
|
|
||||||
|
|
||||||
set_maps(@statuses)
|
|
||||||
|
|
||||||
next_path = api_v1_public_timeline_url(pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
|
|
||||||
prev_path = api_v1_public_timeline_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
|
|
||||||
render :index
|
|
||||||
end
|
|
||||||
|
|
||||||
def tag
|
|
||||||
@tag = Tag.find_by(name: params[:id].downcase)
|
|
||||||
@statuses = @tag.nil? ? [] : Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
|
||||||
@statuses = cache_collection(@statuses)
|
|
||||||
|
|
||||||
set_maps(@statuses)
|
|
||||||
|
|
||||||
next_path = api_v1_hashtag_timeline_url(params[:id], pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
|
|
||||||
prev_path = api_v1_hashtag_timeline_url(params[:id], pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
|
|
||||||
|
|
||||||
set_pagination_headers(next_path, prev_path)
|
|
||||||
|
|
||||||
render :index
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def cache_collection(raw)
|
|
||||||
super(raw, Status)
|
|
||||||
end
|
|
||||||
|
|
||||||
def pagination_params(core_params)
|
|
||||||
params.permit(:local, :limit).merge(core_params)
|
|
||||||
end
|
|
||||||
end
|
|
@@ -1,15 +1,20 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::Web::SettingsController < ApiController
|
class Api::Web::SettingsController < Api::BaseController
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
def update
|
def update
|
||||||
setting = ::Web::Setting.where(user: current_user).first_or_initialize(user: current_user)
|
|
||||||
setting.data = params[:data]
|
setting.data = params[:data]
|
||||||
setting.save!
|
setting.save!
|
||||||
|
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def setting
|
||||||
|
@_setting ||= ::Web::Setting.where(user: current_user).first_or_initialize(user: current_user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@@ -39,7 +39,7 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def check_suspension
|
def check_suspension
|
||||||
head 403 if current_user.account.suspended?
|
forbidden if current_user.account.suspended?
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
@@ -6,6 +6,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||||||
before_action :check_enabled_registrations, only: [:new, :create]
|
before_action :check_enabled_registrations, only: [:new, :create]
|
||||||
before_action :configure_sign_up_params, only: [:create]
|
before_action :configure_sign_up_params, only: [:create]
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def build_resource(hash = nil)
|
def build_resource(hash = nil)
|
||||||
|
@@ -12,13 +12,13 @@ class Auth::SessionsController < Devise::SessionsController
|
|||||||
def create
|
def create
|
||||||
super do |resource|
|
super do |resource|
|
||||||
remember_me(resource)
|
remember_me(resource)
|
||||||
flash[:notice] = nil
|
flash.delete(:notice)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
super
|
super
|
||||||
flash[:notice] = nil
|
flash.delete(:notice)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
@@ -27,7 +27,7 @@ class Auth::SessionsController < Devise::SessionsController
|
|||||||
if session[:otp_user_id]
|
if session[:otp_user_id]
|
||||||
User.find(session[:otp_user_id])
|
User.find(session[:otp_user_id])
|
||||||
elsif user_params[:email]
|
elsif user_params[:email]
|
||||||
User.find_by(email: user_params[:email])
|
User.find_for_authentication(email: user_params[:email])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -35,10 +35,10 @@ class Auth::SessionsController < Devise::SessionsController
|
|||||||
params.require(:user).permit(:email, :password, :otp_attempt)
|
params.require(:user).permit(:email, :password, :otp_attempt)
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_sign_in_path_for(_resource)
|
def after_sign_in_path_for(resource)
|
||||||
last_url = stored_location_for(:user)
|
last_url = stored_location_for(:user)
|
||||||
|
|
||||||
if [about_path].include?(last_url)
|
if home_paths(resource).include?(last_url)
|
||||||
root_path
|
root_path
|
||||||
else
|
else
|
||||||
last_url || root_path
|
last_url || root_path
|
||||||
@@ -81,4 +81,14 @@ class Auth::SessionsController < Devise::SessionsController
|
|||||||
session[:otp_user_id] = user.id
|
session[:otp_user_id] = user.id
|
||||||
render :two_factor
|
render :two_factor
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def home_paths(resource)
|
||||||
|
paths = [about_path]
|
||||||
|
if single_user_mode? && resource.is_a?(User)
|
||||||
|
paths << short_account_path(username: resource.account)
|
||||||
|
end
|
||||||
|
paths
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@@ -40,7 +40,7 @@ class AuthorizeFollowsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def account_from_remote_follow
|
def account_from_remote_follow
|
||||||
FollowRemoteAccountService.new.call(acct_without_prefix)
|
ResolveRemoteAccountService.new.call(acct_without_prefix)
|
||||||
end
|
end
|
||||||
|
|
||||||
def acct_param_is_url?
|
def acct_param_is_url?
|
||||||
|
22
app/controllers/concerns/authorization.rb
Normal file
22
app/controllers/concerns/authorization.rb
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Authorization
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
include Pundit
|
||||||
|
|
||||||
|
def pundit_user
|
||||||
|
current_account
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize(*)
|
||||||
|
super
|
||||||
|
rescue Pundit::NotAuthorizedError
|
||||||
|
raise Mastodon::NotPermittedError
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_with(user, record, query)
|
||||||
|
Pundit.authorize(user, record, query)
|
||||||
|
rescue Pundit::NotAuthorizedError
|
||||||
|
raise Mastodon::NotPermittedError
|
||||||
|
end
|
||||||
|
end
|
30
app/controllers/concerns/export_controller_concern.rb
Normal file
30
app/controllers/concerns/export_controller_concern.rb
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ExportControllerConcern
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
before_action :authenticate_user!
|
||||||
|
before_action :load_export
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_export
|
||||||
|
@export = Export.new(current_account)
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_export_file
|
||||||
|
respond_to do |format|
|
||||||
|
format.csv { send_data export_data, filename: export_filename }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def export_data
|
||||||
|
raise 'Override in controller'
|
||||||
|
end
|
||||||
|
|
||||||
|
def export_filename
|
||||||
|
"#{controller_name}.csv"
|
||||||
|
end
|
||||||
|
end
|
@@ -17,12 +17,24 @@ module Localized
|
|||||||
end
|
end
|
||||||
|
|
||||||
def default_locale
|
def default_locale
|
||||||
ENV.fetch('DEFAULT_LOCALE') do
|
request_locale || env_locale || I18n.default_locale
|
||||||
user_supplied_locale || I18n.default_locale
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_supplied_locale
|
def env_locale
|
||||||
http_accept_language.language_region_compatible_from(I18n.available_locales)
|
ENV['DEFAULT_LOCALE']
|
||||||
|
end
|
||||||
|
|
||||||
|
def request_locale
|
||||||
|
preferred_locale || compatible_locale
|
||||||
|
end
|
||||||
|
|
||||||
|
def preferred_locale
|
||||||
|
http_accept_language.preferred_language_from([env_locale]) ||
|
||||||
|
http_accept_language.preferred_language_from(I18n.available_locales)
|
||||||
|
end
|
||||||
|
|
||||||
|
def compatible_locale
|
||||||
|
http_accept_language.compatible_language_from([env_locale]) ||
|
||||||
|
http_accept_language.compatible_language_from(I18n.available_locales)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -4,19 +4,13 @@ module ObfuscateFilename
|
|||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
class_methods do
|
class_methods do
|
||||||
def obfuscate_filename(*args)
|
|
||||||
before_action { obfuscate_filename(*args) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def obfuscate_filename(path)
|
def obfuscate_filename(path)
|
||||||
|
before_action do
|
||||||
file = params.dig(*path)
|
file = params.dig(*path)
|
||||||
return if file.nil?
|
next if file.nil?
|
||||||
|
|
||||||
file.original_filename = secure_token + File.extname(file.original_filename)
|
file.original_filename = SecureRandom.hex(8) + File.extname(file.original_filename)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
def secure_token(length = 16)
|
|
||||||
SecureRandom.hex(length / 2)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
57
app/controllers/concerns/rate_limit_headers.rb
Normal file
57
app/controllers/concerns/rate_limit_headers.rb
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RateLimitHeaders
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
before_action :set_rate_limit_headers, if: :rate_limited_request?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_rate_limit_headers
|
||||||
|
apply_header_limit
|
||||||
|
apply_header_remaining
|
||||||
|
apply_header_reset
|
||||||
|
end
|
||||||
|
|
||||||
|
def rate_limited_request?
|
||||||
|
!request.env['rack.attack.throttle_data'].nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_header_limit
|
||||||
|
response.headers['X-RateLimit-Limit'] = rate_limit_limit
|
||||||
|
end
|
||||||
|
|
||||||
|
def rate_limit_limit
|
||||||
|
api_throttle_data[:limit].to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_header_remaining
|
||||||
|
response.headers['X-RateLimit-Remaining'] = rate_limit_remaining
|
||||||
|
end
|
||||||
|
|
||||||
|
def rate_limit_remaining
|
||||||
|
(api_throttle_data[:limit] - api_throttle_data[:count]).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_header_reset
|
||||||
|
response.headers['X-RateLimit-Reset'] = rate_limit_reset
|
||||||
|
end
|
||||||
|
|
||||||
|
def rate_limit_reset
|
||||||
|
(request_time + reset_period_offset).iso8601(6)
|
||||||
|
end
|
||||||
|
|
||||||
|
def api_throttle_data
|
||||||
|
request.env['rack.attack.throttle_data']['api']
|
||||||
|
end
|
||||||
|
|
||||||
|
def request_time
|
||||||
|
@_request_time ||= Time.now.utc
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_period_offset
|
||||||
|
api_throttle_data[:period] - request_time.to_i % api_throttle_data[:period]
|
||||||
|
end
|
||||||
|
end
|
@@ -17,7 +17,7 @@ module UserTrackingConcern
|
|||||||
current_user.update_tracked_fields!(request)
|
current_user.update_tracked_fields!(request)
|
||||||
|
|
||||||
# Regenerate feed if needed
|
# Regenerate feed if needed
|
||||||
RegenerationWorker.perform_async(current_user.account_id) if user_needs_feed_update?
|
regenerate_feed! if user_needs_feed_update?
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_needs_sign_in_update?
|
def user_needs_sign_in_update?
|
||||||
@@ -27,4 +27,9 @@ module UserTrackingConcern
|
|||||||
def user_needs_feed_update?
|
def user_needs_feed_update?
|
||||||
current_user.last_sign_in_at < REGENERATE_FEED_DAYS.days.ago
|
current_user.last_sign_in_at < REGENERATE_FEED_DAYS.days.ago
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def regenerate_feed!
|
||||||
|
Redis.current.setnx("account:#{current_user.account_id}:regeneration", true) == 1 && Redis.current.expire("account:#{current_user.account_id}:regeneration", 3_600 * 24)
|
||||||
|
RegenerationWorker.perform_async(current_user.account_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@@ -4,6 +4,6 @@ class FollowerAccountsController < ApplicationController
|
|||||||
include AccountControllerConcern
|
include AccountControllerConcern
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@follows = Follow.where(target_account: @account).order(id: :desc).page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
|
@follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -4,6 +4,6 @@ class FollowingAccountsController < ApplicationController
|
|||||||
include AccountControllerConcern
|
include AccountControllerConcern
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@follows = Follow.where(account: @account).order(id: :desc).page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
|
@follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
11
app/controllers/manifests_controller.rb
Normal file
11
app/controllers/manifests_controller.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ManifestsController < ApplicationController
|
||||||
|
before_action :set_instance_presenter
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
def set_instance_presenter
|
||||||
|
@instance_presenter = InstancePresenter.new
|
||||||
|
end
|
||||||
|
end
|
@@ -1,6 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class MediaController < ApplicationController
|
class MediaController < ApplicationController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
before_action :verify_permitted_status
|
before_action :verify_permitted_status
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@@ -14,6 +16,9 @@ class MediaController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def verify_permitted_status
|
def verify_permitted_status
|
||||||
raise ActiveRecord::RecordNotFound unless media_attachment.status.permitted?(current_account)
|
authorize media_attachment.status, :show?
|
||||||
|
rescue Mastodon::NotPermittedError
|
||||||
|
# Reraise in order to get a 404 instead of a 403 error code
|
||||||
|
raise ActiveRecord::RecordNotFound
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
32
app/controllers/settings/deletes_controller.rb
Normal file
32
app/controllers/settings/deletes_controller.rb
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Settings::DeletesController < ApplicationController
|
||||||
|
layout 'admin'
|
||||||
|
|
||||||
|
before_action :check_enabled_deletion
|
||||||
|
before_action :authenticate_user!
|
||||||
|
|
||||||
|
def show
|
||||||
|
@confirmation = Form::DeleteConfirmation.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
if current_user.valid_password?(delete_params[:password])
|
||||||
|
Admin::SuspensionWorker.perform_async(current_user.account_id, true)
|
||||||
|
sign_out
|
||||||
|
redirect_to new_user_session_path, notice: I18n.t('deletes.success_msg')
|
||||||
|
else
|
||||||
|
redirect_to settings_delete_path, alert: I18n.t('deletes.bad_password_msg')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def check_enabled_deletion
|
||||||
|
redirect_to root_path unless Setting.open_deletion
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_params
|
||||||
|
params.require(:form_delete_confirmation).permit(:password)
|
||||||
|
end
|
||||||
|
end
|
@@ -1,23 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Settings
|
|
||||||
module Exports
|
|
||||||
class BaseController < ApplicationController
|
|
||||||
before_action :authenticate_user!
|
|
||||||
|
|
||||||
def index
|
|
||||||
@export = Export.new(current_account)
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.csv { send_data export_data, filename: export_filename }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def export_filename
|
|
||||||
"#{controller_name}.csv"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@@ -2,7 +2,13 @@
|
|||||||
|
|
||||||
module Settings
|
module Settings
|
||||||
module Exports
|
module Exports
|
||||||
class BlockedAccountsController < BaseController
|
class BlockedAccountsController < ApplicationController
|
||||||
|
include ExportControllerConcern
|
||||||
|
|
||||||
|
def index
|
||||||
|
send_export_file
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def export_data
|
def export_data
|
||||||
|
@@ -2,7 +2,13 @@
|
|||||||
|
|
||||||
module Settings
|
module Settings
|
||||||
module Exports
|
module Exports
|
||||||
class FollowingAccountsController < BaseController
|
class FollowingAccountsController < ApplicationController
|
||||||
|
include ExportControllerConcern
|
||||||
|
|
||||||
|
def index
|
||||||
|
send_export_file
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def export_data
|
def export_data
|
||||||
|
@@ -2,7 +2,13 @@
|
|||||||
|
|
||||||
module Settings
|
module Settings
|
||||||
module Exports
|
module Exports
|
||||||
class MutedAccountsController < BaseController
|
class MutedAccountsController < ApplicationController
|
||||||
|
include ExportControllerConcern
|
||||||
|
|
||||||
|
def index
|
||||||
|
send_export_file
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def export_data
|
def export_data
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'sidekiq-bulk'
|
||||||
|
|
||||||
class Settings::FollowerDomainsController < ApplicationController
|
class Settings::FollowerDomainsController < ApplicationController
|
||||||
layout 'admin'
|
layout 'admin'
|
||||||
|
|
||||||
@@ -13,8 +15,8 @@ class Settings::FollowerDomainsController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
domains = bulk_params[:select] || []
|
domains = bulk_params[:select] || []
|
||||||
|
|
||||||
domains.each do |domain|
|
SoftBlockDomainFollowersWorker.push_bulk(domains) do |domain|
|
||||||
SoftBlockDomainFollowersWorker.perform_async(current_account.id, domain)
|
[current_account.id, domain]
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to settings_follower_domains_path, notice: I18n.t('followers.success', count: domains.size)
|
redirect_to settings_follower_domains_path, notice: I18n.t('followers.success', count: domains.size)
|
||||||
|
@@ -35,6 +35,7 @@ class Settings::PreferencesController < ApplicationController
|
|||||||
params.require(:user).permit(
|
params.require(:user).permit(
|
||||||
:setting_default_privacy,
|
:setting_default_privacy,
|
||||||
:setting_boost_modal,
|
:setting_boost_modal,
|
||||||
|
:setting_delete_modal,
|
||||||
:setting_auto_play_gif,
|
:setting_auto_play_gif,
|
||||||
notification_emails: %i(follow follow_request reblog favourite mention digest),
|
notification_emails: %i(follow follow_request reblog favourite mention digest),
|
||||||
interactions: %i(must_be_follower must_be_following)
|
interactions: %i(must_be_follower must_be_following)
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class StatusesController < ApplicationController
|
class StatusesController < ApplicationController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
layout 'public'
|
layout 'public'
|
||||||
|
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
@@ -30,7 +32,10 @@ class StatusesController < ApplicationController
|
|||||||
@stream_entry = @status.stream_entry
|
@stream_entry = @status.stream_entry
|
||||||
@type = @stream_entry.activity_type.downcase
|
@type = @stream_entry.activity_type.downcase
|
||||||
|
|
||||||
raise ActiveRecord::RecordNotFound unless @status.permitted?(current_account)
|
authorize @status, :show?
|
||||||
|
rescue Mastodon::NotPermittedError
|
||||||
|
# Reraise in order to get a 404
|
||||||
|
raise ActiveRecord::RecordNotFound
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_account_suspension
|
def check_account_suspension
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class StreamEntriesController < ApplicationController
|
class StreamEntriesController < ApplicationController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
layout 'public'
|
layout 'public'
|
||||||
|
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
@@ -11,13 +13,9 @@ class StreamEntriesController < ApplicationController
|
|||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
return gone if @stream_entry.activity.nil?
|
|
||||||
|
|
||||||
if @stream_entry.activity_type == 'Status'
|
|
||||||
@ancestors = @stream_entry.activity.reply? ? cache_collection(@stream_entry.activity.ancestors(current_account), Status) : []
|
@ancestors = @stream_entry.activity.reply? ? cache_collection(@stream_entry.activity.ancestors(current_account), Status) : []
|
||||||
@descendants = cache_collection(@stream_entry.activity.descendants(current_account), Status)
|
@descendants = cache_collection(@stream_entry.activity.descendants(current_account), Status)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
format.atom do
|
format.atom do
|
||||||
render xml: AtomSerializer.render(AtomSerializer.new.entry(@stream_entry, true))
|
render xml: AtomSerializer.render(AtomSerializer.new.entry(@stream_entry, true))
|
||||||
@@ -46,7 +44,11 @@ class StreamEntriesController < ApplicationController
|
|||||||
@stream_entry = @account.stream_entries.where(activity_type: 'Status').find(params[:id])
|
@stream_entry = @account.stream_entries.where(activity_type: 'Status').find(params[:id])
|
||||||
@type = @stream_entry.activity_type.downcase
|
@type = @stream_entry.activity_type.downcase
|
||||||
|
|
||||||
raise ActiveRecord::RecordNotFound if @stream_entry.activity.nil? || (@stream_entry.hidden? && (@stream_entry.activity_type != 'Status' || (@stream_entry.activity_type == 'Status' && !@stream_entry.activity.permitted?(current_account))))
|
raise ActiveRecord::RecordNotFound if @stream_entry.activity.nil?
|
||||||
|
authorize @stream_entry.activity, :show? if @stream_entry.hidden?
|
||||||
|
rescue Mastodon::NotPermittedError
|
||||||
|
# Reraise in order to get a 404
|
||||||
|
raise ActiveRecord::RecordNotFound
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_account_suspension
|
def check_account_suspension
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
module WellKnown
|
module WellKnown
|
||||||
class HostMetaController < ApplicationController
|
class HostMetaController < ApplicationController
|
||||||
|
include RoutingHelper
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@webfinger_template = "#{webfinger_url}?resource={uri}"
|
@webfinger_template = "#{webfinger_url}?resource={uri}"
|
||||||
|
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
module WellKnown
|
module WellKnown
|
||||||
class WebfingerController < ApplicationController
|
class WebfingerController < ApplicationController
|
||||||
|
include RoutingHelper
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@account = Account.find_local!(username_from_resource)
|
@account = Account.find_local!(username_from_resource)
|
||||||
@canonical_account_uri = @account.to_webfinger_s
|
@canonical_account_uri = @account.to_webfinger_s
|
||||||
|
@@ -13,6 +13,10 @@ module ApplicationHelper
|
|||||||
Setting.open_registrations
|
Setting.open_registrations
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def open_deletion?
|
||||||
|
Setting.open_deletion
|
||||||
|
end
|
||||||
|
|
||||||
def add_rtl_body_class(other_classes)
|
def add_rtl_body_class(other_classes)
|
||||||
other_classes = "#{other_classes} rtl" if [:ar, :fa, :he].include?(I18n.locale)
|
other_classes = "#{other_classes} rtl" if [:ar, :fa, :he].include?(I18n.locale)
|
||||||
other_classes
|
other_classes
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module StreamEntriesHelper
|
module StreamEntriesHelper
|
||||||
EMBEDDED_CONTROLLER = 'stream_entries'.freeze
|
EMBEDDED_CONTROLLER = 'stream_entries'
|
||||||
EMBEDDED_ACTION = 'embed'.freeze
|
EMBEDDED_ACTION = 'embed'
|
||||||
|
|
||||||
def display_name(account)
|
def display_name(account)
|
||||||
account.display_name.presence || account.username
|
account.display_name.presence || account.username
|
||||||
@@ -47,12 +47,17 @@ module StreamEntriesHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def rtl?(text)
|
def rtl_status?(status)
|
||||||
rtl_characters = /[\p{Hebrew}|\p{Arabic}|\p{Syriac}|\p{Thaana}|\p{Nko}]+/m.match(text)
|
status.local? ? rtl?(status.text) : rtl?(strip_tags(status.text))
|
||||||
|
end
|
||||||
|
|
||||||
if rtl_characters.present?
|
def rtl?(text)
|
||||||
total_size = text.strip.size.to_f
|
text = simplified_text(text)
|
||||||
rtl_size(rtl_characters.to_a) / total_size > 0.3
|
rtl_words = text.scan(/[\p{Hebrew}\p{Arabic}\p{Syriac}\p{Thaana}\p{Nko}]+/m)
|
||||||
|
|
||||||
|
if rtl_words.present?
|
||||||
|
total_size = text.size.to_f
|
||||||
|
rtl_size(rtl_words) / total_size > 0.3
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
@@ -60,8 +65,20 @@ module StreamEntriesHelper
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def rtl_size(characters)
|
def simplified_text(text)
|
||||||
characters.reduce(0) { |acc, elem| acc + elem.size }.to_f
|
text.dup.tap do |new_text|
|
||||||
|
URI.extract(new_text).each do |url|
|
||||||
|
new_text.gsub!(url, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
new_text.gsub!(Account::MENTION_RE, '')
|
||||||
|
new_text.gsub!(Tag::HASHTAG_RE, '')
|
||||||
|
new_text.gsub!(/\s+/, '')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def rtl_size(words)
|
||||||
|
words.reduce(0) { |acc, elem| acc + elem.size }.to_f
|
||||||
end
|
end
|
||||||
|
|
||||||
def embedded_view?
|
def embedded_view?
|
||||||
|
BIN
app/javascript/images/elephant-friend-1.png
Normal file
BIN
app/javascript/images/elephant-friend-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
@@ -1,5 +1,4 @@
|
|||||||
import api, { getLinks } from '../api';
|
import api, { getLinks } from '../api';
|
||||||
import Immutable from 'immutable';
|
|
||||||
|
|
||||||
export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
|
export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
|
||||||
export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
|
export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
|
||||||
@@ -29,22 +28,6 @@ export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST';
|
|||||||
export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS';
|
export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS';
|
||||||
export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL';
|
export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL';
|
||||||
|
|
||||||
export const ACCOUNT_TIMELINE_FETCH_REQUEST = 'ACCOUNT_TIMELINE_FETCH_REQUEST';
|
|
||||||
export const ACCOUNT_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_TIMELINE_FETCH_SUCCESS';
|
|
||||||
export const ACCOUNT_TIMELINE_FETCH_FAIL = 'ACCOUNT_TIMELINE_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const ACCOUNT_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_TIMELINE_EXPAND_REQUEST';
|
|
||||||
export const ACCOUNT_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_TIMELINE_EXPAND_SUCCESS';
|
|
||||||
export const ACCOUNT_TIMELINE_EXPAND_FAIL = 'ACCOUNT_TIMELINE_EXPAND_FAIL';
|
|
||||||
|
|
||||||
export const ACCOUNT_MEDIA_TIMELINE_FETCH_REQUEST = 'ACCOUNT_MEDIA_TIMELINE_FETCH_REQUEST';
|
|
||||||
export const ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS';
|
|
||||||
export const ACCOUNT_MEDIA_TIMELINE_FETCH_FAIL = 'ACCOUNT_MEDIA_TIMELINE_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const ACCOUNT_MEDIA_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_MEDIA_TIMELINE_EXPAND_REQUEST';
|
|
||||||
export const ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS';
|
|
||||||
export const ACCOUNT_MEDIA_TIMELINE_EXPAND_FAIL = 'ACCOUNT_MEDIA_TIMELINE_EXPAND_FAIL';
|
|
||||||
|
|
||||||
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
||||||
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
||||||
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
|
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
|
||||||
@@ -99,93 +82,6 @@ export function fetchAccount(id) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function fetchAccountTimeline(id, replace = false) {
|
|
||||||
return (dispatch, getState) => {
|
|
||||||
const ids = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List());
|
|
||||||
const newestId = ids.size > 0 ? ids.first() : null;
|
|
||||||
|
|
||||||
let params = {};
|
|
||||||
let skipLoading = false;
|
|
||||||
|
|
||||||
if (newestId !== null && !replace) {
|
|
||||||
params.since_id = newestId;
|
|
||||||
skipLoading = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(fetchAccountTimelineRequest(id, skipLoading));
|
|
||||||
|
|
||||||
api(getState).get(`/api/v1/accounts/${id}/statuses`, { params }).then(response => {
|
|
||||||
dispatch(fetchAccountTimelineSuccess(id, response.data, replace, skipLoading));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(fetchAccountTimelineFail(id, error, skipLoading));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fetchAccountMediaTimeline(id, replace = false) {
|
|
||||||
return (dispatch, getState) => {
|
|
||||||
const ids = getState().getIn(['timelines', 'accounts_media_timelines', id, 'items'], Immutable.List());
|
|
||||||
const newestId = ids.size > 0 ? ids.first() : null;
|
|
||||||
|
|
||||||
let params = { only_media: 'true', limit: 12 };
|
|
||||||
let skipLoading = false;
|
|
||||||
|
|
||||||
if (newestId !== null && !replace) {
|
|
||||||
params.since_id = newestId;
|
|
||||||
skipLoading = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(fetchAccountMediaTimelineRequest(id, skipLoading));
|
|
||||||
|
|
||||||
api(getState).get(`/api/v1/accounts/${id}/statuses`, { params }).then(response => {
|
|
||||||
dispatch(fetchAccountMediaTimelineSuccess(id, response.data, replace, skipLoading));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(fetchAccountMediaTimelineFail(id, error, skipLoading));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function expandAccountTimeline(id) {
|
|
||||||
return (dispatch, getState) => {
|
|
||||||
const lastId = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()).last();
|
|
||||||
|
|
||||||
dispatch(expandAccountTimelineRequest(id));
|
|
||||||
|
|
||||||
api(getState).get(`/api/v1/accounts/${id}/statuses`, {
|
|
||||||
params: {
|
|
||||||
limit: 10,
|
|
||||||
max_id: lastId,
|
|
||||||
},
|
|
||||||
}).then(response => {
|
|
||||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
|
||||||
dispatch(expandAccountTimelineSuccess(id, response.data, next));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(expandAccountTimelineFail(id, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function expandAccountMediaTimeline(id) {
|
|
||||||
return (dispatch, getState) => {
|
|
||||||
const lastId = getState().getIn(['timelines', 'accounts_media_timelines', id, 'items'], Immutable.List()).last();
|
|
||||||
|
|
||||||
dispatch(expandAccountMediaTimelineRequest(id));
|
|
||||||
|
|
||||||
api(getState).get(`/api/v1/accounts/${id}/statuses`, {
|
|
||||||
params: {
|
|
||||||
limit: 12,
|
|
||||||
only_media: 'true',
|
|
||||||
max_id: lastId,
|
|
||||||
},
|
|
||||||
}).then(response => {
|
|
||||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
|
||||||
dispatch(expandAccountMediaTimelineSuccess(id, response.data, next));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(expandAccountMediaTimelineFail(id, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fetchAccountRequest(id) {
|
export function fetchAccountRequest(id) {
|
||||||
return {
|
return {
|
||||||
type: ACCOUNT_FETCH_REQUEST,
|
type: ACCOUNT_FETCH_REQUEST,
|
||||||
@@ -275,110 +171,6 @@ export function unfollowAccountFail(error) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function fetchAccountTimelineRequest(id, skipLoading) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_TIMELINE_FETCH_REQUEST,
|
|
||||||
id,
|
|
||||||
skipLoading,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fetchAccountTimelineSuccess(id, statuses, replace, skipLoading) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_TIMELINE_FETCH_SUCCESS,
|
|
||||||
id,
|
|
||||||
statuses,
|
|
||||||
replace,
|
|
||||||
skipLoading,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fetchAccountTimelineFail(id, error, skipLoading) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_TIMELINE_FETCH_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
skipLoading,
|
|
||||||
skipAlert: error.response.status === 404,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fetchAccountMediaTimelineRequest(id, skipLoading) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_MEDIA_TIMELINE_FETCH_REQUEST,
|
|
||||||
id,
|
|
||||||
skipLoading,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fetchAccountMediaTimelineSuccess(id, statuses, replace, skipLoading) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS,
|
|
||||||
id,
|
|
||||||
statuses,
|
|
||||||
replace,
|
|
||||||
skipLoading,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fetchAccountMediaTimelineFail(id, error, skipLoading) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_MEDIA_TIMELINE_FETCH_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
skipLoading,
|
|
||||||
skipAlert: error.response.status === 404,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function expandAccountTimelineRequest(id) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_TIMELINE_EXPAND_REQUEST,
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function expandAccountTimelineSuccess(id, statuses, next) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_TIMELINE_EXPAND_SUCCESS,
|
|
||||||
id,
|
|
||||||
statuses,
|
|
||||||
next,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function expandAccountTimelineFail(id, error) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_TIMELINE_EXPAND_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function expandAccountMediaTimelineRequest(id) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_MEDIA_TIMELINE_EXPAND_REQUEST,
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function expandAccountMediaTimelineSuccess(id, statuses, next) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS,
|
|
||||||
id,
|
|
||||||
statuses,
|
|
||||||
next,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function expandAccountMediaTimelineFail(id, error) {
|
|
||||||
return {
|
|
||||||
type: ACCOUNT_MEDIA_TIMELINE_EXPAND_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function blockAccount(id) {
|
export function blockAccount(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(blockAccountRequest(id));
|
dispatch(blockAccountRequest(id));
|
||||||
@@ -804,7 +596,7 @@ export function authorizeFollowRequest(id) {
|
|||||||
|
|
||||||
api(getState)
|
api(getState)
|
||||||
.post(`/api/v1/follow_requests/${id}/authorize`)
|
.post(`/api/v1/follow_requests/${id}/authorize`)
|
||||||
.then(response => dispatch(authorizeFollowRequestSuccess(id)))
|
.then(() => dispatch(authorizeFollowRequestSuccess(id)))
|
||||||
.catch(error => dispatch(authorizeFollowRequestFail(id, error)));
|
.catch(error => dispatch(authorizeFollowRequestFail(id, error)));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -838,7 +630,7 @@ export function rejectFollowRequest(id) {
|
|||||||
|
|
||||||
api(getState)
|
api(getState)
|
||||||
.post(`/api/v1/follow_requests/${id}/reject`)
|
.post(`/api/v1/follow_requests/${id}/reject`)
|
||||||
.then(response => dispatch(rejectFollowRequestSuccess(id)))
|
.then(() => dispatch(rejectFollowRequestSuccess(id)))
|
||||||
.catch(error => dispatch(rejectFollowRequestFail(id, error)));
|
.catch(error => dispatch(rejectFollowRequestFail(id, error)));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
40
app/javascript/mastodon/actions/columns.js
Normal file
40
app/javascript/mastodon/actions/columns.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { saveSettings } from './settings';
|
||||||
|
|
||||||
|
export const COLUMN_ADD = 'COLUMN_ADD';
|
||||||
|
export const COLUMN_REMOVE = 'COLUMN_REMOVE';
|
||||||
|
export const COLUMN_MOVE = 'COLUMN_MOVE';
|
||||||
|
|
||||||
|
export function addColumn(id, params) {
|
||||||
|
return dispatch => {
|
||||||
|
dispatch({
|
||||||
|
type: COLUMN_ADD,
|
||||||
|
id,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(saveSettings());
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function removeColumn(uuid) {
|
||||||
|
return dispatch => {
|
||||||
|
dispatch({
|
||||||
|
type: COLUMN_REMOVE,
|
||||||
|
uuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(saveSettings());
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function moveColumn(uuid, direction) {
|
||||||
|
return dispatch => {
|
||||||
|
dispatch({
|
||||||
|
type: COLUMN_MOVE,
|
||||||
|
uuid,
|
||||||
|
direction,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(saveSettings());
|
||||||
|
};
|
||||||
|
};
|
@@ -16,7 +16,7 @@ export function blockDomain(domain, accountId) {
|
|||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(blockDomainRequest(domain));
|
dispatch(blockDomainRequest(domain));
|
||||||
|
|
||||||
api(getState).post('/api/v1/domain_blocks', { domain }).then(response => {
|
api(getState).post('/api/v1/domain_blocks', { domain }).then(() => {
|
||||||
dispatch(blockDomainSuccess(domain, accountId));
|
dispatch(blockDomainSuccess(domain, accountId));
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
dispatch(blockDomainFail(domain, err));
|
dispatch(blockDomainFail(domain, err));
|
||||||
@@ -51,7 +51,7 @@ export function unblockDomain(domain, accountId) {
|
|||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(unblockDomainRequest(domain));
|
dispatch(unblockDomainRequest(domain));
|
||||||
|
|
||||||
api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(response => {
|
api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => {
|
||||||
dispatch(unblockDomainSuccess(domain, accountId));
|
dispatch(unblockDomainSuccess(domain, accountId));
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
dispatch(unblockDomainFail(domain, err));
|
dispatch(unblockDomainFail(domain, err));
|
||||||
|
@@ -17,7 +17,7 @@ export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL';
|
|||||||
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
|
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
|
||||||
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
|
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
|
||||||
|
|
||||||
const messages = defineMessages({
|
defineMessages({
|
||||||
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
|
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -124,25 +124,22 @@ export function refreshNotificationsFail(error, skipLoading) {
|
|||||||
|
|
||||||
export function expandNotifications() {
|
export function expandNotifications() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const url = getState().getIn(['notifications', 'next'], null);
|
const items = getState().getIn(['notifications', 'items'], Immutable.List());
|
||||||
const lastId = getState().getIn(['notifications', 'items']).last();
|
|
||||||
|
|
||||||
if (url === null || getState().getIn(['notifications', 'isLoading'])) {
|
if (getState().getIn(['notifications', 'isLoading']) || items.size === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(expandNotificationsRequest());
|
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
max_id: lastId,
|
max_id: items.last().get('id'),
|
||||||
limit: 20,
|
limit: 20,
|
||||||
|
exclude_types: excludeTypesFromSettings(getState()),
|
||||||
};
|
};
|
||||||
|
|
||||||
params.exclude_types = excludeTypesFromSettings(getState());
|
dispatch(expandNotificationsRequest());
|
||||||
|
|
||||||
api(getState).get(url, params).then(response => {
|
api(getState).get('/api/v1/notifications', { params }).then(response => {
|
||||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
|
|
||||||
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
|
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
|
||||||
fetchRelatedRelationships(dispatch, response.data);
|
fetchRelatedRelationships(dispatch, response.data);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
@@ -3,10 +3,14 @@ import axios from 'axios';
|
|||||||
export const SETTING_CHANGE = 'SETTING_CHANGE';
|
export const SETTING_CHANGE = 'SETTING_CHANGE';
|
||||||
|
|
||||||
export function changeSetting(key, value) {
|
export function changeSetting(key, value) {
|
||||||
return {
|
return dispatch => {
|
||||||
|
dispatch({
|
||||||
type: SETTING_CHANGE,
|
type: SETTING_CHANGE,
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(saveSettings());
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -74,7 +74,7 @@ export function deleteStatus(id) {
|
|||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(deleteStatusRequest(id));
|
dispatch(deleteStatusRequest(id));
|
||||||
|
|
||||||
api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
|
api(getState).delete(`/api/v1/statuses/${id}`).then(() => {
|
||||||
dispatch(deleteStatusSuccess(id));
|
dispatch(deleteStatusSuccess(id));
|
||||||
dispatch(deleteFromTimelines(id));
|
dispatch(deleteFromTimelines(id));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@@ -152,7 +152,7 @@ export function muteStatus(id) {
|
|||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(muteStatusRequest(id));
|
dispatch(muteStatusRequest(id));
|
||||||
|
|
||||||
api(getState).post(`/api/v1/statuses/${id}/mute`).then(response => {
|
api(getState).post(`/api/v1/statuses/${id}/mute`).then(() => {
|
||||||
dispatch(muteStatusSuccess(id));
|
dispatch(muteStatusSuccess(id));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(muteStatusFail(id, error));
|
dispatch(muteStatusFail(id, error));
|
||||||
@@ -186,7 +186,7 @@ export function unmuteStatus(id) {
|
|||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(unmuteStatusRequest(id));
|
dispatch(unmuteStatusRequest(id));
|
||||||
|
|
||||||
api(getState).post(`/api/v1/statuses/${id}/unmute`).then(response => {
|
api(getState).post(`/api/v1/statuses/${id}/unmute`).then(() => {
|
||||||
dispatch(unmuteStatusSuccess(id));
|
dispatch(unmuteStatusSuccess(id));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(unmuteStatusFail(id, error));
|
dispatch(unmuteStatusFail(id, error));
|
||||||
|
@@ -56,91 +56,89 @@ export function deleteFromTimelines(id) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function refreshTimelineRequest(timeline, id, skipLoading) {
|
export function refreshTimelineRequest(timeline, skipLoading) {
|
||||||
return {
|
return {
|
||||||
type: TIMELINE_REFRESH_REQUEST,
|
type: TIMELINE_REFRESH_REQUEST,
|
||||||
timeline,
|
timeline,
|
||||||
id,
|
|
||||||
skipLoading,
|
skipLoading,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function refreshTimeline(timeline, id = null) {
|
export function refreshTimeline(timelineId, path, params = {}) {
|
||||||
return function (dispatch, getState) {
|
return function (dispatch, getState) {
|
||||||
if (getState().getIn(['timelines', timeline, 'isLoading'])) {
|
const timeline = getState().getIn(['timelines', timelineId], Immutable.Map());
|
||||||
|
|
||||||
|
if (timeline.get('isLoading') || timeline.get('online')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ids = getState().getIn(['timelines', timeline, 'items'], Immutable.List());
|
const ids = timeline.get('items', Immutable.List());
|
||||||
const newestId = ids.size > 0 ? ids.first() : null;
|
const newestId = ids.size > 0 ? ids.first() : null;
|
||||||
let params = getState().getIn(['timelines', timeline, 'params'], {});
|
|
||||||
const path = getState().getIn(['timelines', timeline, 'path'])(id);
|
|
||||||
|
|
||||||
let skipLoading = false;
|
let skipLoading = timeline.get('loaded');
|
||||||
|
|
||||||
if (newestId !== null && getState().getIn(['timelines', timeline, 'loaded']) && (id === null || getState().getIn(['timelines', timeline, 'id']) === id)) {
|
if (newestId !== null) {
|
||||||
if (id === null && getState().getIn(['timelines', timeline, 'online'])) {
|
params.since_id = newestId;
|
||||||
// Skip refreshing when timeline is live anyway
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
params = { ...params, since_id: newestId };
|
dispatch(refreshTimelineRequest(timelineId, skipLoading));
|
||||||
skipLoading = true;
|
|
||||||
} else if (getState().getIn(['timelines', timeline, 'loaded'])) {
|
|
||||||
skipLoading = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(refreshTimelineRequest(timeline, id, skipLoading));
|
|
||||||
|
|
||||||
api(getState).get(path, { params }).then(response => {
|
api(getState).get(path, { params }).then(response => {
|
||||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
dispatch(refreshTimelineSuccess(timeline, response.data, skipLoading, next ? next.uri : null));
|
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(refreshTimelineFail(timeline, error, skipLoading));
|
dispatch(refreshTimelineFail(timelineId, error, skipLoading));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
|
||||||
|
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
|
||||||
|
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
|
||||||
|
export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
|
||||||
|
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
|
||||||
|
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
|
||||||
|
|
||||||
export function refreshTimelineFail(timeline, error, skipLoading) {
|
export function refreshTimelineFail(timeline, error, skipLoading) {
|
||||||
return {
|
return {
|
||||||
type: TIMELINE_REFRESH_FAIL,
|
type: TIMELINE_REFRESH_FAIL,
|
||||||
timeline,
|
timeline,
|
||||||
error,
|
error,
|
||||||
skipLoading,
|
skipLoading,
|
||||||
|
skipAlert: error.response.status === 404,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function expandTimeline(timeline) {
|
export function expandTimeline(timelineId, path, params = {}) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
if (getState().getIn(['timelines', timeline, 'isLoading'])) {
|
const timeline = getState().getIn(['timelines', timelineId], Immutable.Map());
|
||||||
|
const ids = timeline.get('items', Immutable.List());
|
||||||
|
|
||||||
|
if (timeline.get('isLoading') || ids.size === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getState().getIn(['timelines', timeline, 'items']).size === 0) {
|
params.max_id = ids.last();
|
||||||
return;
|
params.limit = 10;
|
||||||
}
|
|
||||||
|
|
||||||
const path = getState().getIn(['timelines', timeline, 'path'])(getState().getIn(['timelines', timeline, 'id']));
|
dispatch(expandTimelineRequest(timelineId));
|
||||||
const params = getState().getIn(['timelines', timeline, 'params'], {});
|
|
||||||
const lastId = getState().getIn(['timelines', timeline, 'items']).last();
|
|
||||||
|
|
||||||
dispatch(expandTimelineRequest(timeline));
|
api(getState).get(path, { params }).then(response => {
|
||||||
|
|
||||||
api(getState).get(path, {
|
|
||||||
params: {
|
|
||||||
...params,
|
|
||||||
max_id: lastId,
|
|
||||||
limit: 10,
|
|
||||||
},
|
|
||||||
}).then(response => {
|
|
||||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
dispatch(expandTimelineSuccess(timeline, response.data, next ? next.uri : null));
|
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(expandTimelineFail(timeline, error));
|
dispatch(expandTimelineFail(timelineId, error));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home');
|
||||||
|
export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public');
|
||||||
|
export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
|
||||||
|
export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
|
||||||
|
export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
|
||||||
|
export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
|
||||||
|
|
||||||
export function expandTimelineRequest(timeline) {
|
export function expandTimelineRequest(timeline) {
|
||||||
return {
|
return {
|
||||||
type: TIMELINE_EXPAND_REQUEST,
|
type: TIMELINE_EXPAND_REQUEST,
|
||||||
|
@@ -27,22 +27,15 @@ class Account extends ImmutablePureComponent {
|
|||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor (props, context) {
|
handleFollow = () => {
|
||||||
super(props, context);
|
|
||||||
this.handleFollow = this.handleFollow.bind(this);
|
|
||||||
this.handleBlock = this.handleBlock.bind(this);
|
|
||||||
this.handleMute = this.handleMute.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFollow () {
|
|
||||||
this.props.onFollow(this.props.account);
|
this.props.onFollow(this.props.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlock () {
|
handleBlock = () => {
|
||||||
this.props.onBlock(this.props.account);
|
this.props.onBlock(this.props.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMute () {
|
handleMute = () => {
|
||||||
this.props.onMute(this.props.account);
|
this.props.onMute(this.props.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,11 +55,11 @@ class Account extends ImmutablePureComponent {
|
|||||||
const muting = account.getIn(['relationship', 'muting']);
|
const muting = account.getIn(['relationship', 'muting']);
|
||||||
|
|
||||||
if (requested) {
|
if (requested) {
|
||||||
buttons = <IconButton disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} />;
|
buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />;
|
||||||
} else if (blocking) {
|
} else if (blocking) {
|
||||||
buttons = <IconButton active={true} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
|
buttons = <IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
|
||||||
} else if (muting) {
|
} else if (muting) {
|
||||||
buttons = <IconButton active={true} icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
|
buttons = <IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
|
||||||
} else {
|
} else {
|
||||||
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
|
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
||||||
|
|
||||||
class AttachmentList extends React.PureComponent {
|
class AttachmentList extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.list.isRequired,
|
media: ImmutablePropTypes.list.isRequired,
|
||||||
|
@@ -4,6 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { isRtl } from '../rtl';
|
import { isRtl } from '../rtl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import Textarea from 'react-textarea-autosize';
|
||||||
|
|
||||||
const textAtCursorMatchesToken = (str, caretPosition) => {
|
const textAtCursorMatchesToken = (str, caretPosition) => {
|
||||||
let word;
|
let word;
|
||||||
@@ -51,23 +52,14 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
|||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor (props, context) {
|
state = {
|
||||||
super(props, context);
|
|
||||||
this.state = {
|
|
||||||
suggestionsHidden: false,
|
suggestionsHidden: false,
|
||||||
selectedSuggestion: 0,
|
selectedSuggestion: 0,
|
||||||
lastToken: null,
|
lastToken: null,
|
||||||
tokenStart: 0,
|
tokenStart: 0,
|
||||||
};
|
};
|
||||||
this.onChange = this.onChange.bind(this);
|
|
||||||
this.onKeyDown = this.onKeyDown.bind(this);
|
|
||||||
this.onBlur = this.onBlur.bind(this);
|
|
||||||
this.onSuggestionClick = this.onSuggestionClick.bind(this);
|
|
||||||
this.setTextarea = this.setTextarea.bind(this);
|
|
||||||
this.onPaste = this.onPaste.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange (e) {
|
onChange = (e) => {
|
||||||
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
|
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
|
||||||
|
|
||||||
if (token !== null && this.state.lastToken !== token) {
|
if (token !== null && this.state.lastToken !== token) {
|
||||||
@@ -78,14 +70,10 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
|||||||
this.props.onSuggestionsClearRequested();
|
this.props.onSuggestionsClearRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
// auto-resize textarea
|
|
||||||
e.target.style.height = 'auto';
|
|
||||||
e.target.style.height = `${e.target.scrollHeight}px`;
|
|
||||||
|
|
||||||
this.props.onChange(e);
|
this.props.onChange(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown (e) {
|
onKeyDown = (e) => {
|
||||||
const { suggestions, disabled } = this.props;
|
const { suggestions, disabled } = this.props;
|
||||||
const { selectedSuggestion, suggestionsHidden } = this.state;
|
const { selectedSuggestion, suggestionsHidden } = this.state;
|
||||||
|
|
||||||
@@ -135,17 +123,11 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
|||||||
this.props.onKeyDown(e);
|
this.props.onKeyDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
onBlur () {
|
onBlur = () => {
|
||||||
// If we hide the suggestions immediately, then this will prevent the
|
|
||||||
// onClick for the suggestions themselves from firing.
|
|
||||||
// Setting a short window for that to take place before hiding the
|
|
||||||
// suggestions ensures that can't happen.
|
|
||||||
setTimeout(() => {
|
|
||||||
this.setState({ suggestionsHidden: true });
|
this.setState({ suggestionsHidden: true });
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuggestionClick (e) {
|
onSuggestionClick = (e) => {
|
||||||
const suggestion = Number(e.currentTarget.getAttribute('data-index'));
|
const suggestion = Number(e.currentTarget.getAttribute('data-index'));
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
|
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
|
||||||
@@ -158,21 +140,17 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTextarea (c) {
|
setTextarea = (c) => {
|
||||||
this.textarea = c;
|
this.textarea = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
onPaste (e) {
|
onPaste = (e) => {
|
||||||
if (e.clipboardData && e.clipboardData.files.length === 1) {
|
if (e.clipboardData && e.clipboardData.files.length === 1) {
|
||||||
this.props.onPaste(e.clipboardData.files);
|
this.props.onPaste(e.clipboardData.files);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset () {
|
|
||||||
this.textarea.style.height = 'auto';
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
|
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
|
||||||
const { suggestionsHidden, selectedSuggestion } = this.state;
|
const { suggestionsHidden, selectedSuggestion } = this.state;
|
||||||
@@ -184,8 +162,8 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='autosuggest-textarea'>
|
<div className='autosuggest-textarea'>
|
||||||
<textarea
|
<Textarea
|
||||||
ref={this.setTextarea}
|
inputRef={this.setTextarea}
|
||||||
className='autosuggest-textarea__textarea'
|
className='autosuggest-textarea__textarea'
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
@@ -207,7 +185,8 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
|||||||
key={suggestion}
|
key={suggestion}
|
||||||
data-index={suggestion}
|
data-index={suggestion}
|
||||||
className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`}
|
className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`}
|
||||||
onClick={this.onSuggestionClick}>
|
onMouseDown={this.onSuggestionClick}
|
||||||
|
>
|
||||||
<AutosuggestAccountContainer id={suggestion} />
|
<AutosuggestAccountContainer id={suggestion} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user