Compare commits
3 commits
cybrespace
...
theme_witc
Author | SHA1 | Date | |
---|---|---|---|
|
ef0480cbad | ||
|
6826b6466c | ||
|
9924ca8810 |
6111 changed files with 50054 additions and 218252 deletions
66
.babelrc
Normal file
66
.babelrc
Normal file
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"presets": [
|
||||
"react",
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"exclude": ["transform-async-to-generator", "transform-regenerator"],
|
||||
"loose": true,
|
||||
"modules": false,
|
||||
"targets": {
|
||||
"browsers": ["last 2 versions", "IE >= 11", "iOS >= 9"]
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": [
|
||||
"syntax-dynamic-import",
|
||||
["transform-object-rest-spread", { "useBuiltIns": true }],
|
||||
"transform-decorators-legacy",
|
||||
"transform-class-properties",
|
||||
[
|
||||
"react-intl",
|
||||
{
|
||||
"messagesDir": "./build/messages"
|
||||
}
|
||||
],
|
||||
"preval"
|
||||
],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": [
|
||||
"transform-react-jsx-source",
|
||||
"transform-react-jsx-self"
|
||||
]
|
||||
},
|
||||
"production": {
|
||||
"plugins": [
|
||||
"lodash",
|
||||
[
|
||||
"transform-react-remove-prop-types",
|
||||
{
|
||||
"mode": "remove",
|
||||
"removeImport": true,
|
||||
"additionalLibraries": [
|
||||
"react-immutable-proptypes"
|
||||
]
|
||||
}
|
||||
],
|
||||
"transform-react-inline-elements",
|
||||
[
|
||||
"transform-runtime",
|
||||
{
|
||||
"helpers": true,
|
||||
"polyfill": false,
|
||||
"regenerator": false
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"test": {
|
||||
"plugins": [
|
||||
"transform-es2015-modules-commonjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
https://github.com/heroku/heroku-buildpack-apt
|
||||
https://github.com/Scalingo/ffmpeg-buildpack
|
||||
https://github.com/Scalingo/nodejs-buildpack
|
||||
https://github.com/Scalingo/ruby-buildpack
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
version: 2
|
||||
|
||||
aliases:
|
||||
- &defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.7-buster-node
|
||||
environment: &ruby_environment
|
||||
BUNDLE_JOBS: 3
|
||||
BUNDLE_RETRY: 3
|
||||
BUNDLE_APP_CONFIG: ./.bundle/
|
||||
BUNDLE_PATH: ./vendor/bundle/
|
||||
DB_HOST: localhost
|
||||
DB_USER: root
|
||||
RAILS_ENV: test
|
||||
ALLOW_NOPAM: true
|
||||
CONTINUOUS_INTEGRATION: true
|
||||
DISABLE_SIMPLECOV: true
|
||||
PAM_ENABLED: true
|
||||
PAM_DEFAULT_SERVICE: pam_test
|
||||
PAM_CONTROLLED_SERVICE: pam_test_controlled
|
||||
working_directory: ~/projects/mastodon/
|
||||
|
||||
- &attach_workspace
|
||||
attach_workspace:
|
||||
at: ~/projects/
|
||||
|
||||
- &persist_to_workspace
|
||||
persist_to_workspace:
|
||||
root: ~/projects/
|
||||
paths:
|
||||
- ./mastodon/
|
||||
|
||||
- &restore_ruby_dependencies
|
||||
restore_cache:
|
||||
keys:
|
||||
- v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }}
|
||||
- v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-
|
||||
- v3-ruby-dependencies-
|
||||
|
||||
- &install_steps
|
||||
steps:
|
||||
- checkout
|
||||
- *attach_workspace
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v2-node-dependencies-{{ checksum "yarn.lock" }}
|
||||
- v2-node-dependencies-
|
||||
- run:
|
||||
name: Install yarn dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
- save_cache:
|
||||
key: v2-node-dependencies-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ./node_modules/
|
||||
- *persist_to_workspace
|
||||
|
||||
- &install_system_dependencies
|
||||
run:
|
||||
name: Install system dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler
|
||||
|
||||
- &install_ruby_dependencies
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *install_system_dependencies
|
||||
- run:
|
||||
name: Set Ruby version
|
||||
command: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version
|
||||
- *restore_ruby_dependencies
|
||||
- run:
|
||||
name: Set bundler settings
|
||||
command: |
|
||||
bundle config --local clean 'true'
|
||||
bundle config --local deployment 'true'
|
||||
bundle config --local with 'pam_authentication'
|
||||
bundle config --local without 'development production'
|
||||
bundle config --local frozen 'true'
|
||||
bundle config --local path $BUNDLE_PATH
|
||||
- run:
|
||||
name: Install bundler dependencies
|
||||
command: bundle check || (bundle install && bundle clean)
|
||||
- save_cache:
|
||||
key: v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }}
|
||||
paths:
|
||||
- ./.bundle/
|
||||
- ./vendor/bundle/
|
||||
- persist_to_workspace:
|
||||
root: ~/projects/
|
||||
paths:
|
||||
- ./mastodon/.bundle/
|
||||
- ./mastodon/vendor/bundle/
|
||||
|
||||
- &test_steps
|
||||
parallelism: 4
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *install_system_dependencies
|
||||
- run:
|
||||
name: Install FFMPEG
|
||||
command: sudo apt-get install -y ffmpeg
|
||||
- run:
|
||||
name: Load database schema
|
||||
command: ./bin/rails db:create db:schema:load db:seed
|
||||
- run:
|
||||
name: Run rspec in parallel
|
||||
command: |
|
||||
bundle exec rspec --profile 10 \
|
||||
--format RspecJunitFormatter \
|
||||
--out test_results/rspec.xml \
|
||||
--format progress \
|
||||
$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
|
||||
- store_test_results:
|
||||
path: test_results
|
||||
jobs:
|
||||
install:
|
||||
<<: *defaults
|
||||
<<: *install_steps
|
||||
|
||||
install-ruby2.7:
|
||||
<<: *defaults
|
||||
<<: *install_ruby_dependencies
|
||||
|
||||
install-ruby2.6:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.6-buster-node
|
||||
environment: *ruby_environment
|
||||
<<: *install_ruby_dependencies
|
||||
|
||||
build:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *install_system_dependencies
|
||||
- run:
|
||||
name: Precompile assets
|
||||
command: ./bin/rails assets:precompile
|
||||
- persist_to_workspace:
|
||||
root: ~/projects/
|
||||
paths:
|
||||
- ./mastodon/public/assets
|
||||
- ./mastodon/public/packs-test/
|
||||
|
||||
test-migrations:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.7-buster-node
|
||||
environment: *ruby_environment
|
||||
- image: circleci/postgres:12.2
|
||||
environment:
|
||||
POSTGRES_USER: root
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
- image: circleci/redis:5-alpine
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *install_system_dependencies
|
||||
- run:
|
||||
name: Create database
|
||||
command: ./bin/rails db:create
|
||||
- run:
|
||||
name: Run migrations
|
||||
command: ./bin/rails db:migrate
|
||||
|
||||
test-ruby2.7:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.7-buster-node
|
||||
environment: *ruby_environment
|
||||
- image: circleci/postgres:12.2
|
||||
environment:
|
||||
POSTGRES_USER: root
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
- image: circleci/redis:5-alpine
|
||||
<<: *test_steps
|
||||
|
||||
test-ruby2.6:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.6-buster-node
|
||||
environment: *ruby_environment
|
||||
- image: circleci/postgres:12.2
|
||||
environment:
|
||||
POSTGRES_USER: root
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
- image: circleci/redis:5-alpine
|
||||
<<: *test_steps
|
||||
|
||||
test-webui:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:12-buster
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- run:
|
||||
name: Run jest
|
||||
command: yarn test:jest
|
||||
|
||||
check-i18n:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- *install_system_dependencies
|
||||
- run:
|
||||
name: Check locale file normalization
|
||||
command: bundle exec i18n-tasks check-normalized
|
||||
- run:
|
||||
name: Check for unused strings
|
||||
command: bundle exec i18n-tasks unused -l en
|
||||
- run:
|
||||
name: Check for wrong string interpolations
|
||||
command: bundle exec i18n-tasks check-consistent-interpolations
|
||||
- run:
|
||||
name: Check that all required locale files exist
|
||||
command: bundle exec rake repo:check_locales_files
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-and-test:
|
||||
jobs:
|
||||
- install
|
||||
- install-ruby2.7:
|
||||
requires:
|
||||
- install
|
||||
- install-ruby2.6:
|
||||
requires:
|
||||
- install
|
||||
- install-ruby2.7
|
||||
- build:
|
||||
requires:
|
||||
- install-ruby2.7
|
||||
- test-migrations:
|
||||
requires:
|
||||
- install-ruby2.7
|
||||
- test-ruby2.7:
|
||||
requires:
|
||||
- install-ruby2.7
|
||||
- build
|
||||
- test-ruby2.6:
|
||||
requires:
|
||||
- install-ruby2.6
|
||||
- build
|
||||
- test-webui:
|
||||
requires:
|
||||
- install
|
||||
- check-i18n:
|
||||
requires:
|
||||
- install-ruby2.7
|
|
@ -27,11 +27,10 @@ plugins:
|
|||
enabled: true
|
||||
eslint:
|
||||
enabled: true
|
||||
channel: eslint-7
|
||||
channel: eslint-4
|
||||
rubocop:
|
||||
enabled: true
|
||||
channel: rubocop-0-92
|
||||
sass-lint:
|
||||
scss-lint:
|
||||
enabled: true
|
||||
exclude_patterns:
|
||||
- spec/
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
.bundle
|
||||
.env
|
||||
.env.*
|
||||
public/system
|
||||
|
@ -12,4 +11,3 @@ vendor/bundle
|
|||
*~
|
||||
postgres
|
||||
redis
|
||||
elasticsearch
|
||||
|
|
75
.env.nanobox
75
.env.nanobox
|
@ -11,14 +11,24 @@ DB_NAME=gonano
|
|||
DB_PASS=$DATA_DB_PASS
|
||||
DB_PORT=5432
|
||||
|
||||
# DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
|
||||
DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
|
||||
|
||||
# Optional ElasticSearch configuration
|
||||
ES_ENABLED=true
|
||||
ES_HOST=$DATA_ELASTIC_HOST
|
||||
ES_PORT=9200
|
||||
# ES_ENABLED=true
|
||||
# ES_HOST=localhost
|
||||
# ES_PORT=9200
|
||||
|
||||
BIND=0.0.0.0
|
||||
# Optimizations
|
||||
LD_PRELOAD=/data/lib/libjemalloc.so
|
||||
|
||||
# ImageMagick optimizations
|
||||
MAGICK_TEMPORARY_PATH=/app/tmp
|
||||
MAGICK_MEMORY_LIMIT=128MiB
|
||||
MAGICK_MAP_LIMIT=64MiB
|
||||
MAGICK_TIME_LIMIT=15
|
||||
MAGICK_AREA_LIMIT=16MP
|
||||
MAGICK_WIDTH_LIMIT=8KP
|
||||
MAGICK_HEIGHT_LIMIT=8KP
|
||||
|
||||
# Federation
|
||||
# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
|
||||
|
@ -74,7 +84,6 @@ SMTP_PORT=587
|
|||
SMTP_LOGIN=$SMTP_LOGIN
|
||||
SMTP_PASSWORD=$SMTP_PASSWORD
|
||||
SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||
#SMTP_REPLY_TO=
|
||||
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
|
||||
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
|
||||
#SMTP_AUTH_METHOD=plain
|
||||
|
@ -88,17 +97,9 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||
# PAPERCLIP_ROOT_URL=/system
|
||||
|
||||
# Optional asset host for multi-server setups
|
||||
# The asset host must allow cross origin request from WEB_DOMAIN or LOCAL_DOMAIN
|
||||
# if WEB_DOMAIN is not set. For example, the server may have the
|
||||
# following header field:
|
||||
# Access-Control-Allow-Origin: https://example.com/
|
||||
# CDN_HOST=https://assets.example.com
|
||||
|
||||
# S3 (optional)
|
||||
# The attachment host must allow cross origin request from WEB_DOMAIN or
|
||||
# LOCAL_DOMAIN if WEB_DOMAIN is not set. For example, the server may have the
|
||||
# following header field:
|
||||
# Access-Control-Allow-Origin: https://192.168.1.123:9000/
|
||||
# S3_ENABLED=true
|
||||
# S3_BUCKET=
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
|
@ -108,8 +109,6 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||
# S3_HOSTNAME=192.168.1.123:9000
|
||||
|
||||
# S3 (Minio Config (optional) Please check Minio instance for details)
|
||||
# The attachment host must allow cross origin request - see the description
|
||||
# above.
|
||||
# S3_ENABLED=true
|
||||
# S3_BUCKET=
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
|
@ -120,30 +119,12 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||
# S3_ENDPOINT=
|
||||
# S3_SIGNATURE_VERSION=
|
||||
|
||||
# Google Cloud Storage (optional)
|
||||
# Use S3 compatible API. Since GCS does not support Multipart Upload,
|
||||
# increase the value of S3_MULTIPART_THRESHOLD to disable Multipart Upload.
|
||||
# The attachment host must allow cross origin request - see the description
|
||||
# above.
|
||||
# S3_ENABLED=true
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
# S3_REGION=
|
||||
# S3_PROTOCOL=https
|
||||
# S3_HOSTNAME=storage.googleapis.com
|
||||
# S3_ENDPOINT=https://storage.googleapis.com
|
||||
# S3_MULTIPART_THRESHOLD=52428801 # 50.megabytes
|
||||
|
||||
# Swift (optional)
|
||||
# The attachment host must allow cross origin request - see the description
|
||||
# above.
|
||||
# SWIFT_ENABLED=true
|
||||
# SWIFT_USERNAME=
|
||||
# For Keystone V3, the value for SWIFT_TENANT should be the project name
|
||||
# SWIFT_TENANT=
|
||||
# SWIFT_PASSWORD=
|
||||
# Some OpenStack V3 providers require PROJECT_ID (optional)
|
||||
# SWIFT_PROJECT_ID=
|
||||
# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid
|
||||
# issues with token rate-limiting during high load.
|
||||
# SWIFT_AUTH_URL=
|
||||
|
@ -155,8 +136,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||
# Defaults to 60 seconds. Set to 0 to disable
|
||||
# SWIFT_CACHE_TTL=
|
||||
|
||||
# Optional alias for S3 (e.g. to serve files on a custom domain, possibly using Cloudfront or Cloudflare)
|
||||
# S3_ALIAS_HOST=
|
||||
# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
|
||||
# S3_CLOUDFRONT_HOST=
|
||||
|
||||
# Streaming API integration
|
||||
# STREAMING_API_BASE_URL=
|
||||
|
@ -183,11 +164,6 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||
# LDAP_BIND_DN=
|
||||
# LDAP_PASSWORD=
|
||||
# LDAP_UID=cn
|
||||
# LDAP_MAIL=mail
|
||||
# LDAP_SEARCH_FILTER=(|(%{uid}=%{email})(%{mail}=%{email}))
|
||||
# LDAP_UID_CONVERSION_ENABLED=true
|
||||
# LDAP_UID_CONVERSION_SEARCH=., -
|
||||
# LDAP_UID_CONVERSION_REPLACE=_
|
||||
|
||||
# PAM authentication (optional)
|
||||
# PAM authentication uses for the email generation the "email" pam variable
|
||||
|
@ -195,8 +171,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||
# The pam environment variable "email" is provided by:
|
||||
# https://github.com/devkral/pam_email_extractor
|
||||
# PAM_ENABLED=true
|
||||
# Fallback email domain for email address generation (LOCAL_DOMAIN by default)
|
||||
# PAM_EMAIL_DOMAIN=example.com
|
||||
# Fallback Suffix for email address generation (nil by default)
|
||||
# PAM_DEFAULT_SUFFIX=pam
|
||||
# Name of the pam service (pam "auth" section is evaluated)
|
||||
# PAM_DEFAULT_SERVICE=rpam
|
||||
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
|
||||
|
@ -231,8 +207,8 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||
|
||||
# Optional SAML authentication (cf. omniauth-saml)
|
||||
# SAML_ENABLED=true
|
||||
# SAML_ACS_URL=http://localhost:3000/auth/auth/saml/callback
|
||||
# SAML_ISSUER=https://example.com
|
||||
# SAML_ACS_URL=
|
||||
# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback
|
||||
# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
|
||||
# SAML_IDP_CERT=
|
||||
# SAML_IDP_CERT_FINGERPRINT=
|
||||
|
@ -244,14 +220,7 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
|
||||
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42"
|
||||
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
|
||||
|
||||
# Use HTTP proxy for outgoing request (optional)
|
||||
# http_proxy=http://gateway.local:8118
|
||||
# Access control for hidden service.
|
||||
# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
|
||||
|
|
|
@ -1,60 +1,212 @@
|
|||
# This is a sample configuration file. You can generate your configuration
|
||||
# with the `rake mastodon:setup` interactive setup wizard, but to customize
|
||||
# your setup even further, you'll need to edit it manually. This sample does
|
||||
# not demonstrate all available configuration options. Please look at
|
||||
# https://docs.joinmastodon.org/admin/config/ for the full documentation.
|
||||
|
||||
# Federation
|
||||
# ----------
|
||||
# This identifies your server and cannot be changed safely later
|
||||
# ----------
|
||||
LOCAL_DOMAIN=example.com
|
||||
|
||||
# Redis
|
||||
# -----
|
||||
REDIS_HOST=localhost
|
||||
# Service dependencies
|
||||
# You may set REDIS_URL instead for more advanced options
|
||||
# You may also set REDIS_NAMESPACE to share Redis between multiple Mastodon servers
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
# PostgreSQL
|
||||
# ----------
|
||||
DB_HOST=/var/run/postgresql
|
||||
DB_USER=mastodon
|
||||
DB_NAME=mastodon_production
|
||||
# You may set DATABASE_URL instead for more advanced options
|
||||
DB_HOST=db
|
||||
DB_USER=postgres
|
||||
DB_NAME=postgres
|
||||
DB_PASS=
|
||||
DB_PORT=5432
|
||||
# Optional ElasticSearch configuration
|
||||
# ES_ENABLED=true
|
||||
# ES_HOST=es
|
||||
# ES_PORT=9200
|
||||
|
||||
# ElasticSearch (optional)
|
||||
# ------------------------
|
||||
ES_ENABLED=true
|
||||
ES_HOST=localhost
|
||||
ES_PORT=9200
|
||||
# Federation
|
||||
# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
|
||||
# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
|
||||
LOCAL_DOMAIN=example.com
|
||||
|
||||
# Secrets
|
||||
# -------
|
||||
# Make sure to use `rake secret` to generate secrets
|
||||
# -------
|
||||
# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
|
||||
|
||||
# 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 `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose)
|
||||
SECRET_KEY_BASE=
|
||||
OTP_SECRET=
|
||||
|
||||
# Web Push
|
||||
# --------
|
||||
# Generate with `rake mastodon:webpush:generate_vapid_key`
|
||||
# --------
|
||||
# VAPID keys (used for push notifications
|
||||
# You can generate the keys using the following command (first is the private key, second is the public one)
|
||||
# You should only generate this once per instance. If you later decide to change it, all push subscription will
|
||||
# be invalidated, requiring the users to access the website again to resubscribe.
|
||||
#
|
||||
# Generate with `RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web rake mastodon:webpush:generate_vapid_key` if you use docker compose)
|
||||
#
|
||||
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
|
||||
VAPID_PRIVATE_KEY=
|
||||
VAPID_PUBLIC_KEY=
|
||||
|
||||
# Sending mail
|
||||
# ------------
|
||||
# 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.mailgun.org
|
||||
SMTP_PORT=587
|
||||
SMTP_LOGIN=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM_ADDRESS=notificatons@example.com
|
||||
SMTP_FROM_ADDRESS=notifications@example.com
|
||||
#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
|
||||
#SMTP_TLS=true
|
||||
|
||||
# File storage (optional)
|
||||
# -----------------------
|
||||
S3_ENABLED=true
|
||||
S3_BUCKET=files.example.com
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
S3_ALIAS_HOST=files.example.com
|
||||
# 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=https://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=
|
||||
|
||||
# Swift (optional)
|
||||
# SWIFT_ENABLED=true
|
||||
# SWIFT_USERNAME=
|
||||
# For Keystone V3, the value for SWIFT_TENANT should be the project name
|
||||
# SWIFT_TENANT=
|
||||
# SWIFT_PASSWORD=
|
||||
# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid
|
||||
# issues with token rate-limiting during high load.
|
||||
# SWIFT_AUTH_URL=
|
||||
# SWIFT_CONTAINER=
|
||||
# SWIFT_OBJECT_URL=
|
||||
# SWIFT_REGION=
|
||||
# Defaults to 'default'
|
||||
# SWIFT_DOMAIN_NAME=
|
||||
# Defaults to 60 seconds. Set to 0 to disable
|
||||
# SWIFT_CACHE_TTL=
|
||||
|
||||
# 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
|
||||
|
||||
# LDAP authentication (optional)
|
||||
# LDAP_ENABLED=true
|
||||
# LDAP_HOST=localhost
|
||||
# LDAP_PORT=389
|
||||
# LDAP_METHOD=simple_tls
|
||||
# LDAP_BASE=
|
||||
# LDAP_BIND_DN=
|
||||
# LDAP_PASSWORD=
|
||||
# LDAP_UID=cn
|
||||
|
||||
# PAM authentication (optional)
|
||||
# PAM authentication uses for the email generation the "email" pam variable
|
||||
# and optional as fallback PAM_DEFAULT_SUFFIX
|
||||
# The pam environment variable "email" is provided by:
|
||||
# https://github.com/devkral/pam_email_extractor
|
||||
# PAM_ENABLED=true
|
||||
# Fallback email domain for email address generation (LOCAL_DOMAIN by default)
|
||||
# PAM_EMAIL_DOMAIN=example.com
|
||||
# Name of the pam service (pam "auth" section is evaluated)
|
||||
# PAM_DEFAULT_SERVICE=rpam
|
||||
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
|
||||
# PAM_CONTROLLED_SERVICE=rpam
|
||||
|
||||
# Global OAuth settings (optional) :
|
||||
# If you have only one strategy, you may want to enable this
|
||||
# OAUTH_REDIRECT_AT_SIGN_IN=true
|
||||
|
||||
# Optional CAS authentication (cf. omniauth-cas) :
|
||||
# CAS_ENABLED=true
|
||||
# CAS_URL=https://sso.myserver.com/
|
||||
# CAS_HOST=sso.myserver.com/
|
||||
# CAS_PORT=443
|
||||
# CAS_SSL=true
|
||||
# CAS_VALIDATE_URL=
|
||||
# CAS_CALLBACK_URL=
|
||||
# CAS_LOGOUT_URL=
|
||||
# CAS_LOGIN_URL=
|
||||
# CAS_UID_FIELD='user'
|
||||
# CAS_CA_PATH=
|
||||
# CAS_DISABLE_SSL_VERIFICATION=false
|
||||
# CAS_UID_KEY='user'
|
||||
# CAS_NAME_KEY='name'
|
||||
# CAS_EMAIL_KEY='email'
|
||||
# CAS_NICKNAME_KEY='nickname'
|
||||
# CAS_FIRST_NAME_KEY='firstname'
|
||||
# CAS_LAST_NAME_KEY='lastname'
|
||||
# CAS_LOCATION_KEY='location'
|
||||
# CAS_IMAGE_KEY='image'
|
||||
# CAS_PHONE_KEY='phone'
|
||||
|
||||
# Optional SAML authentication (cf. omniauth-saml)
|
||||
# SAML_ENABLED=true
|
||||
# SAML_ACS_URL=
|
||||
# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback
|
||||
# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
|
||||
# SAML_IDP_CERT=
|
||||
# SAML_IDP_CERT_FINGERPRINT=
|
||||
# SAML_NAME_IDENTIFIER_FORMAT=
|
||||
# SAML_CERT=
|
||||
# SAML_PRIVATE_KEY=
|
||||
# SAML_SECURITY_WANT_ASSERTION_SIGNED=true
|
||||
# SAML_SECURITY_WANT_ASSERTION_ENCRYPTED=true
|
||||
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
|
||||
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
|
||||
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
|
||||
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
# Node.js
|
||||
NODE_ENV=tests
|
||||
# Federation
|
||||
LOCAL_DOMAIN=cb6e6126.ngrok.io
|
||||
LOCAL_HTTPS=true
|
||||
|
|
|
@ -1,4 +1,2 @@
|
|||
VAGRANT=true
|
||||
LOCAL_DOMAIN=mastodon.local
|
||||
BIND=0.0.0.0
|
||||
DB_HOST=/var/run/postgresql/
|
||||
LOCAL_DOMAIN=mastodon.dev
|
|
@ -1,13 +1,30 @@
|
|||
/build/**
|
||||
/coverage/**
|
||||
/db/**
|
||||
/lib/**
|
||||
/log/**
|
||||
/node_modules/**
|
||||
/nonobox/**
|
||||
/public/**
|
||||
!/public/embed.js
|
||||
/spec/**
|
||||
/tmp/**
|
||||
/vendor/**
|
||||
!.eslintrc.js
|
||||
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
||||
#
|
||||
# If you find yourself ignoring temporary files generated by your text editor
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile '~/.gitignore_global'
|
||||
|
||||
# Ignore bundler config.
|
||||
/.bundle
|
||||
|
||||
# Ignore the default SQLite database.
|
||||
/db/*.sqlite3
|
||||
/db/*.sqlite3-journal
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
/log/*
|
||||
!/log/.keep
|
||||
/tmp
|
||||
coverage
|
||||
public/system
|
||||
public/assets
|
||||
.env
|
||||
.env.production
|
||||
node_modules/
|
||||
neo4j/
|
||||
|
||||
# Ignore Vagrant files
|
||||
.vagrant/
|
||||
|
||||
# Ignore Capistrano customizations
|
||||
config/deploy/*
|
||||
|
|
209
.eslintrc.js
209
.eslintrc.js
|
@ -1,209 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true,
|
||||
jest: true,
|
||||
},
|
||||
|
||||
globals: {
|
||||
ATTACHMENT_HOST: false,
|
||||
},
|
||||
|
||||
parser: 'babel-eslint',
|
||||
|
||||
plugins: [
|
||||
'react',
|
||||
'jsx-a11y',
|
||||
'import',
|
||||
'promise',
|
||||
],
|
||||
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
experimentalObjectRestSpread: true,
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 2018,
|
||||
},
|
||||
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
'import/extensions': [
|
||||
'.js',
|
||||
],
|
||||
'import/ignore': [
|
||||
'node_modules',
|
||||
'\\.(css|scss|json)$',
|
||||
],
|
||||
'import/resolver': {
|
||||
node: {
|
||||
paths: ['app/javascript'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
'brace-style': 'warn',
|
||||
'comma-dangle': ['error', 'always-multiline'],
|
||||
'comma-spacing': [
|
||||
'warn',
|
||||
{
|
||||
before: false,
|
||||
after: true,
|
||||
},
|
||||
],
|
||||
'comma-style': ['warn', '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',
|
||||
'warn',
|
||||
],
|
||||
},
|
||||
],
|
||||
'no-fallthrough': 'error',
|
||||
'no-irregular-whitespace': 'error',
|
||||
'no-mixed-spaces-and-tabs': 'warn',
|
||||
'no-nested-ternary': 'warn',
|
||||
'no-trailing-spaces': 'warn',
|
||||
'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': [
|
||||
'error',
|
||||
{
|
||||
classes: 'always',
|
||||
},
|
||||
],
|
||||
quotes: ['error', 'single'],
|
||||
semi: 'error',
|
||||
strict: 'off',
|
||||
'valid-typeof': '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-duplicate-props': '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-string-refs': 'error',
|
||||
'react/prop-types': 'error',
|
||||
'react/self-closing-comp': 'error',
|
||||
|
||||
'jsx-a11y/accessible-emoji': 'warn',
|
||||
'jsx-a11y/alt-text': 'warn',
|
||||
'jsx-a11y/anchor-has-content': 'warn',
|
||||
'jsx-a11y/anchor-is-valid': [
|
||||
'warn',
|
||||
{
|
||||
components: [
|
||||
'Link',
|
||||
'NavLink',
|
||||
],
|
||||
specialLink: [
|
||||
'to',
|
||||
],
|
||||
aspect: [
|
||||
'noHref',
|
||||
'invalidHref',
|
||||
'preferButton',
|
||||
],
|
||||
},
|
||||
],
|
||||
'jsx-a11y/aria-activedescendant-has-tabindex': 'warn',
|
||||
'jsx-a11y/aria-props': 'warn',
|
||||
'jsx-a11y/aria-proptypes': 'warn',
|
||||
'jsx-a11y/aria-role': 'warn',
|
||||
'jsx-a11y/aria-unsupported-elements': 'warn',
|
||||
'jsx-a11y/heading-has-content': 'warn',
|
||||
'jsx-a11y/html-has-lang': 'warn',
|
||||
'jsx-a11y/iframe-has-title': 'warn',
|
||||
'jsx-a11y/img-redundant-alt': 'warn',
|
||||
'jsx-a11y/interactive-supports-focus': 'warn',
|
||||
'jsx-a11y/label-has-for': 'off',
|
||||
'jsx-a11y/mouse-events-have-key-events': 'warn',
|
||||
'jsx-a11y/no-access-key': 'warn',
|
||||
'jsx-a11y/no-distracting-elements': 'warn',
|
||||
'jsx-a11y/no-noninteractive-element-interactions': [
|
||||
'warn',
|
||||
{
|
||||
handlers: [
|
||||
'onClick',
|
||||
],
|
||||
},
|
||||
],
|
||||
'jsx-a11y/no-onchange': 'warn',
|
||||
'jsx-a11y/no-redundant-roles': 'warn',
|
||||
'jsx-a11y/no-static-element-interactions': [
|
||||
'warn',
|
||||
{
|
||||
handlers: [
|
||||
'onClick',
|
||||
],
|
||||
},
|
||||
],
|
||||
'jsx-a11y/role-has-required-aria-props': 'warn',
|
||||
'jsx-a11y/role-supports-aria-props': 'off',
|
||||
'jsx-a11y/scope': 'warn',
|
||||
'jsx-a11y/tabindex-no-positive': 'warn',
|
||||
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
js: 'never',
|
||||
},
|
||||
],
|
||||
'import/newline-after-import': 'error',
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: [
|
||||
'config/webpack/**',
|
||||
'app/javascript/mastodon/test_setup.js',
|
||||
'app/javascript/**/__tests__/**',
|
||||
],
|
||||
},
|
||||
],
|
||||
'import/no-unresolved': 'error',
|
||||
'import/no-webpack-loader-syntax': 'error',
|
||||
|
||||
'promise/catch-or-return': [
|
||||
'error',
|
||||
{
|
||||
allowFinally: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
154
.eslintrc.yml
Normal file
154
.eslintrc.yml
Normal file
|
@ -0,0 +1,154 @@
|
|||
---
|
||||
root: true
|
||||
|
||||
env:
|
||||
browser: true
|
||||
node: true
|
||||
es6: true
|
||||
jest: true
|
||||
|
||||
parser: babel-eslint
|
||||
|
||||
plugins:
|
||||
- react
|
||||
- jsx-a11y
|
||||
- import
|
||||
|
||||
parserOptions:
|
||||
sourceType: module
|
||||
ecmaFeatures:
|
||||
experimentalObjectRestSpread: true
|
||||
jsx: true
|
||||
ecmaVersion: 2018
|
||||
|
||||
settings:
|
||||
import/extensions:
|
||||
- .js
|
||||
import/ignore:
|
||||
- node_modules
|
||||
- \\.(css|scss|json)$
|
||||
|
||||
rules:
|
||||
brace-style: warn
|
||||
comma-dangle:
|
||||
- error
|
||||
- always-multiline
|
||||
comma-spacing:
|
||||
- warn
|
||||
- before: false
|
||||
after: true
|
||||
comma-style:
|
||||
- warn
|
||||
- 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
|
||||
- warn
|
||||
no-fallthrough: error
|
||||
no-irregular-whitespace: error
|
||||
no-mixed-spaces-and-tabs: warn
|
||||
no-nested-ternary: warn
|
||||
no-trailing-spaces: warn
|
||||
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:
|
||||
- error
|
||||
- classes: always
|
||||
quotes:
|
||||
- error
|
||||
- single
|
||||
semi: error
|
||||
strict: off
|
||||
valid-typeof: 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-duplicate-props: 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-string-refs: error
|
||||
react/prop-types: error
|
||||
react/self-closing-comp: error
|
||||
|
||||
jsx-a11y/accessible-emoji: warn
|
||||
jsx-a11y/alt-text: warn
|
||||
jsx-a11y/anchor-has-content: warn
|
||||
jsx-a11y/aria-activedescendant-has-tabindex: warn
|
||||
jsx-a11y/aria-props: warn
|
||||
jsx-a11y/aria-proptypes: warn
|
||||
jsx-a11y/aria-role: warn
|
||||
jsx-a11y/aria-unsupported-elements: warn
|
||||
jsx-a11y/heading-has-content: warn
|
||||
jsx-a11y/href-no-hash: warn
|
||||
jsx-a11y/html-has-lang: warn
|
||||
jsx-a11y/iframe-has-title: warn
|
||||
jsx-a11y/img-redundant-alt: warn
|
||||
jsx-a11y/interactive-supports-focus: warn
|
||||
jsx-a11y/label-has-for: off
|
||||
jsx-a11y/mouse-events-have-key-events: warn
|
||||
jsx-a11y/no-access-key: warn
|
||||
jsx-a11y/no-distracting-elements: warn
|
||||
jsx-a11y/no-noninteractive-element-interactions:
|
||||
- warn
|
||||
- handlers:
|
||||
- onClick
|
||||
jsx-a11y/no-onchange: warn
|
||||
jsx-a11y/no-redundant-roles: warn
|
||||
jsx-a11y/no-static-element-interactions:
|
||||
- warn
|
||||
- handlers:
|
||||
- onClick
|
||||
jsx-a11y/role-has-required-aria-props: warn
|
||||
jsx-a11y/role-supports-aria-props: off
|
||||
jsx-a11y/scope: warn
|
||||
jsx-a11y/tabindex-no-positive: warn
|
||||
|
||||
import/extensions:
|
||||
- error
|
||||
- always
|
||||
- js: never
|
||||
import/newline-after-import: error
|
||||
import/no-extraneous-dependencies:
|
||||
- error
|
||||
- devDependencies:
|
||||
- "config/webpack/**"
|
||||
- "app/javascript/mastodon/test_setup.js"
|
||||
- "app/javascript/**/__tests__/**"
|
||||
import/no-unresolved: error
|
||||
import/no-webpack-loader-syntax: error
|
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
|
@ -1,3 +0,0 @@
|
|||
patreon: mastodon
|
||||
open_collective: mastodon
|
||||
github: [Gargron]
|
6
.github/ISSUE_TEMPLATE.md
vendored
Normal file
6
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
[Issue text goes here].
|
||||
|
||||
* * * *
|
||||
|
||||
- [ ] I searched or browsed the repo’s other issues to ensure this is not a duplicate.
|
||||
- [ ] This bug happens on a [tagged release](https://github.com/tootsuite/mastodon/releases) and not on `master` (If you're a user, don't worry about this).
|
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,27 +0,0 @@
|
|||
---
|
||||
name: Bug Report
|
||||
about: If something isn't working as expected
|
||||
labels: bug
|
||||
---
|
||||
|
||||
<!-- Make sure that you are submitting a new bug that was not previously reported or already fixed -->
|
||||
|
||||
<!-- Please use a concise and distinct title for the issue -->
|
||||
|
||||
### Expected behaviour
|
||||
|
||||
<!-- What should have happened? -->
|
||||
|
||||
### Actual behaviour
|
||||
|
||||
<!-- What happened? -->
|
||||
|
||||
### Steps to reproduce the problem
|
||||
|
||||
<!-- What were you trying to do? -->
|
||||
|
||||
### Specifications
|
||||
|
||||
<!-- What version or commit hash of Mastodon did you find this bug in? -->
|
||||
|
||||
<!-- If a front-end issue, what browser and operating systems were you using? -->
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,5 +0,0 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Mastodon Meta Discussion Board
|
||||
url: https://discourse.joinmastodon.org/
|
||||
about: Please ask and answer questions here.
|
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,16 +0,0 @@
|
|||
---
|
||||
name: Feature Request
|
||||
about: I have a suggestion
|
||||
---
|
||||
|
||||
<!-- Please use a concise and distinct title for the issue -->
|
||||
|
||||
<!-- Consider: Could it be implemented as a 3rd party app using the REST API instead? -->
|
||||
|
||||
### Pitch
|
||||
|
||||
<!-- Describe your idea for a feature. Make sure it has not already been suggested/implemented/turned down before -->
|
||||
|
||||
### Motivation
|
||||
|
||||
<!-- Why do you think this feature is needed? Who would benefit from it? -->
|
10
.github/ISSUE_TEMPLATE/support.md
vendored
10
.github/ISSUE_TEMPLATE/support.md
vendored
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
name: Support
|
||||
about: Ask for help with your deployment
|
||||
|
||||
---
|
||||
|
||||
We primarily use GitHub as a bug and feature tracker. For usage questions, troubleshooting of deployments and other individual technical assistance, please use one of the resources below:
|
||||
|
||||
- https://discourse.joinmastodon.org
|
||||
- #mastodon on irc.freenode.net
|
22
.github/dependabot.yml
vendored
22
.github/dependabot.yml
vendored
|
@ -1,22 +0,0 @@
|
|||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 99
|
||||
allow:
|
||||
- dependency-type: direct
|
||||
|
||||
- package-ecosystem: bundler
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 99
|
||||
allow:
|
||||
- dependency-type: direct
|
10
.github/stale.yml
vendored
10
.github/stale.yml
vendored
|
@ -1,10 +0,0 @@
|
|||
daysUntilStale: 120
|
||||
daysUntilClose: 7
|
||||
exemptLabels:
|
||||
- security
|
||||
staleLabel: wontfix
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
only: pulls
|
34
.gitignore
vendored
34
.gitignore
vendored
|
@ -13,40 +13,32 @@
|
|||
/db/*.sqlite3-journal
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
.eslintcache
|
||||
/log/*
|
||||
!/log/.keep
|
||||
/tmp
|
||||
/coverage
|
||||
/public/system
|
||||
/public/assets
|
||||
/public/packs
|
||||
/public/packs-test
|
||||
coverage
|
||||
public/system
|
||||
public/assets
|
||||
public/packs
|
||||
public/packs-test
|
||||
.env
|
||||
.env.production
|
||||
.env.development
|
||||
/node_modules/
|
||||
/build/
|
||||
node_modules/
|
||||
build/
|
||||
|
||||
# Ignore Vagrant files
|
||||
.vagrant/
|
||||
|
||||
# Ignore Capistrano customizations
|
||||
/config/deploy/*
|
||||
config/deploy/*
|
||||
|
||||
# Ignore IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Ignore postgres + redis + elasticsearch volume optionally created by docker-compose
|
||||
/postgres
|
||||
/redis
|
||||
/elasticsearch
|
||||
|
||||
# ignore Helm lockfile, dependency charts, and local values file
|
||||
/chart/Chart.lock
|
||||
/chart/charts/*.tgz
|
||||
/chart/values.yaml
|
||||
# Ignore postgres + redis volume optionally created by docker-compose
|
||||
postgres
|
||||
redis
|
||||
|
||||
# Ignore Apple files
|
||||
.DS_Store
|
||||
|
@ -62,8 +54,6 @@ npm-debug.log
|
|||
yarn-error.log
|
||||
yarn-debug.log
|
||||
|
||||
# Ignore vagrant log files
|
||||
*-cloudimg-console.log
|
||||
|
||||
# Ignore Docker option files
|
||||
docker-compose.override.yml
|
||||
|
||||
|
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
|||
12
|
||||
6
|
||||
|
|
9
.postcssrc.yml
Normal file
9
.postcssrc.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
plugins:
|
||||
postcss-smart-import: {}
|
||||
precss: {}
|
||||
autoprefixer:
|
||||
browsers:
|
||||
- last 2 versions
|
||||
- IE >= 11
|
||||
- iOS >= 9
|
||||
postcss-object-fit-images: {}
|
204
.rubocop.yml
204
.rubocop.yml
|
@ -1,8 +1,5 @@
|
|||
require:
|
||||
- rubocop-rails
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.4
|
||||
TargetRubyVersion: 2.3
|
||||
Exclude:
|
||||
- 'spec/**/*'
|
||||
- 'db/**/*'
|
||||
|
@ -14,7 +11,6 @@ AllCops:
|
|||
- 'Vagrantfile'
|
||||
- 'vendor/**/*'
|
||||
- 'lib/json_ld/*'
|
||||
- 'lib/templates/**/*'
|
||||
|
||||
Bundler/OrderedGems:
|
||||
Enabled: false
|
||||
|
@ -25,78 +21,34 @@ Layout/AccessModifierIndentation:
|
|||
Layout/EmptyLineAfterMagicComment:
|
||||
Enabled: false
|
||||
|
||||
Layout/EmptyLineAfterGuardClause:
|
||||
Enabled: false
|
||||
|
||||
Layout/EmptyLinesAroundAttributeAccessor:
|
||||
Enabled: true
|
||||
|
||||
Layout/HashAlignment:
|
||||
Enabled: false
|
||||
# EnforcedHashRocketStyle: table
|
||||
# EnforcedColonStyle: table
|
||||
|
||||
Layout/SpaceAroundMethodCallOperator:
|
||||
Enabled: true
|
||||
|
||||
Layout/SpaceInsideHashLiteralBraces:
|
||||
EnforcedStyle: space
|
||||
|
||||
Lint/DeprecatedOpenSSLConstant:
|
||||
Enabled: true
|
||||
|
||||
Lint/DuplicateElsifCondition:
|
||||
Enabled: true
|
||||
|
||||
Lint/MixedRegexpCaptureTypes:
|
||||
Enabled: true
|
||||
|
||||
Lint/RaiseException:
|
||||
Enabled: true
|
||||
|
||||
Lint/StructNewOverride:
|
||||
Enabled: true
|
||||
|
||||
Lint/UselessAccessModifier:
|
||||
ContextCreatingMethods:
|
||||
- class_methods
|
||||
|
||||
Metrics/AbcSize:
|
||||
Max: 100
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
|
||||
Metrics/BlockLength:
|
||||
Max: 55
|
||||
Max: 35
|
||||
Exclude:
|
||||
- 'lib/tasks/**/*'
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Max: 3
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
|
||||
Metrics/ClassLength:
|
||||
CountComments: false
|
||||
Max: 400
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
Max: 300
|
||||
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 25
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
|
||||
Layout/LineLength:
|
||||
Metrics/LineLength:
|
||||
AllowURI: true
|
||||
Enabled: false
|
||||
|
||||
Metrics/MethodLength:
|
||||
CountComments: false
|
||||
Max: 65
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
Max: 55
|
||||
|
||||
Metrics/ModuleLength:
|
||||
CountComments: false
|
||||
|
@ -107,90 +59,17 @@ Metrics/ParameterLists:
|
|||
CountKeywordArgs: true
|
||||
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 25
|
||||
|
||||
Naming/MemoizedInstanceVariableName:
|
||||
Enabled: false
|
||||
|
||||
Naming/MethodParameterName:
|
||||
Enabled: true
|
||||
Max: 20
|
||||
|
||||
Rails:
|
||||
Enabled: true
|
||||
|
||||
Rails/ApplicationController:
|
||||
Enabled: false
|
||||
Exclude:
|
||||
- 'app/controllers/well_known/**/*.rb'
|
||||
|
||||
Rails/BelongsTo:
|
||||
Enabled: false
|
||||
|
||||
Rails/ContentTag:
|
||||
Enabled: false
|
||||
|
||||
Rails/EnumHash:
|
||||
Enabled: false
|
||||
|
||||
Rails/Exit:
|
||||
Exclude:
|
||||
- 'lib/mastodon/*'
|
||||
- 'lib/cli.rb'
|
||||
|
||||
Rails/FilePath:
|
||||
Enabled: false
|
||||
|
||||
Rails/HasAndBelongsToMany:
|
||||
Enabled: false
|
||||
|
||||
Rails/HasManyOrHasOneDependent:
|
||||
Enabled: false
|
||||
|
||||
Rails/HelperInstanceVariable:
|
||||
Enabled: false
|
||||
|
||||
Rails/HttpStatus:
|
||||
Enabled: false
|
||||
|
||||
Rails/IndexBy:
|
||||
Enabled: false
|
||||
|
||||
Rails/InverseOf:
|
||||
Enabled: false
|
||||
|
||||
Rails/LexicallyScopedActionFilter:
|
||||
Enabled: false
|
||||
|
||||
Rails/OutputSafety:
|
||||
Enabled: true
|
||||
|
||||
Rails/RakeEnvironment:
|
||||
Enabled: false
|
||||
|
||||
Rails/RedundantForeignKey:
|
||||
Enabled: false
|
||||
|
||||
Rails/SkipsModelValidations:
|
||||
Enabled: false
|
||||
|
||||
Rails/UniqueValidationWithoutIndex:
|
||||
Enabled: false
|
||||
|
||||
Style/AccessorGrouping:
|
||||
Enabled: true
|
||||
|
||||
Style/AccessModifierDeclarations:
|
||||
Enabled: false
|
||||
|
||||
Style/ArrayCoercion:
|
||||
Enabled: true
|
||||
|
||||
Style/BisectedAttrAccessor:
|
||||
Enabled: true
|
||||
|
||||
Style/CaseLikeIf:
|
||||
Enabled: false
|
||||
|
||||
Style/ClassAndModuleChildren:
|
||||
Enabled: false
|
||||
|
||||
|
@ -205,51 +84,15 @@ Style/Documentation:
|
|||
Style/DoubleNegation:
|
||||
Enabled: true
|
||||
|
||||
Style/ExpandPathArguments:
|
||||
Enabled: false
|
||||
|
||||
Style/ExponentialNotation:
|
||||
Enabled: true
|
||||
|
||||
Style/FormatString:
|
||||
Enabled: false
|
||||
|
||||
Style/FormatStringToken:
|
||||
Enabled: false
|
||||
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: true
|
||||
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
||||
|
||||
Style/HashAsLastArrayItem:
|
||||
Enabled: false
|
||||
|
||||
Style/HashEachMethods:
|
||||
Enabled: true
|
||||
|
||||
Style/HashLikeCase:
|
||||
Enabled: true
|
||||
|
||||
Style/HashTransformKeys:
|
||||
Enabled: true
|
||||
|
||||
Style/HashTransformValues:
|
||||
Enabled: false
|
||||
|
||||
Style/IfUnlessModifier:
|
||||
Enabled: false
|
||||
|
||||
Style/InverseMethods:
|
||||
Enabled: false
|
||||
|
||||
Style/Lambda:
|
||||
Enabled: false
|
||||
|
||||
Style/MutableConstant:
|
||||
Enabled: false
|
||||
|
||||
Style/PercentLiteralDelimiters:
|
||||
PreferredDelimiters:
|
||||
'%i': '()'
|
||||
|
@ -258,44 +101,11 @@ Style/PercentLiteralDelimiters:
|
|||
Style/PerlBackrefs:
|
||||
AutoCorrect: false
|
||||
|
||||
Style/RedundantAssignment:
|
||||
Enabled: false
|
||||
|
||||
Style/RedundantFetchBlock:
|
||||
Enabled: true
|
||||
|
||||
Style/RedundantFileExtensionInRequire:
|
||||
Enabled: true
|
||||
|
||||
Style/RedundantRegexpCharacterClass:
|
||||
Enabled: false
|
||||
|
||||
Style/RedundantRegexpEscape:
|
||||
Enabled: false
|
||||
|
||||
Style/RedundantReturn:
|
||||
Enabled: true
|
||||
|
||||
Style/RegexpLiteral:
|
||||
Enabled: false
|
||||
|
||||
Style/RescueStandardError:
|
||||
Enabled: false
|
||||
|
||||
Style/SignalException:
|
||||
Enabled: false
|
||||
|
||||
Style/SlicingWithRange:
|
||||
Enabled: true
|
||||
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
Style/TrailingCommaInLiteral:
|
||||
EnforcedStyleForMultiline: 'comma'
|
||||
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
EnforcedStyleForMultiline: 'comma'
|
||||
|
||||
Style/UnpackFirst:
|
||||
Enabled: false
|
||||
|
|
|
@ -1 +1 @@
|
|||
2.7.2
|
||||
2.5.0
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
# Linter Documentation:
|
||||
# https://github.com/sasstools/sass-lint/tree/v1.13.1/docs/options
|
||||
|
||||
files:
|
||||
include: app/javascript/styles/**/*.scss
|
||||
ignore:
|
||||
- app/javascript/styles/mastodon/reset.scss
|
||||
|
||||
rules:
|
||||
# Disallows
|
||||
no-color-literals: 0
|
||||
no-css-comments: 0
|
||||
no-duplicate-properties: 0
|
||||
no-ids: 0
|
||||
no-important: 0
|
||||
no-mergeable-selectors: 0
|
||||
no-misspelled-properties: 0
|
||||
no-qualifying-elements: 0
|
||||
no-transition-all: 0
|
||||
no-vendor-prefixes: 0
|
||||
|
||||
# Nesting
|
||||
force-element-nesting: 0
|
||||
force-attribute-nesting: 0
|
||||
force-pseudo-nesting: 0
|
||||
|
||||
# Name Formats
|
||||
class-name-format: 0
|
||||
leading-zero: 0
|
||||
|
||||
# Style Guide
|
||||
attribute-quotes: 0
|
||||
hex-length: 0
|
||||
indentation: 0
|
||||
nesting-depth: 0
|
||||
property-sort-order: 0
|
||||
quotes: 0
|
264
.scss-lint.yml
Normal file
264
.scss-lint.yml
Normal file
|
@ -0,0 +1,264 @@
|
|||
# Linter Documentation:
|
||||
# https://github.com/brigade/scss-lint/blob/v0.42.2/lib/scss_lint/linter/README.md
|
||||
|
||||
scss_files: 'app/javascript/styles/**/*.scss'
|
||||
|
||||
exclude:
|
||||
- app/javascript/styles/reset.scss
|
||||
|
||||
linters:
|
||||
# Reports when you use improper spacing around ! (the "bang") in !default,
|
||||
# !global, !important, and !optional flags.
|
||||
BangFormat:
|
||||
enabled: false
|
||||
|
||||
# Whether or not to prefer `border: 0` over `border: none`.
|
||||
BorderZero:
|
||||
enabled: false
|
||||
|
||||
# Reports when you define a rule set using a selector with chained classes
|
||||
# (a.k.a. adjoining classes).
|
||||
ChainedClasses:
|
||||
enabled: false
|
||||
|
||||
# Prefer hexadecimal color codes over color keywords.
|
||||
# (e.g. `color: green` is a color keyword)
|
||||
ColorKeyword:
|
||||
enabled: false
|
||||
|
||||
# Prefer color literals (keywords or hexadecimal codes) to be used only in
|
||||
# variable declarations. They should be referred to via variables everywhere
|
||||
# else.
|
||||
ColorVariable:
|
||||
enabled: true
|
||||
|
||||
# Which form of comments to prefer in CSS.
|
||||
Comment:
|
||||
enabled: false
|
||||
|
||||
# Reports @debug statements (which you probably left behind accidentally).
|
||||
DebugStatement:
|
||||
enabled: false
|
||||
|
||||
# Rule sets should be ordered as follows:
|
||||
# - @extend declarations
|
||||
# - @include declarations without inner @content
|
||||
# - properties, @include declarations with inner @content
|
||||
# - nested rule sets.
|
||||
DeclarationOrder:
|
||||
enabled: false
|
||||
|
||||
# `scss-lint:disable` control comments should be preceded by a comment
|
||||
# explaining why these linters are being disabled for this file.
|
||||
# See https://github.com/brigade/scss-lint#disabling-linters-via-source for
|
||||
# more information.
|
||||
DisableLinterReason:
|
||||
enabled: true
|
||||
|
||||
# Reports when you define the same property twice in a single rule set.
|
||||
DuplicateProperty:
|
||||
enabled: false
|
||||
|
||||
# Separate rule, function, and mixin declarations with empty lines.
|
||||
EmptyLineBetweenBlocks:
|
||||
enabled: true
|
||||
|
||||
# Reports when you have an empty rule set.
|
||||
EmptyRule:
|
||||
enabled: true
|
||||
|
||||
# Reports when you have an @extend directive.
|
||||
ExtendDirective:
|
||||
enabled: false
|
||||
|
||||
# Files should always have a final newline. This results in better diffs
|
||||
# when adding lines to the file, since SCM systems such as git won't
|
||||
# think that you touched the last line.
|
||||
FinalNewline:
|
||||
enabled: false
|
||||
|
||||
# HEX colors should use three-character values where possible.
|
||||
HexLength:
|
||||
enabled: false
|
||||
|
||||
# HEX color values should use lower-case colors to differentiate between
|
||||
# letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`.
|
||||
HexNotation:
|
||||
enabled: true
|
||||
|
||||
# Avoid using ID selectors.
|
||||
IdSelector:
|
||||
enabled: false
|
||||
|
||||
# The basenames of @imported SCSS partials should not begin with an
|
||||
# underscore and should not include the filename extension.
|
||||
ImportPath:
|
||||
enabled: false
|
||||
|
||||
# Avoid using !important in properties. It is usually indicative of a
|
||||
# misunderstanding of CSS specificity and can lead to brittle code.
|
||||
ImportantRule:
|
||||
enabled: false
|
||||
|
||||
# Indentation should always be done in increments of 2 spaces.
|
||||
Indentation:
|
||||
enabled: true
|
||||
width: 2
|
||||
|
||||
# Don't write leading zeros for numeric values with a decimal point.
|
||||
LeadingZero:
|
||||
enabled: false
|
||||
|
||||
# Reports when you define the same selector twice in a single sheet.
|
||||
MergeableSelector:
|
||||
enabled: false
|
||||
|
||||
# Functions, mixins, variables, and placeholders should be declared
|
||||
# with all lowercase letters and hyphens instead of underscores.
|
||||
NameFormat:
|
||||
enabled: false
|
||||
|
||||
# Avoid nesting selectors too deeply.
|
||||
NestingDepth:
|
||||
enabled: false
|
||||
|
||||
# Always use placeholder selectors in @extend.
|
||||
PlaceholderInExtend:
|
||||
enabled: false
|
||||
|
||||
# Sort properties in a strict order.
|
||||
PropertySortOrder:
|
||||
enabled: false
|
||||
|
||||
# Reports when you use an unknown or disabled CSS property
|
||||
# (ignoring vendor-prefixed properties).
|
||||
PropertySpelling:
|
||||
enabled: false
|
||||
|
||||
# Configure which units are allowed for property values.
|
||||
PropertyUnits:
|
||||
enabled: false
|
||||
|
||||
# Pseudo-elements, like ::before, and ::first-letter, should be declared
|
||||
# with two colons. Pseudo-classes, like :hover and :first-child, should
|
||||
# be declared with one colon.
|
||||
PseudoElement:
|
||||
enabled: true
|
||||
|
||||
# Avoid qualifying elements in selectors (also known as "tag-qualifying").
|
||||
QualifyingElement:
|
||||
enabled: false
|
||||
|
||||
# Don't write selectors with a depth of applicability greater than 3.
|
||||
SelectorDepth:
|
||||
enabled: false
|
||||
|
||||
# Selectors should always use hyphenated-lowercase, rather than camelCase or
|
||||
# snake_case.
|
||||
SelectorFormat:
|
||||
enabled: false
|
||||
convention: hyphenated_lowercase
|
||||
|
||||
# Prefer the shortest shorthand form possible for properties that support it.
|
||||
Shorthand:
|
||||
enabled: true
|
||||
|
||||
# Each property should have its own line, except in the special case of
|
||||
# single line rulesets.
|
||||
SingleLinePerProperty:
|
||||
enabled: true
|
||||
allow_single_line_rule_sets: true
|
||||
|
||||
# Split selectors onto separate lines after each comma, and have each
|
||||
# individual selector occupy a single line.
|
||||
SingleLinePerSelector:
|
||||
enabled: true
|
||||
|
||||
# Commas in lists should be followed by a space.
|
||||
SpaceAfterComma:
|
||||
enabled: false
|
||||
|
||||
# Properties should be formatted with a single space separating the colon
|
||||
# from the property's value.
|
||||
SpaceAfterPropertyColon:
|
||||
enabled: true
|
||||
|
||||
# Properties should be formatted with no space between the name and the
|
||||
# colon.
|
||||
SpaceAfterPropertyName:
|
||||
enabled: true
|
||||
|
||||
# Variables should be formatted with a single space separating the colon
|
||||
# from the variable's value.
|
||||
SpaceAfterVariableColon:
|
||||
enabled: true
|
||||
|
||||
# Variables should be formatted with no space between the name and the
|
||||
# colon.
|
||||
SpaceAfterVariableName:
|
||||
enabled: false
|
||||
|
||||
# Operators should be formatted with a single space on both sides of an
|
||||
# infix operator.
|
||||
SpaceAroundOperator:
|
||||
enabled: true
|
||||
|
||||
# Opening braces should be preceded by a single space.
|
||||
SpaceBeforeBrace:
|
||||
enabled: true
|
||||
|
||||
# Parentheses should not be padded with spaces.
|
||||
SpaceBetweenParens:
|
||||
enabled: false
|
||||
|
||||
# Enforces that string literals should be written with a consistent form
|
||||
# of quotes (single or double).
|
||||
StringQuotes:
|
||||
enabled: false
|
||||
|
||||
# Property values, @extend, @include, and @import directives, and variable
|
||||
# declarations should always end with a semicolon.
|
||||
TrailingSemicolon:
|
||||
enabled: true
|
||||
|
||||
# Reports lines containing trailing whitespace.
|
||||
TrailingWhitespace:
|
||||
enabled: true
|
||||
|
||||
# Don't write trailing zeros for numeric values with a decimal point.
|
||||
TrailingZero:
|
||||
enabled: false
|
||||
|
||||
# Don't use the `all` keyword to specify transition properties.
|
||||
TransitionAll:
|
||||
enabled: false
|
||||
|
||||
# Numeric values should not contain unnecessary fractional portions.
|
||||
UnnecessaryMantissa:
|
||||
enabled: false
|
||||
|
||||
# Do not use parent selector references (&) when they would otherwise
|
||||
# be unnecessary.
|
||||
UnnecessaryParentReference:
|
||||
enabled: false
|
||||
|
||||
# URLs should be valid and not contain protocols or domain names.
|
||||
UrlFormat:
|
||||
enabled: true
|
||||
|
||||
# URLs should always be enclosed within quotes.
|
||||
UrlQuotes:
|
||||
enabled: true
|
||||
|
||||
# Properties, like color and font, are easier to read and maintain
|
||||
# when defined using variables rather than literals.
|
||||
VariableForProperty:
|
||||
enabled: false
|
||||
|
||||
# Avoid vendor prefixes. Or rather: don't write them yourself.
|
||||
VendorPrefix:
|
||||
enabled: false
|
||||
|
||||
# Omit length units on zero values, e.g. `0px` vs. `0`.
|
||||
ZeroUnit:
|
||||
enabled: true
|
59
.travis.yml
Normal file
59
.travis.yml
Normal file
|
@ -0,0 +1,59 @@
|
|||
language: ruby
|
||||
cache:
|
||||
bundler: true
|
||||
yarn: true
|
||||
directories:
|
||||
- node_modules
|
||||
- public/assets
|
||||
- public/packs-test
|
||||
- tmp/cache/babel-loader
|
||||
dist: trusty
|
||||
sudo: false
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
env:
|
||||
global:
|
||||
- LOCAL_DOMAIN=cb6e6126.ngrok.io
|
||||
- LOCAL_HTTPS=true
|
||||
- RAILS_ENV=test
|
||||
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true
|
||||
- PARALLEL_TEST_PROCESSORS=2
|
||||
|
||||
addons:
|
||||
postgresql: 9.4
|
||||
apt:
|
||||
sources:
|
||||
- trusty-media
|
||||
- sourceline: deb https://dl.yarnpkg.com/debian/ stable main
|
||||
key_url: https://dl.yarnpkg.com/debian/pubkey.gpg
|
||||
packages:
|
||||
- ffmpeg
|
||||
- libicu-dev
|
||||
- libprotobuf-dev
|
||||
- protobuf-compiler
|
||||
- yarn
|
||||
|
||||
rvm:
|
||||
- 2.4.2
|
||||
- 2.5.0
|
||||
|
||||
services:
|
||||
- redis-server
|
||||
|
||||
install:
|
||||
- nvm install
|
||||
- bundle install --path=vendor/bundle --without development production --retry=3 --jobs=16
|
||||
- yarn install
|
||||
|
||||
before_script:
|
||||
- ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile
|
||||
|
||||
script:
|
||||
- travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec
|
||||
- yarn run test:jest
|
||||
- bundle exec i18n-tasks check-normalized && bundle exec i18n-tasks unused
|
|
@ -43,4 +43,4 @@ Gruntfile.js
|
|||
|
||||
# for specific ignore
|
||||
!.svgo.yml
|
||||
!sass-lint/**/*.yml
|
||||
|
||||
|
|
1118
AUTHORS.md
1118
AUTHORS.md
File diff suppressed because it is too large
Load diff
18
Aptfile
18
Aptfile
|
@ -8,21 +8,3 @@ libprotobuf-dev
|
|||
libxdamage1
|
||||
libxfixes3
|
||||
protobuf-compiler
|
||||
zlib1g-dev
|
||||
libcairo2
|
||||
libcroco3
|
||||
libdatrie1
|
||||
libgdk-pixbuf2.0-0
|
||||
libgraphite2-3
|
||||
libharfbuzz0b
|
||||
libpango-1.0-0
|
||||
libpangocairo-1.0-0
|
||||
libpangoft2-1.0-0
|
||||
libpixman-1-0
|
||||
librsvg2-2
|
||||
libthai-data
|
||||
libthai0
|
||||
libvpx5
|
||||
libxcb-render0
|
||||
libxcb-shm0
|
||||
libxrender1
|
||||
|
|
1690
CHANGELOG.md
1690
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
@ -1,39 +1,51 @@
|
|||
Contributing
|
||||
CONTRIBUTING
|
||||
============
|
||||
|
||||
Thank you for considering contributing to Mastodon 🐘
|
||||
There are three ways in which you can contribute to this repository:
|
||||
|
||||
You can contribute in the following ways:
|
||||
1. By improving the documentation
|
||||
2. By working on the back-end application
|
||||
3. By working on the front-end application
|
||||
|
||||
- Finding and reporting bugs
|
||||
- Translating the Mastodon interface into various languages
|
||||
- Contributing code to Mastodon by fixing bugs or implementing features
|
||||
- Improving the documentation
|
||||
Choosing what to work on in a large open source project is not easy. The list of [GitHub issues](https://github.com/tootsuite/mastodon/issues) may provide some ideas, but not every feature request has been greenlit. Likewise, not every change or feature that resolves a personal itch will be merged into the main repository. Some communication ahead of time may be wise. If your addition creates a new feature or setting, or otherwise changes how things work in some substantial way, please remember to submit a correlating pull request to document your changes in the [documentation](http://github.com/tootsuite/documentation).
|
||||
|
||||
If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
|
||||
Below are the guidelines for working on pull requests:
|
||||
|
||||
## Bug reports
|
||||
## General
|
||||
|
||||
Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitHub Issues](https://github.com/tootsuite/mastodon/issues). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected.
|
||||
|
||||
## Translations
|
||||
|
||||
You can submit translations via [Crowdin](https://crowdin.com/project/mastodon). They are periodically merged into the codebase.
|
||||
|
||||
[](https://crowdin.com/project/mastodon)
|
||||
|
||||
## Pull requests
|
||||
|
||||
Please use clean, concise titles for your pull requests. We use commit squashing, so the final commit in the master branch will carry the title of the pull request.
|
||||
|
||||
The smaller the set of changes in the pull request is, the quicker it can be reviewed and merged. Splitting tasks into multiple smaller pull requests is often preferable.
|
||||
|
||||
**Pull requests that do not pass automated checks may not be reviewed**. In particular, you need to keep in mind:
|
||||
|
||||
- Unit and integration tests (rspec, jest)
|
||||
- Code style rules (rubocop, eslint)
|
||||
- Normalization of locale files (i18n-tasks)
|
||||
- 2 spaces indentation
|
||||
|
||||
## Documentation
|
||||
|
||||
The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/docs](https://source.joinmastodon.org/mastodon/docs).
|
||||
- No spelling mistakes
|
||||
- No orthographic mistakes
|
||||
- No Markdown syntax errors
|
||||
|
||||
## Requirements
|
||||
|
||||
- Ruby
|
||||
- Node.js
|
||||
- PostgreSQL
|
||||
- Redis
|
||||
- Nginx (optional)
|
||||
|
||||
## Back-end application
|
||||
|
||||
It is expected that you have a working development environment set up. The development environment includes [rubocop](https://github.com/bbatsov/rubocop), which checks your Ruby code for compliance with our style guide and best practices. Sublime Text, likely like other editors, has a [Rubocop plugin](https://github.com/pderichs/sublime_rubocop) that runs checks on files as you edit them. The codebase also has a test suite.
|
||||
|
||||
* The codebase is not perfect, at the time of writing, but it is expected that you do not introduce new code style violations
|
||||
* The rspec test suite must pass
|
||||
* To the extent that it is possible, verify your changes. In the best case, by adding new tests to the test suite. At the very least, by running the server or console and checking it manually
|
||||
* If you are introducing new strings to the user interface, they must be using localization methods
|
||||
|
||||
If your code has syntax errors that won't let it run, it's a good sign that the pull request isn't ready for submission yet.
|
||||
|
||||
## Front-end application
|
||||
|
||||
It is expected that you have a working development environment set up (see back-end application section). This project includes an ESLint configuration file, with which you can lint your changes.
|
||||
|
||||
* Avoid grave ESLint violations
|
||||
* Verify that your changes work
|
||||
* If you are introducing new strings, they must be using localization methods
|
||||
|
||||
If the JavaScript or CSS assets won't compile due to a syntax error, it's a good sign that the pull request isn't ready for submission yet.
|
||||
|
|
204
Dockerfile
204
Dockerfile
|
@ -1,144 +1,84 @@
|
|||
FROM ubuntu:20.04 as build-dep
|
||||
FROM ruby:2.4.3-alpine3.6
|
||||
|
||||
# Use bash for the shell
|
||||
SHELL ["bash", "-c"]
|
||||
LABEL maintainer="https://github.com/tootsuite/mastodon" \
|
||||
description="Your self-hosted, globally interconnected microblogging community"
|
||||
|
||||
# Install Node v12 (LTS)
|
||||
ENV NODE_VER="12.20.0"
|
||||
RUN ARCH= && \
|
||||
dpkgArch="$(dpkg --print-architecture)" && \
|
||||
case "${dpkgArch##*-}" in \
|
||||
amd64) ARCH='x64';; \
|
||||
ppc64el) ARCH='ppc64le';; \
|
||||
s390x) ARCH='s390x';; \
|
||||
arm64) ARCH='arm64';; \
|
||||
armhf) ARCH='armv7l';; \
|
||||
i386) ARCH='x86';; \
|
||||
*) echo "unsupported architecture"; exit 1 ;; \
|
||||
esac && \
|
||||
echo "Etc/UTC" > /etc/localtime && \
|
||||
apt update && \
|
||||
apt -y install wget python && \
|
||||
cd ~ && \
|
||||
wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \
|
||||
tar xf node-v$NODE_VER-linux-$ARCH.tar.gz && \
|
||||
rm node-v$NODE_VER-linux-$ARCH.tar.gz && \
|
||||
mv node-v$NODE_VER-linux-$ARCH /opt/node
|
||||
|
||||
# Install jemalloc
|
||||
ENV JE_VER="5.2.1"
|
||||
RUN apt update && \
|
||||
apt -y install make autoconf gcc g++ && \
|
||||
cd ~ && \
|
||||
wget https://github.com/jemalloc/jemalloc/archive/$JE_VER.tar.gz && \
|
||||
tar xf $JE_VER.tar.gz && \
|
||||
cd jemalloc-$JE_VER && \
|
||||
./autogen.sh && \
|
||||
./configure --prefix=/opt/jemalloc && \
|
||||
make -j$(nproc) > /dev/null && \
|
||||
make install_bin install_include install_lib && \
|
||||
cd .. && rm -rf jemalloc-$JE_VER $JE_VER.tar.gz
|
||||
|
||||
# Install Ruby
|
||||
ENV RUBY_VER="2.7.2"
|
||||
ENV CPPFLAGS="-I/opt/jemalloc/include"
|
||||
ENV LDFLAGS="-L/opt/jemalloc/lib/"
|
||||
RUN apt update && \
|
||||
apt -y install build-essential \
|
||||
bison libyaml-dev libgdbm-dev libreadline-dev \
|
||||
libncurses5-dev libffi-dev zlib1g-dev libssl-dev && \
|
||||
cd ~ && \
|
||||
wget https://cache.ruby-lang.org/pub/ruby/${RUBY_VER%.*}/ruby-$RUBY_VER.tar.gz && \
|
||||
tar xf ruby-$RUBY_VER.tar.gz && \
|
||||
cd ruby-$RUBY_VER && \
|
||||
./configure --prefix=/opt/ruby \
|
||||
--with-jemalloc \
|
||||
--with-shared \
|
||||
--disable-install-doc && \
|
||||
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
|
||||
make -j$(nproc) > /dev/null && \
|
||||
make install && \
|
||||
cd .. && rm -rf ruby-$RUBY_VER.tar.gz ruby-$RUBY_VER
|
||||
|
||||
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin"
|
||||
|
||||
RUN npm install -g yarn && \
|
||||
gem install bundler && \
|
||||
apt update && \
|
||||
apt -y install git libicu-dev libidn11-dev \
|
||||
libpq-dev libprotobuf-dev protobuf-compiler
|
||||
|
||||
COPY Gemfile* package.json yarn.lock /opt/mastodon/
|
||||
|
||||
RUN cd /opt/mastodon && \
|
||||
bundle config set deployment 'true' && \
|
||||
bundle config set without 'development test' && \
|
||||
bundle install -j$(nproc) && \
|
||||
yarn install --pure-lockfile
|
||||
|
||||
FROM ubuntu:20.04
|
||||
|
||||
# Copy over all the langs needed for runtime
|
||||
COPY --from=build-dep /opt/node /opt/node
|
||||
COPY --from=build-dep /opt/ruby /opt/ruby
|
||||
COPY --from=build-dep /opt/jemalloc /opt/jemalloc
|
||||
|
||||
# Add more PATHs to the PATH
|
||||
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
|
||||
|
||||
# Create the mastodon user
|
||||
ARG UID=991
|
||||
ARG GID=991
|
||||
RUN apt update && \
|
||||
echo "Etc/UTC" > /etc/localtime && \
|
||||
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
|
||||
apt install -y whois wget && \
|
||||
addgroup --gid $GID mastodon && \
|
||||
useradd -m -u $UID -g $GID -d /opt/mastodon mastodon && \
|
||||
echo "mastodon:`head /dev/urandom | tr -dc A-Za-z0-9 | head -c 24 | mkpasswd -s -m sha-256`" | chpasswd
|
||||
|
||||
# Install mastodon runtime deps
|
||||
RUN apt -y --no-install-recommends install \
|
||||
libssl1.1 libpq5 imagemagick ffmpeg \
|
||||
libicu66 libprotobuf17 libidn11 libyaml-0-2 \
|
||||
file ca-certificates tzdata libreadline8 && \
|
||||
apt -y install gcc && \
|
||||
ln -s /opt/mastodon /mastodon && \
|
||||
gem install bundler && \
|
||||
rm -rf /var/cache && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
ENV RAILS_SERVE_STATIC_FILES=true \
|
||||
RAILS_ENV=production NODE_ENV=production
|
||||
|
||||
# Add tini
|
||||
ENV TINI_VERSION="0.19.0"
|
||||
RUN dpkgArch="$(dpkg --print-architecture)" && \
|
||||
ARCH=$dpkgArch && \
|
||||
wget https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-$ARCH \
|
||||
https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-$ARCH.sha256sum && \
|
||||
cat tini-$ARCH.sha256sum | sha256sum -c - && \
|
||||
mv tini-$ARCH /tini && rm tini-$ARCH.sha256sum && \
|
||||
chmod +x /tini
|
||||
ARG YARN_VERSION=1.3.2
|
||||
ARG YARN_DOWNLOAD_SHA256=6cfe82e530ef0837212f13e45c1565ba53f5199eec2527b85ecbcd88bf26821d
|
||||
ARG LIBICONV_VERSION=1.15
|
||||
ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178
|
||||
|
||||
# Copy over mastodon source, and dependencies from building, and set permissions
|
||||
COPY --chown=mastodon:mastodon . /opt/mastodon
|
||||
COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon
|
||||
EXPOSE 3000 4000
|
||||
|
||||
# Run mastodon services in prod mode
|
||||
ENV RAILS_ENV="production"
|
||||
ENV NODE_ENV="production"
|
||||
WORKDIR /mastodon
|
||||
|
||||
# Tell rails to serve static files
|
||||
ENV RAILS_SERVE_STATIC_FILES="true"
|
||||
ENV BIND="0.0.0.0"
|
||||
RUN apk -U upgrade \
|
||||
&& apk add -t build-dependencies \
|
||||
build-base \
|
||||
icu-dev \
|
||||
libidn-dev \
|
||||
libressl \
|
||||
libtool \
|
||||
postgresql-dev \
|
||||
protobuf-dev \
|
||||
python \
|
||||
&& apk add \
|
||||
ca-certificates \
|
||||
ffmpeg \
|
||||
file \
|
||||
git \
|
||||
icu-libs \
|
||||
imagemagick \
|
||||
libidn \
|
||||
libpq \
|
||||
nodejs \
|
||||
nodejs-npm \
|
||||
protobuf \
|
||||
tini \
|
||||
tzdata \
|
||||
&& update-ca-certificates \
|
||||
&& mkdir -p /tmp/src /opt \
|
||||
&& wget -O yarn.tar.gz "https://github.com/yarnpkg/yarn/releases/download/v$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" \
|
||||
&& echo "$YARN_DOWNLOAD_SHA256 *yarn.tar.gz" | sha256sum -c - \
|
||||
&& tar -xzf yarn.tar.gz -C /tmp/src \
|
||||
&& rm yarn.tar.gz \
|
||||
&& mv /tmp/src/yarn-v$YARN_VERSION /opt/yarn \
|
||||
&& ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
|
||||
&& wget -O libiconv.tar.gz "https://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
|
||||
&& echo "$LIBICONV_DOWNLOAD_SHA256 *libiconv.tar.gz" | sha256sum -c - \
|
||||
&& tar -xzf libiconv.tar.gz -C /tmp/src \
|
||||
&& rm libiconv.tar.gz \
|
||||
&& cd /tmp/src/libiconv-$LIBICONV_VERSION \
|
||||
&& ./configure --prefix=/usr/local \
|
||||
&& make -j$(getconf _NPROCESSORS_ONLN)\
|
||||
&& make install \
|
||||
&& libtool --finish /usr/local/lib \
|
||||
&& cd /mastodon \
|
||||
&& rm -rf /tmp/* /var/cache/apk/*
|
||||
|
||||
COPY Gemfile Gemfile.lock package.json yarn.lock .yarnclean /mastodon/
|
||||
|
||||
RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \
|
||||
&& bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \
|
||||
&& yarn --pure-lockfile \
|
||||
&& yarn cache clean
|
||||
|
||||
RUN addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon \
|
||||
&& mkdir -p /mastodon/public/system /mastodon/public/assets /mastodon/public/packs \
|
||||
&& chown -R mastodon:mastodon /mastodon/public
|
||||
|
||||
COPY . /mastodon
|
||||
|
||||
RUN chown -R mastodon:mastodon /mastodon
|
||||
|
||||
VOLUME /mastodon/public/system /mastodon/public/assets /mastodon/public/packs
|
||||
|
||||
# Set the run user
|
||||
USER mastodon
|
||||
|
||||
# Precompile assets
|
||||
RUN cd ~ && \
|
||||
OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile && \
|
||||
yarn cache clean
|
||||
|
||||
# Set the work dir and the container entry point
|
||||
WORKDIR /opt/mastodon
|
||||
ENTRYPOINT ["/tini", "--"]
|
||||
EXPOSE 3000 4000
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
|
|
187
Gemfile
187
Gemfile
|
@ -1,163 +1,140 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
ruby '>= 2.5.0', '< 3.0.0'
|
||||
ruby '>= 2.3.0', '< 2.6.0'
|
||||
|
||||
gem 'pkg-config', '~> 1.4'
|
||||
gem 'pkg-config', '~> 1.2'
|
||||
|
||||
gem 'puma', '~> 5.0'
|
||||
gem 'rails', '~> 5.2.4.4'
|
||||
gem 'sprockets', '~> 3.7.2'
|
||||
gem 'thor', '~> 1.0'
|
||||
gem 'rack', '~> 2.2.3'
|
||||
gem 'puma', '~> 3.10'
|
||||
gem 'rails', '~> 5.1.4'
|
||||
|
||||
gem 'hamlit-rails', '~> 0.2'
|
||||
gem 'pg', '~> 1.2'
|
||||
gem 'makara', '~> 0.4'
|
||||
gem 'pghero', '~> 2.7'
|
||||
gem 'dotenv-rails', '~> 2.7'
|
||||
gem 'pg', '~> 0.20'
|
||||
gem 'pghero', '~> 1.7'
|
||||
gem 'dotenv-rails', '~> 2.2'
|
||||
|
||||
gem 'aws-sdk-s3', '~> 1.85', require: false
|
||||
gem 'fog-core', '<= 2.1.0'
|
||||
gem 'fog-openstack', '~> 0.3', require: false
|
||||
gem 'paperclip', '~> 6.0'
|
||||
gem 'aws-sdk', '~> 2.10', require: false
|
||||
gem 'fog-core', '~> 1.45'
|
||||
gem 'fog-local', '~> 0.4', require: false
|
||||
gem 'fog-openstack', '~> 0.1', require: false
|
||||
gem 'paperclip', '~> 5.1'
|
||||
gem 'paperclip-av-transcoder', '~> 0.6'
|
||||
gem 'streamio-ffmpeg', '~> 3.0'
|
||||
gem 'blurhash', '~> 0.1'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.10'
|
||||
gem 'addressable', '~> 2.7'
|
||||
gem 'bootsnap', '~> 1.5', require: false
|
||||
gem 'addressable', '~> 2.5'
|
||||
gem 'bootsnap'
|
||||
gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.7'
|
||||
gem 'charlock_holmes', '~> 0.7.5'
|
||||
gem 'iso-639'
|
||||
gem 'chewy', '~> 5.1'
|
||||
gem 'cld3', '~> 3.3.0'
|
||||
gem 'devise', '~> 4.7'
|
||||
gem 'devise-two-factor', '~> 3.1'
|
||||
gem 'chewy', '~> 5.0'
|
||||
gem 'cld3', '~> 3.2.0'
|
||||
gem 'devise', '~> 4.4'
|
||||
gem 'devise-two-factor', '~> 3.0'
|
||||
|
||||
group :pam_authentication, optional: true do
|
||||
gem 'devise_pam_authenticatable2', '~> 9.2'
|
||||
gem 'devise_pam_authenticatable2', '~> 9.0'
|
||||
end
|
||||
|
||||
gem 'net-ldap', '~> 0.16'
|
||||
gem 'omniauth-cas', '~> 2.0'
|
||||
gem 'net-ldap', '~> 0.10'
|
||||
gem 'omniauth-cas', '~> 1.1'
|
||||
gem 'omniauth-saml', '~> 1.10'
|
||||
gem 'omniauth', '~> 1.9'
|
||||
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
||||
gem 'omniauth', '~> 1.2'
|
||||
|
||||
gem 'color_diff', '~> 0.1'
|
||||
gem 'discard', '~> 1.2'
|
||||
gem 'doorkeeper', '~> 5.4'
|
||||
gem 'ed25519', '~> 1.2'
|
||||
gem 'doorkeeper', '~> 4.2'
|
||||
gem 'fast_blank', '~> 1.0'
|
||||
gem 'fastimage'
|
||||
gem 'goldfinger', '~> 2.1'
|
||||
gem 'hiredis', '~> 0.6'
|
||||
gem 'redis-namespace', '~> 1.8'
|
||||
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
|
||||
gem 'redis-namespace', '~> 1.5'
|
||||
gem 'htmlentities', '~> 4.3'
|
||||
gem 'http', '~> 4.4'
|
||||
gem 'http', '~> 3.0'
|
||||
gem 'http_accept_language', '~> 2.1'
|
||||
gem 'httplog', '~> 1.4.3'
|
||||
gem 'httplog', '~> 0.99'
|
||||
gem 'idn-ruby', require: 'idn'
|
||||
gem 'kaminari', '~> 1.2'
|
||||
gem 'kaminari', '~> 1.1'
|
||||
gem 'link_header', '~> 0.0'
|
||||
gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
|
||||
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
|
||||
gem 'nokogiri', '~> 1.10'
|
||||
gem 'mime-types', '~> 3.1'
|
||||
gem 'nokogiri', '~> 1.8'
|
||||
gem 'nsa', '~> 0.2'
|
||||
gem 'oj', '~> 3.10'
|
||||
gem 'ox', '~> 2.13'
|
||||
gem 'parslet'
|
||||
gem 'parallel', '~> 1.20'
|
||||
gem 'posix-spawn'
|
||||
gem 'pundit', '~> 2.1'
|
||||
gem 'oj', '~> 3.3'
|
||||
gem 'ostatus2', '~> 2.0'
|
||||
gem 'ox', '~> 2.8'
|
||||
gem 'pundit', '~> 1.1'
|
||||
gem 'premailer-rails'
|
||||
gem 'rack-attack', '~> 6.3'
|
||||
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
||||
gem 'rails-i18n', '~> 5.1'
|
||||
gem 'rack-attack', '~> 5.0'
|
||||
gem 'rack-cors', '~> 0.4', require: 'rack/cors'
|
||||
gem 'rack-timeout', '~> 0.4'
|
||||
gem 'rails-i18n', '~> 5.0'
|
||||
gem 'rails-settings-cached', '~> 0.6'
|
||||
gem 'redis', '~> 4.2', require: ['redis', 'redis/connection/hiredis']
|
||||
gem 'redis', '~> 3.3', require: ['redis', 'redis/connection/hiredis']
|
||||
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
||||
gem 'rqrcode', '~> 1.1'
|
||||
gem 'ruby-progressbar', '~> 1.10'
|
||||
gem 'sanitize', '~> 5.2'
|
||||
gem 'scenic', '~> 1.5'
|
||||
gem 'sidekiq', '~> 6.1'
|
||||
gem 'sidekiq-scheduler', '~> 3.0'
|
||||
gem 'sidekiq-unique-jobs', '~> 6.0'
|
||||
gem 'sidekiq-bulk', '~>0.2.0'
|
||||
gem 'simple-navigation', '~> 4.1'
|
||||
gem 'simple_form', '~> 5.0'
|
||||
gem 'rqrcode', '~> 0.10'
|
||||
gem 'ruby-oembed', '~> 0.12', require: 'oembed'
|
||||
gem 'ruby-progressbar', '~> 1.4'
|
||||
gem 'sanitize', '~> 4.6.4'
|
||||
gem 'sidekiq', '~> 5.0'
|
||||
gem 'sidekiq-scheduler', '~> 2.1'
|
||||
gem 'sidekiq-unique-jobs', '~> 5.0'
|
||||
gem 'sidekiq-bulk', '~>0.1.1'
|
||||
gem 'simple-navigation', '~> 4.0'
|
||||
gem 'simple_form', '~> 3.4'
|
||||
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
||||
gem 'stoplight', '~> 2.2.1'
|
||||
gem 'strong_migrations', '~> 0.7'
|
||||
gem 'tty-prompt', '~> 0.22', require: false
|
||||
gem 'strong_migrations'
|
||||
gem 'tty-command'
|
||||
gem 'tty-prompt'
|
||||
gem 'twitter-text', '~> 1.14'
|
||||
gem 'tzinfo-data', '~> 1.2020'
|
||||
gem 'webpacker', '~> 5.2'
|
||||
gem 'tzinfo-data', '~> 1.2017'
|
||||
gem 'webpacker', '~> 3.0'
|
||||
gem 'webpush'
|
||||
gem 'webauthn', '~> 3.0.0.alpha1'
|
||||
|
||||
gem 'json-ld'
|
||||
gem 'json-ld-preloaded', '~> 3.1'
|
||||
gem 'rdf-normalize', '~> 0.4'
|
||||
gem 'json-ld-preloaded', '~> 2.2.1'
|
||||
gem 'rdf-normalize', '~> 0.3.1'
|
||||
|
||||
group :development, :test do
|
||||
gem 'fabrication', '~> 2.21'
|
||||
gem 'fuubar', '~> 2.5'
|
||||
gem 'fabrication', '~> 2.18'
|
||||
gem 'fuubar', '~> 2.2'
|
||||
gem 'i18n-tasks', '~> 0.9', require: false
|
||||
gem 'pry-byebug', '~> 3.9'
|
||||
gem 'pry-rails', '~> 0.3'
|
||||
gem 'rspec-rails', '~> 4.0'
|
||||
gem 'rspec-rails', '~> 3.7'
|
||||
end
|
||||
|
||||
group :production, :test do
|
||||
gem 'private_address_check', '~> 0.5'
|
||||
gem 'private_address_check', '~> 0.4.1'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'capybara', '~> 3.33'
|
||||
gem 'capybara', '~> 2.15'
|
||||
gem 'climate_control', '~> 0.2'
|
||||
gem 'faker', '~> 2.14'
|
||||
gem 'microformats', '~> 4.2'
|
||||
gem 'faker', '~> 1.7'
|
||||
gem 'microformats', '~> 4.0'
|
||||
gem 'rails-controller-testing', '~> 1.0'
|
||||
gem 'rspec-sidekiq', '~> 3.1'
|
||||
gem 'simplecov', '~> 0.19', require: false
|
||||
gem 'webmock', '~> 3.10'
|
||||
gem 'parallel_tests', '~> 3.4'
|
||||
gem 'rspec_junit_formatter', '~> 0.4'
|
||||
gem 'rspec-sidekiq', '~> 3.0'
|
||||
gem 'simplecov', '~> 0.14', require: false
|
||||
gem 'webmock', '~> 3.0'
|
||||
gem 'parallel_tests', '~> 2.17'
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'active_record_query_trace', '~> 1.8'
|
||||
gem 'annotate', '~> 3.1'
|
||||
gem 'better_errors', '~> 2.9'
|
||||
gem 'active_record_query_trace', '~> 1.5'
|
||||
gem 'annotate', '~> 2.7'
|
||||
gem 'better_errors', '~> 2.4'
|
||||
gem 'binding_of_caller', '~> 0.7'
|
||||
gem 'bullet', '~> 6.1'
|
||||
gem 'letter_opener', '~> 1.7'
|
||||
gem 'letter_opener_web', '~> 1.4'
|
||||
gem 'bullet', '~> 5.5'
|
||||
gem 'letter_opener', '~> 1.4'
|
||||
gem 'letter_opener_web', '~> 1.3'
|
||||
gem 'memory_profiler'
|
||||
gem 'rubocop', '~> 1.3', require: false
|
||||
gem 'rubocop-rails', '~> 2.8', require: false
|
||||
gem 'brakeman', '~> 4.10', require: false
|
||||
gem 'bundler-audit', '~> 0.7', require: false
|
||||
gem 'rubocop', require: false
|
||||
gem 'brakeman', '~> 4.0', require: false
|
||||
gem 'bundler-audit', '~> 0.6', require: false
|
||||
gem 'scss_lint', '~> 0.55', require: false
|
||||
|
||||
gem 'capistrano', '~> 3.14'
|
||||
gem 'capistrano-rails', '~> 1.6'
|
||||
gem 'capistrano-rbenv', '~> 2.2'
|
||||
gem 'capistrano', '~> 3.10'
|
||||
gem 'capistrano-rails', '~> 1.3'
|
||||
gem 'capistrano-rbenv', '~> 2.1'
|
||||
gem 'capistrano-yarn', '~> 2.0'
|
||||
|
||||
gem 'stackprof'
|
||||
end
|
||||
|
||||
group :production do
|
||||
gem 'lograge', '~> 0.11'
|
||||
gem 'lograge', '~> 0.7'
|
||||
gem 'redis-rails', '~> 5.0'
|
||||
end
|
||||
|
||||
gem 'concurrent-ruby', require: false
|
||||
gem 'connection_pool', require: false
|
||||
|
||||
gem 'xorcist', '~> 1.1'
|
||||
gem 'pluck_each', '~> 0.1.3'
|
||||
|
|
1074
Gemfile.lock
1074
Gemfile.lock
File diff suppressed because it is too large
Load diff
14
Procfile
14
Procfile
|
@ -1,14 +1,2 @@
|
|||
web: bin/heroku-web
|
||||
web: bundle exec puma -C config/puma.rb
|
||||
worker: bundle exec sidekiq
|
||||
|
||||
# For the streaming API, you need a separate app that shares Postgres and Redis:
|
||||
#
|
||||
# heroku create
|
||||
# heroku buildpacks:add heroku/nodejs
|
||||
# heroku config:set RUN_STREAMING=true
|
||||
# heroku addons:attach <main-app>::DATABASE
|
||||
# heroku addons:attach <main-app>::REDIS
|
||||
#
|
||||
# and let the main app use the separate app:
|
||||
#
|
||||
# heroku config:set STREAMING_API_BASE_URL=wss://<streaming-app>.herokuapp.com -a <main-app>
|
||||
|
|
101
README.md
101
README.md
|
@ -1,95 +1,96 @@
|
|||

|
||||
========
|
||||
|
||||
[][releases]
|
||||
[][circleci]
|
||||
[][travis]
|
||||
[][code_climate]
|
||||
[][crowdin]
|
||||
[][docker]
|
||||
|
||||
[releases]: https://github.com/tootsuite/mastodon/releases
|
||||
[circleci]: https://circleci.com/gh/tootsuite/mastodon
|
||||
[travis]: https://travis-ci.org/tootsuite/mastodon
|
||||
[code_climate]: https://codeclimate.com/github/tootsuite/mastodon
|
||||
[crowdin]: https://crowdin.com/project/mastodon
|
||||
[docker]: https://hub.docker.com/r/tootsuite/mastodon/
|
||||
|
||||
Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub)!
|
||||
Mastodon is a **free, open-source social network server** based on **open web protocols** like ActivityPub and OStatus. The social focus of the project is a viable decentralized alternative to commercial social media silos that returns the control of the content distribution channels to the people. The technical focus of the project is a good user interface, a clean REST API for 3rd party apps and robust anti-abuse tools.
|
||||
|
||||
Click below to **learn more** in a video:
|
||||
Click on the screenshot below to watch a demo of the UI:
|
||||
|
||||
[][youtube_demo]
|
||||
[][youtube_demo]
|
||||
|
||||
[youtube_demo]: https://www.youtube.com/watch?v=IPSbNdBmWKE
|
||||
[youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU
|
||||
|
||||
## Navigation
|
||||
**Ruby on Rails** is used for the back-end, while **React.js** and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided.
|
||||
|
||||
- [Project homepage 🐘](https://joinmastodon.org)
|
||||
- [Support the development via Patreon][patreon]
|
||||
- [View sponsors](https://joinmastodon.org/sponsors)
|
||||
- [Blog](https://blog.joinmastodon.org)
|
||||
- [Documentation](https://docs.joinmastodon.org)
|
||||
- [Browse Mastodon servers](https://joinmastodon.org/#getting-started)
|
||||
- [Browse Mastodon apps](https://joinmastodon.org/apps)
|
||||
If you would like, you can [support the development of this project on Patreon][patreon] or [Liberapay][liberapay]. Alternatively, you can donate to this BTC address: `17j2g7vpgHhLuXhN4bueZFCvdxxieyRVWd`
|
||||
|
||||
[patreon]: https://www.patreon.com/mastodon
|
||||
[patreon]: https://www.patreon.com/user?u=619786
|
||||
[liberapay]: https://liberapay.com/Mastodon/
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Frequently Asked Questions](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md)
|
||||
- [Use this tool to find Twitter friends on Mastodon](https://bridge.joinmastodon.org)
|
||||
- [API overview](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md)
|
||||
- [List of Mastodon instances](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md)
|
||||
- [List of apps](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md)
|
||||
- [List of sponsors](https://joinmastodon.org/sponsors)
|
||||
|
||||
## Features
|
||||
|
||||
<img src="https://docs.joinmastodon.org/elephant.svg" align="right" width="30%" />
|
||||
|
||||
**No vendor lock-in: Fully interoperable with any conforming platform**
|
||||
|
||||
It doesn't have to be Mastodon, whatever implements ActivityPub is part of the social network! [Learn more](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/)
|
||||
It doesn't have to be Mastodon, whatever implements ActivityPub or OStatus is part of the social network!
|
||||
|
||||
**Real-time, chronological timeline updates**
|
||||
**Real-time timeline updates**
|
||||
|
||||
See the updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well!
|
||||
|
||||
**Federated thread resolving**
|
||||
|
||||
If someone you follow replies to a user unknown to the server, the server fetches the full thread so you can view it without leaving the UI
|
||||
|
||||
**Media attachments like images and short videos**
|
||||
|
||||
Upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos are looped - like vines!
|
||||
|
||||
**Safety and moderation tools**
|
||||
|
||||
Private posts, locked accounts, phrase filtering, muting, blocking and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/)
|
||||
|
||||
**OAuth2 and a straightforward REST API**
|
||||
|
||||
Mastodon acts as an OAuth2 provider so 3rd party apps can use the REST and Streaming APIs, resulting in a rich app ecosystem with a lot of choices!
|
||||
Mastodon acts as an OAuth2 provider so 3rd party apps can use the API
|
||||
|
||||
**Fast response times**
|
||||
|
||||
Mastodon tries to be as fast and responsive as possible, so all long-running tasks are delegated to background processing
|
||||
|
||||
**Deployable via Docker**
|
||||
|
||||
You don't need to mess with dependencies and configuration if you want to try Mastodon, if you have Docker and Docker Compose the deployment is extremely easy
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
Please follow the [development guide](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Development-guide.md) from the documentation repository.
|
||||
|
||||
## Deployment
|
||||
|
||||
**Tech stack:**
|
||||
|
||||
- **Ruby on Rails** powers the REST API and other web pages
|
||||
- **React.js** and Redux are used for the dynamic parts of the interface
|
||||
- **Node.js** powers the streaming API
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- **PostgreSQL** 9.5+
|
||||
- **Redis** 4+
|
||||
- **Ruby** 2.5+
|
||||
- **Node.js** 10.13+
|
||||
|
||||
The repository includes deployment configurations for **Docker and docker-compose**, but also a few specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. The [**stand-alone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
|
||||
|
||||
A **Vagrant** configuration is included for development purposes.
|
||||
There are guides in the documentation repository for [deploying on various platforms](https://github.com/tootsuite/documentation#running-mastodon).
|
||||
|
||||
## Contributing
|
||||
|
||||
Mastodon is **free, open-source software** licensed under **AGPLv3**.
|
||||
|
||||
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository, or submit translations using Crowdin. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
|
||||
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository. [Here are the guidelines for code contributions](CONTRIBUTING.md)
|
||||
|
||||
**IRC channel**: #mastodon on irc.freenode.net
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2016-2020 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md))
|
||||
Copyright (C) 2016-2018 Eugen Rochko & other Mastodon contributors (see AUTHORS.md)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
---
|
||||
|
||||
## Extra credits
|
||||
|
||||
The elephant friend illustrations are created by [Dopatwo](https://mastodon.social/@dopatwo)
|
||||
|
|
12
SECURITY.md
12
SECURITY.md
|
@ -1,12 +0,0 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 3.1.x | :white_check_mark: |
|
||||
| < 3.1 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
hello@joinmastodon.org
|
31
Vagrantfile
vendored
31
Vagrantfile
vendored
|
@ -12,7 +12,7 @@ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
|||
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
|
||||
|
||||
# Add repo for NodeJS
|
||||
curl -sL https://deb.nodesource.com/setup_10.x | sudo bash -
|
||||
curl -sL https://deb.nodesource.com/setup_6.x | sudo bash -
|
||||
|
||||
# Add firewall rule to redirect 80 to PORT and save
|
||||
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]}
|
||||
|
@ -44,18 +44,7 @@ sudo apt-get install \
|
|||
|
||||
# Install rvm
|
||||
read RUBY_VERSION < .ruby-version
|
||||
|
||||
gpg_command="gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB"
|
||||
$($gpg_command)
|
||||
if [ $? -ne 0 ];then
|
||||
echo "GPG command failed, This prevented RVM from installing."
|
||||
echo "Retrying once..." && $($gpg_command)
|
||||
if [ $? -ne 0 ];then
|
||||
echo "GPG failed for the second time, please ensure network connectivity."
|
||||
echo "Exiting..." && exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
|
||||
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
|
||||
source /home/vagrant/.rvm/scripts/rvm
|
||||
|
||||
|
@ -91,14 +80,11 @@ VAGRANTFILE_API_VERSION = "2"
|
|||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
config.vm.box = "ubuntu/bionic64"
|
||||
config.vm.box = "ubuntu/xenial64"
|
||||
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
vb.name = "mastodon"
|
||||
vb.customize ["modifyvm", :id, "--memory", "2048"]
|
||||
# Increase the number of CPUs. Uncomment and adjust to
|
||||
# increase performance
|
||||
# vb.customize ["modifyvm", :id, "--cpus", "3"]
|
||||
|
||||
# Disable VirtualBox DNS proxy to skip long-delay IPv6 resolutions.
|
||||
# https://github.com/mitchellh/vagrant/issues/1172
|
||||
|
@ -111,22 +97,19 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|||
|
||||
end
|
||||
|
||||
config.vm.hostname = "mastodon.dev"
|
||||
|
||||
# This uses the vagrant-hostsupdater plugin, and lets you
|
||||
# access the development site at http://mastodon.local.
|
||||
# If you change it, also change it in .env.vagrant before provisioning
|
||||
# the vagrant server to update the development build.
|
||||
#
|
||||
# access the development site at http://mastodon.dev.
|
||||
# To install:
|
||||
# $ vagrant plugin install vagrant-hostsupdater
|
||||
config.vm.hostname = "mastodon.local"
|
||||
|
||||
if defined?(VagrantPlugins::HostsUpdater)
|
||||
config.vm.network :private_network, ip: "192.168.42.42", nictype: "virtio"
|
||||
config.hostsupdater.remove_on_suspend = false
|
||||
end
|
||||
|
||||
if config.vm.networks.any? { |type, options| type == :private_network }
|
||||
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'vers=3', 'tcp', 'actimeo=1']
|
||||
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'vers=3', 'tcp']
|
||||
else
|
||||
config.vm.synced_folder ".", "/vagrant"
|
||||
end
|
||||
|
|
12
app.json
12
app.json
|
@ -13,6 +13,15 @@
|
|||
"description": "The domain that your Mastodon instance will run on (this can be appname.herokuapp.com or a custom domain)",
|
||||
"required": true
|
||||
},
|
||||
"LOCAL_HTTPS": {
|
||||
"description": "Will your domain support HTTPS? (Automatic for herokuapp, requires manual configuration for custom domains)",
|
||||
"value": "false",
|
||||
"required": true
|
||||
},
|
||||
"PAPERCLIP_SECRET": {
|
||||
"description": "The secret key for storing media files",
|
||||
"generator": "secret"
|
||||
},
|
||||
"SECRET_KEY_BASE": {
|
||||
"description": "The secret key base",
|
||||
"generator": "secret"
|
||||
|
@ -88,6 +97,9 @@
|
|||
{
|
||||
"url": "https://github.com/heroku/heroku-buildpack-apt"
|
||||
},
|
||||
{
|
||||
"url": "heroku/nodejs"
|
||||
},
|
||||
{
|
||||
"url": "heroku/ruby"
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountsIndex < Chewy::Index
|
||||
settings index: { refresh_interval: '5m' }, analysis: {
|
||||
analyzer: {
|
||||
content: {
|
||||
tokenizer: 'whitespace',
|
||||
filter: %w(lowercase asciifolding cjk_width),
|
||||
},
|
||||
|
||||
edge_ngram: {
|
||||
tokenizer: 'edge_ngram',
|
||||
filter: %w(lowercase asciifolding cjk_width),
|
||||
},
|
||||
},
|
||||
|
||||
tokenizer: {
|
||||
edge_ngram: {
|
||||
type: 'edge_ngram',
|
||||
min_gram: 1,
|
||||
max_gram: 15,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
define_type ::Account.searchable.includes(:account_stat), delete_if: ->(account) { account.destroyed? || !account.searchable? } do
|
||||
root date_detection: false do
|
||||
field :id, type: 'long'
|
||||
|
||||
field :display_name, type: 'text', analyzer: 'content' do
|
||||
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
|
||||
end
|
||||
|
||||
field :acct, type: 'text', analyzer: 'content', value: ->(account) { [account.username, account.domain].compact.join('@') } do
|
||||
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
|
||||
end
|
||||
|
||||
field :following_count, type: 'long', value: ->(account) { account.following.local.count }
|
||||
field :followers_count, type: 'long', value: ->(account) { account.followers.local.count }
|
||||
field :last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,36 +31,31 @@ class StatusesIndex < Chewy::Index
|
|||
},
|
||||
}
|
||||
|
||||
define_type ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preloadable_poll) do
|
||||
define_type ::Status.without_reblogs do
|
||||
crutch :mentions do |collection|
|
||||
data = ::Mention.where(status_id: collection.map(&:id)).where(account: Account.local, silent: false).pluck(:status_id, :account_id)
|
||||
data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
|
||||
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
||||
end
|
||||
|
||||
crutch :favourites do |collection|
|
||||
data = ::Favourite.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id)
|
||||
data = ::Favourite.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
|
||||
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
||||
end
|
||||
|
||||
crutch :reblogs do |collection|
|
||||
data = ::Status.where(reblog_of_id: collection.map(&:id)).where(account: Account.local).pluck(:reblog_of_id, :account_id)
|
||||
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
||||
end
|
||||
|
||||
crutch :bookmarks do |collection|
|
||||
data = ::Bookmark.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id)
|
||||
data = ::Status.where(reblog_of_id: collection.map(&:id)).pluck(:reblog_of_id, :account_id)
|
||||
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
||||
end
|
||||
|
||||
root date_detection: false do
|
||||
field :id, type: 'long'
|
||||
field :account_id, type: 'long'
|
||||
|
||||
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
|
||||
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].join("\n\n") } do
|
||||
field :stemmed, type: 'text', analyzer: 'content'
|
||||
end
|
||||
|
||||
field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }
|
||||
field :created_at, type: 'date'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TagsIndex < Chewy::Index
|
||||
settings index: { refresh_interval: '15m' }, analysis: {
|
||||
analyzer: {
|
||||
content: {
|
||||
tokenizer: 'keyword',
|
||||
filter: %w(lowercase asciifolding cjk_width),
|
||||
},
|
||||
|
||||
edge_ngram: {
|
||||
tokenizer: 'edge_ngram',
|
||||
filter: %w(lowercase asciifolding cjk_width),
|
||||
},
|
||||
},
|
||||
|
||||
tokenizer: {
|
||||
edge_ngram: {
|
||||
type: 'edge_ngram',
|
||||
min_gram: 2,
|
||||
max_gram: 15,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
define_type ::Tag.listable, delete_if: ->(tag) { tag.destroyed? || !tag.listable? } do
|
||||
root date_detection: false do
|
||||
field :name, type: 'text', analyzer: 'content' do
|
||||
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
|
||||
end
|
||||
|
||||
field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? }
|
||||
field :usage, type: 'long', value: ->(tag) { tag.history.reduce(0) { |total, day| total + day[:accounts].to_i } }
|
||||
field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,67 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AboutController < ApplicationController
|
||||
include RegistrationSpamConcern
|
||||
before_action :set_body_classes
|
||||
before_action :set_instance_presenter, only: [:show, :more, :terms]
|
||||
|
||||
layout 'public'
|
||||
|
||||
before_action :require_open_federation!, only: [:show, :more]
|
||||
before_action :set_body_classes, only: :show
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_expires_in, only: [:more, :terms]
|
||||
before_action :set_registration_form_time, only: :show
|
||||
|
||||
skip_before_action :require_functional!, only: [:more, :terms]
|
||||
|
||||
def show; end
|
||||
|
||||
def more
|
||||
flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor]
|
||||
|
||||
toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description)
|
||||
|
||||
@contents = toc_generator.html
|
||||
@table_of_contents = toc_generator.toc
|
||||
@blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
|
||||
def show
|
||||
serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
|
||||
@initial_state_json = serializable_resource.to_json
|
||||
end
|
||||
|
||||
def more; end
|
||||
|
||||
def terms; end
|
||||
|
||||
helper_method :display_blocks?
|
||||
helper_method :display_blocks_rationale?
|
||||
helper_method :public_fetch_mode?
|
||||
helper_method :new_user
|
||||
|
||||
private
|
||||
|
||||
def require_open_federation!
|
||||
not_found if whitelist_mode?
|
||||
end
|
||||
|
||||
def display_blocks?
|
||||
Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?)
|
||||
end
|
||||
|
||||
def display_blocks_rationale?
|
||||
Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?)
|
||||
end
|
||||
|
||||
def new_user
|
||||
User.new.tap do |user|
|
||||
user.build_account
|
||||
user.build_invite_request
|
||||
end
|
||||
User.new.tap(&:build_account)
|
||||
end
|
||||
|
||||
helper_method :new_user
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@hide_navbar = true
|
||||
@body_classes = 'about-body'
|
||||
end
|
||||
|
||||
def set_expires_in
|
||||
expires_in 0, public: true
|
||||
def initial_state_params
|
||||
{
|
||||
settings: { known_fediverse: Setting.show_known_fediverse_at_about_page },
|
||||
token: current_session&.token,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ class AccountFollowController < ApplicationController
|
|||
before_action :authenticate_user!
|
||||
|
||||
def create
|
||||
FollowService.new.call(current_user.account, @account, with_rate_limit: true)
|
||||
FollowService.new.call(current_user.account, @account.acct)
|
||||
redirect_to account_path(@account)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,27 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountsController < ApplicationController
|
||||
PAGE_SIZE = 20
|
||||
PAGE_SIZE_MAX = 200
|
||||
PAGE_SIZE = 20
|
||||
|
||||
include AccountControllerConcern
|
||||
include SignatureAuthentication
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
before_action :set_body_classes
|
||||
|
||||
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
@pinned_statuses = []
|
||||
@endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
|
||||
@featured_hashtags = @account.featured_tags.order(statuses_count: :desc)
|
||||
@pinned_statuses = []
|
||||
|
||||
if current_account && @account.blocking?(current_account)
|
||||
@statuses = []
|
||||
|
@ -29,44 +18,37 @@ class AccountsController < ApplicationController
|
|||
end
|
||||
|
||||
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
|
||||
@statuses = cached_filtered_status_page
|
||||
@rss_url = rss_url
|
||||
|
||||
@statuses = filtered_status_page(params)
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
unless @statuses.empty?
|
||||
@older_url = older_url if @statuses.last.id > filtered_statuses.last.id
|
||||
@newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id
|
||||
@older_url = older_url if @statuses.last.id > filtered_statuses.last.id
|
||||
@newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id
|
||||
end
|
||||
end
|
||||
|
||||
format.rss do
|
||||
expires_in 1.minute, public: true
|
||||
|
||||
limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
|
||||
@statuses = filtered_statuses.without_reblogs.limit(limit)
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
|
||||
format.atom do
|
||||
@entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id])
|
||||
render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? }))
|
||||
end
|
||||
|
||||
format.json do
|
||||
expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
|
||||
render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter
|
||||
skip_session!
|
||||
|
||||
render_cached_json(['activitypub', 'actor', @account.cache_key], content_type: 'application/activity+json') do
|
||||
ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def show_pinned_statuses?
|
||||
[replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
|
||||
[replies_requested?, media_requested?, params[:max_id].present?, params[:since_id].present?].none?
|
||||
end
|
||||
|
||||
def filtered_statuses
|
||||
default_statuses.tap do |statuses|
|
||||
statuses.merge!(hashtag_scope) if tag_requested?
|
||||
statuses.merge!(only_media_scope) if media_requested?
|
||||
statuses.merge!(no_replies_scope) unless replies_requested?
|
||||
end
|
||||
|
@ -81,40 +63,19 @@ class AccountsController < ApplicationController
|
|||
end
|
||||
|
||||
def account_media_status_ids
|
||||
@account.media_attachments.attached.reorder(nil).select(:status_id).group(:status_id)
|
||||
@account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
||||
end
|
||||
|
||||
def no_replies_scope
|
||||
Status.without_replies
|
||||
end
|
||||
|
||||
def hashtag_scope
|
||||
tag = Tag.find_normalized(params[:tag])
|
||||
|
||||
if tag
|
||||
Status.tagged_with(tag.id)
|
||||
else
|
||||
Status.none
|
||||
end
|
||||
end
|
||||
|
||||
def username_param
|
||||
params[:username]
|
||||
end
|
||||
|
||||
def skip_temporary_suspension_response?
|
||||
request.format == :json
|
||||
end
|
||||
|
||||
def rss_url
|
||||
if tag_requested?
|
||||
short_account_tag_url(@account, params[:tag], format: 'rss')
|
||||
else
|
||||
short_account_url(@account, format: 'rss')
|
||||
end
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:username])
|
||||
end
|
||||
|
||||
def older_url
|
||||
::Rails.logger.info("older: max_id #{@statuses.last.id}, url #{pagination_url(max_id: @statuses.last.id)}")
|
||||
pagination_url(max_id: @statuses.last.id)
|
||||
end
|
||||
|
||||
|
@ -123,9 +84,7 @@ class AccountsController < ApplicationController
|
|||
end
|
||||
|
||||
def pagination_url(max_id: nil, min_id: nil)
|
||||
if tag_requested?
|
||||
short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id)
|
||||
elsif media_requested?
|
||||
if media_requested?
|
||||
short_account_media_url(@account, max_id: max_id, min_id: min_id)
|
||||
elsif replies_requested?
|
||||
short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
|
||||
|
@ -135,27 +94,18 @@ class AccountsController < ApplicationController
|
|||
end
|
||||
|
||||
def media_requested?
|
||||
request.path.split('.').first.ends_with?('/media') && !tag_requested?
|
||||
request.path.ends_with?('/media')
|
||||
end
|
||||
|
||||
def replies_requested?
|
||||
request.path.split('.').first.ends_with?('/with_replies') && !tag_requested?
|
||||
request.path.ends_with?('/with_replies')
|
||||
end
|
||||
|
||||
def tag_requested?
|
||||
request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
|
||||
end
|
||||
|
||||
def cached_filtered_status_page
|
||||
cache_collection_paginated_by_id(
|
||||
filtered_statuses,
|
||||
Status,
|
||||
PAGE_SIZE,
|
||||
params_slice(:max_id, :min_id, :since_id)
|
||||
)
|
||||
end
|
||||
|
||||
def params_slice(*keys)
|
||||
params.slice(*keys).permit(*keys)
|
||||
def filtered_status_page(params)
|
||||
if params[:min_id].present?
|
||||
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
|
||||
else
|
||||
filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::BaseController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!
|
||||
|
||||
private
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
|
||||
end
|
||||
|
||||
def skip_temporary_suspension_response?
|
||||
false
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::ClaimsController < ActivityPub::BaseController
|
||||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
before_action :require_signature!
|
||||
before_action :set_claim_result
|
||||
|
||||
def create
|
||||
render json: @claim_result, serializer: ActivityPub::OneTimeKeySerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_claim_result
|
||||
@claim_result = ::Keys::ClaimService.new.call(@account.id, params[:id])
|
||||
end
|
||||
end
|
|
@ -1,73 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::CollectionsController < ActivityPub::BaseController
|
||||
class ActivityPub::CollectionsController < Api::BaseController
|
||||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_items
|
||||
before_action :set_account
|
||||
before_action :set_size
|
||||
before_action :set_type
|
||||
before_action :set_cache_headers
|
||||
before_action :set_statuses
|
||||
|
||||
def show
|
||||
expires_in 3.minutes, public: public_fetch_mode?
|
||||
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
||||
render json: collection_presenter,
|
||||
serializer: ActivityPub::CollectionSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json',
|
||||
skip_activities: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_items
|
||||
case params[:id]
|
||||
when 'featured'
|
||||
@items = for_signed_account { cache_collection(@account.pinned_statuses, Status) }
|
||||
when 'tags'
|
||||
@items = for_signed_account { @account.featured_tags }
|
||||
when 'devices'
|
||||
@items = @account.devices
|
||||
else
|
||||
not_found
|
||||
end
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def set_statuses
|
||||
@statuses = scope_for_collection.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
end
|
||||
|
||||
def set_size
|
||||
case params[:id]
|
||||
when 'featured', 'devices', 'tags'
|
||||
@size = @items.size
|
||||
when 'featured'
|
||||
@account.pinned_statuses.count
|
||||
else
|
||||
not_found
|
||||
raise ActiveRecord::NotFound
|
||||
end
|
||||
end
|
||||
|
||||
def set_type
|
||||
def scope_for_collection
|
||||
case params[:id]
|
||||
when 'featured'
|
||||
@type = :ordered
|
||||
when 'devices', 'tags'
|
||||
@type = :unordered
|
||||
@account.statuses.permitted_for(@account, signed_request_account).tap do |scope|
|
||||
scope.merge!(@account.pinned_statuses)
|
||||
end
|
||||
else
|
||||
not_found
|
||||
raise ActiveRecord::NotFound
|
||||
end
|
||||
end
|
||||
|
||||
def collection_presenter
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_collection_url(@account, params[:id]),
|
||||
type: @type,
|
||||
type: :ordered,
|
||||
size: @size,
|
||||
items: @items
|
||||
items: @statuses
|
||||
)
|
||||
end
|
||||
|
||||
def for_signed_account
|
||||
# Because in public fetch mode we cache the response, there would be no
|
||||
# benefit from performing the check below, since a blocked account or domain
|
||||
# would likely be served the cache from the reverse proxy anyway
|
||||
|
||||
if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
|
||||
[]
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseController
|
||||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :require_signature!
|
||||
before_action :set_items
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
expires_in 0, public: false
|
||||
render json: collection_presenter,
|
||||
serializer: ActivityPub::CollectionSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def uri_prefix
|
||||
signed_request_account.uri[/http(s?):\/\/[^\/]+\//]
|
||||
end
|
||||
|
||||
def set_items
|
||||
@items = @account.followers.where(Account.arel_table[:uri].matches(uri_prefix + '%', false, true)).pluck(:uri)
|
||||
end
|
||||
|
||||
def collection_presenter
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_followers_synchronization_url(@account),
|
||||
type: :ordered,
|
||||
items: @items
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,51 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::InboxesController < ActivityPub::BaseController
|
||||
class ActivityPub::InboxesController < Api::BaseController
|
||||
include SignatureVerification
|
||||
include JsonLdHelper
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :skip_unknown_actor_delete
|
||||
before_action :require_signature!
|
||||
skip_before_action :authenticate_user!
|
||||
before_action :set_account
|
||||
|
||||
def create
|
||||
upgrade_account
|
||||
process_collection_synchronization
|
||||
process_payload
|
||||
head 202
|
||||
if signed_request_account
|
||||
upgrade_account
|
||||
process_payload
|
||||
head 202
|
||||
else
|
||||
render plain: signature_verification_failure_reason, status: 401
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def skip_unknown_actor_delete
|
||||
head 202 if unknown_deleted_account?
|
||||
end
|
||||
|
||||
def unknown_deleted_account?
|
||||
json = Oj.load(body, mode: :strict)
|
||||
json.is_a?(Hash) && json['type'] == 'Delete' && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists?
|
||||
rescue Oj::ParseError
|
||||
false
|
||||
end
|
||||
|
||||
def account_required?
|
||||
params[:account_username].present?
|
||||
end
|
||||
|
||||
def skip_temporary_suspension_response?
|
||||
true
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username]) if params[:account_username]
|
||||
end
|
||||
|
||||
def body
|
||||
return @body if defined?(@body)
|
||||
|
||||
@body = request.body.read
|
||||
@body.force_encoding('UTF-8') if @body.present?
|
||||
|
||||
request.body.rewind if request.body.respond_to?(:rewind)
|
||||
|
||||
@body
|
||||
@body ||= request.body.read
|
||||
end
|
||||
|
||||
def upgrade_account
|
||||
|
@ -54,23 +31,11 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
|
|||
ResolveAccountWorker.perform_async(signed_request_account.acct)
|
||||
end
|
||||
|
||||
DeliveryFailureTracker.reset!(signed_request_account.inbox_url)
|
||||
end
|
||||
|
||||
def process_collection_synchronization
|
||||
raw_params = request.headers['Collection-Synchronization']
|
||||
return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true'
|
||||
|
||||
# Re-using the syntax for signature parameters
|
||||
tree = SignatureParamsParser.new.parse(raw_params)
|
||||
params = SignatureParamsTransformer.new.apply(tree)
|
||||
|
||||
ActivityPub::PrepareFollowersSynchronizationService.new.call(signed_request_account, params)
|
||||
rescue Parslet::ParseFailed
|
||||
Rails.logger.warn 'Error parsing Collection-Synchronization header'
|
||||
Pubsubhubbub::UnsubscribeWorker.perform_async(signed_request_account.id) if signed_request_account.subscribed?
|
||||
DeliveryFailureTracker.track_inverse_success!(signed_request_account)
|
||||
end
|
||||
|
||||
def process_payload
|
||||
ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id)
|
||||
ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body.force_encoding('UTF-8'))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,79 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||
LIMIT = 20
|
||||
|
||||
class ActivityPub::OutboxesController < Api::BaseController
|
||||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_statuses
|
||||
before_action :set_cache_headers
|
||||
before_action :set_account
|
||||
|
||||
def show
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?))
|
||||
@statuses = @account.statuses.permitted_for(@account, signed_request_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
|
||||
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def outbox_presenter
|
||||
if page_requested?
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: outbox_url(page_params),
|
||||
type: :ordered,
|
||||
part_of: outbox_url,
|
||||
prev: prev_page,
|
||||
next: next_page,
|
||||
items: @statuses
|
||||
)
|
||||
else
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_outbox_url(@account),
|
||||
type: :ordered,
|
||||
size: @account.statuses_count,
|
||||
first: outbox_url(page: true),
|
||||
last: outbox_url(page: true, min_id: 0)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def outbox_url(**kwargs)
|
||||
if params[:account_username].present?
|
||||
account_outbox_url(@account, **kwargs)
|
||||
else
|
||||
instance_actor_outbox_url(**kwargs)
|
||||
end
|
||||
end
|
||||
|
||||
def next_page
|
||||
account_outbox_url(@account, page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT
|
||||
end
|
||||
|
||||
def prev_page
|
||||
account_outbox_url(@account, page: true, min_id: @statuses.first.id) unless @statuses.empty?
|
||||
end
|
||||
|
||||
def set_statuses
|
||||
return unless page_requested?
|
||||
|
||||
@statuses = cache_collection_paginated_by_id(
|
||||
@account.statuses.permitted_for(@account, signed_request_account),
|
||||
Status,
|
||||
LIMIT,
|
||||
params_slice(:max_id, :min_id, :since_id)
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_outbox_url(@account),
|
||||
type: :ordered,
|
||||
size: @account.statuses_count,
|
||||
items: @statuses
|
||||
)
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
truthy_param?(:page)
|
||||
end
|
||||
|
||||
def page_params
|
||||
{ page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::RepliesController < ActivityPub::BaseController
|
||||
include SignatureVerification
|
||||
include Authorization
|
||||
include AccountOwnedConcern
|
||||
|
||||
DESCENDANTS_LIMIT = 60
|
||||
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_status
|
||||
before_action :set_cache_headers
|
||||
before_action :set_replies
|
||||
|
||||
def index
|
||||
expires_in 0, public: public_fetch_mode?
|
||||
render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pundit_user
|
||||
signed_request_account
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def set_replies
|
||||
@replies = only_other_accounts? ? Status.where.not(account_id: @account.id).joins(:account).merge(Account.without_suspended) : @account.statuses
|
||||
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
|
||||
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
|
||||
end
|
||||
|
||||
def replies_collection_presenter
|
||||
page = ActivityPub::CollectionPresenter.new(
|
||||
id: account_status_replies_url(@account, @status, page_params),
|
||||
type: :unordered,
|
||||
part_of: account_status_replies_url(@account, @status),
|
||||
next: next_page,
|
||||
items: @replies.map { |status| status.local? ? status : status.uri }
|
||||
)
|
||||
|
||||
return page if page_requested?
|
||||
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_status_replies_url(@account, @status),
|
||||
type: :unordered,
|
||||
first: page
|
||||
)
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
truthy_param?(:page)
|
||||
end
|
||||
|
||||
def only_other_accounts?
|
||||
truthy_param?(:only_other_accounts)
|
||||
end
|
||||
|
||||
def next_page
|
||||
only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)
|
||||
|
||||
account_status_replies_url(
|
||||
@account,
|
||||
@status,
|
||||
page: true,
|
||||
min_id: only_other_accounts && !only_other_accounts? ? nil : @replies&.last&.id,
|
||||
only_other_accounts: only_other_accounts
|
||||
)
|
||||
end
|
||||
|
||||
def page_params
|
||||
params_slice(:only_other_accounts, :min_id).merge(page: true)
|
||||
end
|
||||
end
|
|
@ -1,36 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class AccountActionsController < BaseController
|
||||
before_action :set_account
|
||||
|
||||
def new
|
||||
@account_action = Admin::AccountAction.new(type: params[:type], report_id: params[:report_id], send_email_notification: true, include_statuses: true)
|
||||
@warning_presets = AccountWarningPreset.all
|
||||
end
|
||||
|
||||
def create
|
||||
account_action = Admin::AccountAction.new(resource_params)
|
||||
account_action.target_account = @account
|
||||
account_action.current_account = current_account
|
||||
|
||||
account_action.save!
|
||||
|
||||
if account_action.with_report?
|
||||
redirect_to admin_reports_path
|
||||
else
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification, :include_statuses)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,7 +14,6 @@ module Admin
|
|||
else
|
||||
@account = @account_moderation_note.target_account
|
||||
@moderation_notes = @account.targeted_moderation_notes.latest
|
||||
@warnings = @account.targeted_account_warnings.latest.custom
|
||||
|
||||
render template: 'admin/accounts/show'
|
||||
end
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
module Admin
|
||||
class AccountsController < BaseController
|
||||
before_action :set_account, except: [:index]
|
||||
before_action :require_remote_account!, only: [:redownload]
|
||||
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
|
||||
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :enable, :disable, :memorialize]
|
||||
before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
|
||||
before_action :require_local_account!, only: [:enable, :disable, :memorialize]
|
||||
|
||||
def index
|
||||
authorize :account, :index?
|
||||
|
@ -13,97 +13,51 @@ module Admin
|
|||
|
||||
def show
|
||||
authorize @account, :show?
|
||||
|
||||
@deletion_request = @account.deletion_request
|
||||
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
|
||||
@moderation_notes = @account.targeted_moderation_notes.latest
|
||||
@warnings = @account.targeted_account_warnings.latest.custom
|
||||
@domain_block = DomainBlock.rule_for(@account.domain)
|
||||
@moderation_notes = @account.targeted_moderation_notes.latest
|
||||
end
|
||||
|
||||
def subscribe
|
||||
authorize @account, :subscribe?
|
||||
Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def unsubscribe
|
||||
authorize @account, :unsubscribe?
|
||||
Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id)
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def memorialize
|
||||
authorize @account, :memorialize?
|
||||
@account.memorialize!
|
||||
log_action :memorialize, @account
|
||||
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.memorialized_msg', username: @account.acct)
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def enable
|
||||
authorize @account.user, :enable?
|
||||
@account.user.enable!
|
||||
log_action :enable, @account.user
|
||||
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.enabled_msg', username: @account.acct)
|
||||
end
|
||||
|
||||
def approve
|
||||
authorize @account.user, :approve?
|
||||
@account.user.approve!
|
||||
redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
|
||||
end
|
||||
|
||||
def reject
|
||||
authorize @account.user, :reject?
|
||||
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
||||
redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @account, :destroy?
|
||||
Admin::AccountDeletionWorker.perform_async(@account.id)
|
||||
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct)
|
||||
end
|
||||
|
||||
def unsensitive
|
||||
authorize @account, :unsensitive?
|
||||
@account.unsensitize!
|
||||
log_action :unsensitive, @account
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def unsilence
|
||||
authorize @account, :unsilence?
|
||||
@account.unsilence!
|
||||
log_action :unsilence, @account
|
||||
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsilenced_msg', username: @account.acct)
|
||||
end
|
||||
|
||||
def unsuspend
|
||||
authorize @account, :unsuspend?
|
||||
@account.unsuspend!
|
||||
Admin::UnsuspensionWorker.perform_async(@account.id)
|
||||
log_action :unsuspend, @account
|
||||
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsuspended_msg', username: @account.acct)
|
||||
def disable
|
||||
authorize @account.user, :disable?
|
||||
@account.user.disable!
|
||||
log_action :disable, @account.user
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def redownload
|
||||
authorize @account, :redownload?
|
||||
|
||||
@account.update!(last_webfingered_at: nil)
|
||||
ResolveAccountService.new.call(@account)
|
||||
|
||||
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.redownloaded_msg', username: @account.acct)
|
||||
end
|
||||
|
||||
def remove_avatar
|
||||
authorize @account, :remove_avatar?
|
||||
|
||||
@account.avatar = nil
|
||||
@account.reset_avatar!
|
||||
@account.reset_header!
|
||||
@account.save!
|
||||
|
||||
log_action :remove_avatar, @account.user
|
||||
|
||||
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_avatar_msg', username: @account.acct)
|
||||
end
|
||||
|
||||
def remove_header
|
||||
authorize @account, :remove_header?
|
||||
|
||||
@account.header = nil
|
||||
@account.save!
|
||||
|
||||
log_action :remove_header, @account.user
|
||||
|
||||
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_header_msg', username: @account.acct)
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -125,7 +79,19 @@ module Admin
|
|||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(*AccountFilter::KEYS).permit(*AccountFilter::KEYS)
|
||||
params.permit(
|
||||
:local,
|
||||
:remote,
|
||||
:by_domain,
|
||||
:silenced,
|
||||
:recent,
|
||||
:suspended,
|
||||
:username,
|
||||
:display_name,
|
||||
:email,
|
||||
:ip,
|
||||
:staff
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,18 +2,8 @@
|
|||
|
||||
module Admin
|
||||
class ActionLogsController < BaseController
|
||||
before_action :set_action_logs
|
||||
|
||||
def index; end
|
||||
|
||||
private
|
||||
|
||||
def set_action_logs
|
||||
@action_logs = Admin::ActionLogFilter.new(filter_params).results.page(params[:page])
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(:page, *Admin::ActionLogFilter::KEYS).permit(:page, *Admin::ActionLogFilter::KEYS)
|
||||
def index
|
||||
@action_logs = Admin::ActionLog.page(params[:page])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::AnnouncementsController < Admin::BaseController
|
||||
before_action :set_announcements, only: :index
|
||||
before_action :set_announcement, except: [:index, :new, :create]
|
||||
|
||||
def index
|
||||
authorize :announcement, :index?
|
||||
end
|
||||
|
||||
def new
|
||||
authorize :announcement, :create?
|
||||
|
||||
@announcement = Announcement.new
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :announcement, :create?
|
||||
|
||||
@announcement = Announcement.new(resource_params)
|
||||
|
||||
if @announcement.save
|
||||
PublishScheduledAnnouncementWorker.perform_async(@announcement.id) if @announcement.published?
|
||||
log_action :create, @announcement
|
||||
redirect_to admin_announcements_path, notice: @announcement.published? ? I18n.t('admin.announcements.published_msg') : I18n.t('admin.announcements.scheduled_msg')
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize :announcement, :update?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize :announcement, :update?
|
||||
|
||||
if @announcement.update(resource_params)
|
||||
PublishScheduledAnnouncementWorker.perform_async(@announcement.id) if @announcement.published?
|
||||
log_action :update, @announcement
|
||||
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.updated_msg')
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def publish
|
||||
authorize :announcement, :update?
|
||||
@announcement.publish!
|
||||
PublishScheduledAnnouncementWorker.perform_async(@announcement.id)
|
||||
log_action :update, @announcement
|
||||
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.published_msg')
|
||||
end
|
||||
|
||||
def unpublish
|
||||
authorize :announcement, :update?
|
||||
@announcement.unpublish!
|
||||
UnpublishAnnouncementWorker.perform_async(@announcement.id)
|
||||
log_action :update, @announcement
|
||||
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.unpublished_msg')
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize :announcement, :destroy?
|
||||
@announcement.destroy!
|
||||
UnpublishAnnouncementWorker.perform_async(@announcement.id) if @announcement.published?
|
||||
log_action :destroy, @announcement
|
||||
redirect_to admin_announcements_path, notice: I18n.t('admin.announcements.destroyed_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_announcements
|
||||
@announcements = AnnouncementFilter.new(filter_params).results.reverse_chronological.page(params[:page])
|
||||
end
|
||||
|
||||
def set_announcement
|
||||
@announcement = Announcement.find(params[:id])
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(*AnnouncementFilter::KEYS).permit(*AnnouncementFilter::KEYS)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:announcement).permit(:text, :scheduled_at, :starts_at, :ends_at, :all_day)
|
||||
end
|
||||
end
|
|
@ -5,19 +5,8 @@ module Admin
|
|||
include Authorization
|
||||
include AccountableConcern
|
||||
|
||||
layout 'admin'
|
||||
|
||||
before_action :require_staff!
|
||||
before_action :set_body_classes
|
||||
|
||||
private
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_user
|
||||
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
layout 'admin'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class ChangeEmailsController < BaseController
|
||||
before_action :set_account
|
||||
before_action :require_local_account!
|
||||
|
||||
def show
|
||||
authorize @user, :change_email?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @user, :change_email?
|
||||
|
||||
new_email = resource_params.fetch(:unconfirmed_email)
|
||||
|
||||
if new_email != @user.email
|
||||
@user.update!(
|
||||
unconfirmed_email: new_email,
|
||||
# Regenerate the confirmation token:
|
||||
confirmation_token: nil
|
||||
)
|
||||
|
||||
log_action :change_email, @user
|
||||
|
||||
@user.send_confirmation_instructions
|
||||
end
|
||||
|
||||
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.change_email.changed_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
@user = @account.user
|
||||
end
|
||||
|
||||
def require_local_account!
|
||||
redirect_to admin_account_path(@account.id) unless @account.local? && @account.user.present?
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:user).permit(
|
||||
:unconfirmed_email
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,7 +3,6 @@
|
|||
module Admin
|
||||
class ConfirmationsController < BaseController
|
||||
before_action :set_user
|
||||
before_action :check_confirmation, only: [:resend]
|
||||
|
||||
def create
|
||||
authorize @user, :confirm?
|
||||
|
@ -12,24 +11,10 @@ module Admin
|
|||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
def resend
|
||||
authorize @user, :confirm?
|
||||
|
||||
@user.resend_confirmation_instructions
|
||||
|
||||
log_action :confirm, @user
|
||||
|
||||
flash[:notice] = I18n.t('admin.accounts.resend_confirmation.success')
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_confirmation
|
||||
if @user.confirmed?
|
||||
flash[:error] = I18n.t('admin.accounts.resend_confirmation.already_confirmed')
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
def set_user
|
||||
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
|
||||
module Admin
|
||||
class CustomEmojisController < BaseController
|
||||
before_action :set_custom_emoji, except: [:index, :new, :create]
|
||||
before_action :set_filter_params
|
||||
|
||||
def index
|
||||
authorize :custom_emoji, :index?
|
||||
|
||||
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
|
||||
@form = Form::CustomEmojiBatch.new
|
||||
end
|
||||
|
||||
def new
|
||||
authorize :custom_emoji, :create?
|
||||
|
||||
@custom_emoji = CustomEmoji.new
|
||||
end
|
||||
|
||||
|
@ -28,19 +28,69 @@ module Admin
|
|||
end
|
||||
end
|
||||
|
||||
def batch
|
||||
@form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
rescue Mastodon::NotPermittedError
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
|
||||
ensure
|
||||
redirect_to admin_custom_emojis_path(filter_params)
|
||||
def update
|
||||
authorize @custom_emoji, :update?
|
||||
|
||||
if @custom_emoji.update(resource_params)
|
||||
log_action :update, @custom_emoji
|
||||
flash[:notice] = I18n.t('admin.custom_emojis.updated_msg')
|
||||
else
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.update_failed_msg')
|
||||
end
|
||||
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @custom_emoji, :destroy?
|
||||
@custom_emoji.destroy!
|
||||
log_action :destroy, @custom_emoji
|
||||
flash[:notice] = I18n.t('admin.custom_emojis.destroyed_msg')
|
||||
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
|
||||
end
|
||||
|
||||
def copy
|
||||
authorize @custom_emoji, :copy?
|
||||
|
||||
emoji = CustomEmoji.find_or_initialize_by(domain: nil,
|
||||
shortcode: @custom_emoji.shortcode)
|
||||
emoji.image = @custom_emoji.image
|
||||
|
||||
if emoji.save
|
||||
log_action :create, emoji
|
||||
flash[:notice] = I18n.t('admin.custom_emojis.copied_msg')
|
||||
else
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
|
||||
end
|
||||
|
||||
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
|
||||
end
|
||||
|
||||
def enable
|
||||
authorize @custom_emoji, :enable?
|
||||
@custom_emoji.update!(disabled: false)
|
||||
log_action :enable, @custom_emoji
|
||||
flash[:notice] = I18n.t('admin.custom_emojis.enabled_msg')
|
||||
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
|
||||
end
|
||||
|
||||
def disable
|
||||
authorize @custom_emoji, :disable?
|
||||
@custom_emoji.update!(disabled: true)
|
||||
log_action :disable, @custom_emoji
|
||||
flash[:notice] = I18n.t('admin.custom_emojis.disabled_msg')
|
||||
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_custom_emoji
|
||||
@custom_emoji = CustomEmoji.find(params[:id])
|
||||
end
|
||||
|
||||
def set_filter_params
|
||||
@filter_params = filter_params.to_hash.symbolize_keys
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
|
||||
end
|
||||
|
@ -50,29 +100,12 @@ module Admin
|
|||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(:page, *CustomEmojiFilter::KEYS).permit(:page, *CustomEmojiFilter::KEYS)
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:update]
|
||||
'update'
|
||||
elsif params[:list]
|
||||
'list'
|
||||
elsif params[:unlist]
|
||||
'unlist'
|
||||
elsif params[:enable]
|
||||
'enable'
|
||||
elsif params[:disable]
|
||||
'disable'
|
||||
elsif params[:copy]
|
||||
'copy'
|
||||
elsif params[:delete]
|
||||
'delete'
|
||||
end
|
||||
end
|
||||
|
||||
def form_custom_emoji_batch_params
|
||||
params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
|
||||
params.permit(
|
||||
:local,
|
||||
:remote,
|
||||
:by_domain,
|
||||
:shortcode
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
require 'sidekiq/api'
|
||||
|
||||
module Admin
|
||||
class DashboardController < BaseController
|
||||
def index
|
||||
@users_count = User.count
|
||||
@pending_users_count = User.pending.count
|
||||
@registrations_week = Redis.current.get("activity:accounts:local:#{current_week}") || 0
|
||||
@logins_week = Redis.current.pfcount("activity:logins:#{current_week}")
|
||||
@interactions_week = Redis.current.get("activity:interactions:#{current_week}") || 0
|
||||
@relay_enabled = Relay.enabled.exists?
|
||||
@single_user_mode = Rails.configuration.x.single_user_mode
|
||||
@registrations_enabled = Setting.registrations_mode != 'none'
|
||||
@deletions_enabled = Setting.open_deletion
|
||||
@invites_enabled = Setting.min_invite_role == 'user'
|
||||
@search_enabled = Chewy.enabled?
|
||||
@version = Mastodon::Version.to_s
|
||||
@database_version = ActiveRecord::Base.connection.execute('SELECT VERSION()').first['version'].match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
|
||||
@redis_version = redis_info['redis_version']
|
||||
@reports_count = Report.unresolved.count
|
||||
@queue_backlog = Sidekiq::Stats.new.enqueued
|
||||
@recent_users = User.confirmed.recent.includes(:account).limit(8)
|
||||
@database_size = ActiveRecord::Base.connection.execute('SELECT pg_database_size(current_database())').first['pg_database_size']
|
||||
@redis_size = redis_info['used_memory']
|
||||
@ldap_enabled = ENV['LDAP_ENABLED'] == 'true'
|
||||
@cas_enabled = ENV['CAS_ENABLED'] == 'true'
|
||||
@saml_enabled = ENV['SAML_ENABLED'] == 'true'
|
||||
@pam_enabled = ENV['PAM_ENABLED'] == 'true'
|
||||
@hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true'
|
||||
@trending_hashtags = TrendingTags.get(10, filtered: false)
|
||||
@pending_tags_count = Tag.pending_review.count
|
||||
@authorized_fetch = authorized_fetch_mode?
|
||||
@whitelist_enabled = whitelist_mode?
|
||||
@profile_directory = Setting.profile_directory
|
||||
@timeline_preview = Setting.timeline_preview
|
||||
@spam_check_enabled = Setting.spam_check_enabled
|
||||
@trends_enabled = Setting.trends
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_week
|
||||
@current_week ||= Time.now.utc.to_date.cweek
|
||||
end
|
||||
|
||||
def redis_info
|
||||
@redis_info ||= begin
|
||||
if Redis.current.is_a?(Redis::Namespace)
|
||||
Redis.current.redis.info
|
||||
else
|
||||
Redis.current.info
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,40 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::DomainAllowsController < Admin::BaseController
|
||||
before_action :set_domain_allow, only: [:destroy]
|
||||
|
||||
def new
|
||||
authorize :domain_allow, :create?
|
||||
|
||||
@domain_allow = DomainAllow.new(domain: params[:_domain])
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :domain_allow, :create?
|
||||
|
||||
@domain_allow = DomainAllow.new(resource_params)
|
||||
|
||||
if @domain_allow.save
|
||||
log_action :create, @domain_allow
|
||||
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.created_msg')
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @domain_allow, :destroy?
|
||||
UnallowDomainService.new.call(@domain_allow)
|
||||
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_domain_allow
|
||||
@domain_allow = DomainAllow.find(params[:id])
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:domain_allow).permit(:domain)
|
||||
end
|
||||
end
|
|
@ -2,57 +2,29 @@
|
|||
|
||||
module Admin
|
||||
class DomainBlocksController < BaseController
|
||||
before_action :set_domain_block, only: [:show, :destroy, :edit, :update]
|
||||
before_action :set_domain_block, only: [:show, :destroy]
|
||||
|
||||
def index
|
||||
authorize :domain_block, :index?
|
||||
@domain_blocks = DomainBlock.page(params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
authorize :domain_block, :create?
|
||||
@domain_block = DomainBlock.new(domain: params[:_domain])
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize :domain_block, :create?
|
||||
@domain_block = DomainBlock.new
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :domain_block, :create?
|
||||
|
||||
@domain_block = DomainBlock.new(resource_params)
|
||||
existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil
|
||||
|
||||
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
|
||||
@domain_block.save
|
||||
flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe # rubocop:disable Rails/OutputSafety
|
||||
@domain_block.errors[:domain].clear
|
||||
render :new
|
||||
else
|
||||
if existing_domain_block.present?
|
||||
@domain_block = existing_domain_block
|
||||
@domain_block.update(resource_params)
|
||||
end
|
||||
|
||||
if @domain_block.save
|
||||
DomainBlockWorker.perform_async(@domain_block.id)
|
||||
log_action :create, @domain_block
|
||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize :domain_block, :update?
|
||||
|
||||
@domain_block.update(update_params)
|
||||
|
||||
severity_changed = @domain_block.severity_changed?
|
||||
|
||||
if @domain_block.save
|
||||
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
|
||||
log_action :update, @domain_block
|
||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
||||
DomainBlockWorker.perform_async(@domain_block.id)
|
||||
log_action :create, @domain_block
|
||||
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.created_msg')
|
||||
else
|
||||
render :edit
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -62,9 +34,9 @@ module Admin
|
|||
|
||||
def destroy
|
||||
authorize @domain_block, :destroy?
|
||||
UnblockDomainService.new.call(@domain_block)
|
||||
UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
|
||||
log_action :destroy, @domain_block
|
||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.destroyed_msg')
|
||||
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -73,12 +45,12 @@ module Admin
|
|||
@domain_block = DomainBlock.find(params[:id])
|
||||
end
|
||||
|
||||
def update_params
|
||||
params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
|
||||
def resource_params
|
||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :retroactive)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
|
||||
def retroactive_unblock?
|
||||
ActiveRecord::Type.lookup(:boolean).cast(resource_params[:retroactive])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,12 +6,12 @@ module Admin
|
|||
|
||||
def index
|
||||
authorize :email_domain_block, :index?
|
||||
@email_domain_blocks = EmailDomainBlock.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page])
|
||||
@email_domain_blocks = EmailDomainBlock.page(params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
authorize :email_domain_block, :create?
|
||||
@email_domain_block = EmailDomainBlock.new(domain: params[:_domain])
|
||||
@email_domain_block = EmailDomainBlock.new
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -21,28 +21,6 @@ module Admin
|
|||
|
||||
if @email_domain_block.save
|
||||
log_action :create, @email_domain_block
|
||||
|
||||
if @email_domain_block.with_dns_records?
|
||||
hostnames = []
|
||||
ips = []
|
||||
|
||||
Resolv::DNS.open do |dns|
|
||||
dns.timeouts = 5
|
||||
|
||||
hostnames = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }
|
||||
|
||||
([@email_domain_block.domain] + hostnames).uniq.each do |hostname|
|
||||
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s })
|
||||
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s })
|
||||
end
|
||||
end
|
||||
|
||||
(hostnames + ips).each do |hostname|
|
||||
another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: @email_domain_block)
|
||||
log_action :create, another_email_domain_block if another_email_domain_block.save
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
|
||||
else
|
||||
render :new
|
||||
|
@ -63,7 +41,7 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:email_domain_block).permit(:domain, :with_dns_records)
|
||||
params.require(:email_domain_block).permit(:domain)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,33 +2,42 @@
|
|||
|
||||
module Admin
|
||||
class InstancesController < BaseController
|
||||
before_action :set_instances, only: :index
|
||||
before_action :set_instance, only: :show
|
||||
|
||||
def index
|
||||
authorize :instance, :index?
|
||||
@instances = ordered_instances
|
||||
end
|
||||
|
||||
def show
|
||||
authorize :instance, :show?
|
||||
def resubscribe
|
||||
authorize :instance, :resubscribe?
|
||||
params.require(:by_domain)
|
||||
Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id))
|
||||
redirect_to admin_instances_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_instance
|
||||
@instance = Instance.find(params[:id])
|
||||
end
|
||||
|
||||
def set_instances
|
||||
@instances = filtered_instances.page(params[:page])
|
||||
end
|
||||
|
||||
def filtered_instances
|
||||
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
|
||||
InstanceFilter.new(filter_params).results
|
||||
end
|
||||
|
||||
def paginated_instances
|
||||
filtered_instances.page(params[:page])
|
||||
end
|
||||
|
||||
helper_method :paginated_instances
|
||||
|
||||
def ordered_instances
|
||||
paginated_instances.map { |account| Instance.new(account) }
|
||||
end
|
||||
|
||||
def subscribeable_accounts
|
||||
Account.with_followers.remote.where(domain: params[:by_domain])
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(*InstanceFilter::KEYS).permit(*InstanceFilter::KEYS)
|
||||
params.permit(
|
||||
:domain_name
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,12 +30,6 @@ module Admin
|
|||
redirect_to admin_invites_path
|
||||
end
|
||||
|
||||
def deactivate_all
|
||||
authorize :invite, :deactivate_all?
|
||||
Invite.available.in_batches.update_all(expires_at: Time.now.utc)
|
||||
redirect_to admin_invites_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
|
@ -47,7 +41,7 @@ module Admin
|
|||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(*InviteFilter::KEYS).permit(*InviteFilter::KEYS)
|
||||
params.permit(:available, :expired)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class IpBlocksController < BaseController
|
||||
def index
|
||||
authorize :ip_block, :index?
|
||||
|
||||
@ip_blocks = IpBlock.page(params[:page])
|
||||
@form = Form::IpBlockBatch.new
|
||||
end
|
||||
|
||||
def new
|
||||
authorize :ip_block, :create?
|
||||
|
||||
@ip_block = IpBlock.new(ip: '', severity: :no_access, expires_in: 1.year)
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :ip_block, :create?
|
||||
|
||||
@ip_block = IpBlock.new(resource_params)
|
||||
|
||||
if @ip_block.save
|
||||
log_action :create, @ip_block
|
||||
redirect_to admin_ip_blocks_path, notice: I18n.t('admin.ip_blocks.created_msg')
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def batch
|
||||
@form = Form::IpBlockBatch.new(form_ip_block_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.ip_blocks.no_ip_block_selected')
|
||||
rescue Mastodon::NotPermittedError
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
|
||||
ensure
|
||||
redirect_to admin_ip_blocks_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:ip_block).permit(:ip, :severity, :comment, :expires_in)
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
'delete' if params[:delete]
|
||||
end
|
||||
|
||||
def form_ip_block_batch_params
|
||||
params.require(:form_ip_block_batch).permit(ip_block_ids: [])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,52 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class PendingAccountsController < BaseController
|
||||
before_action :set_accounts, only: :index
|
||||
|
||||
def index
|
||||
@form = Form::AccountBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
@form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
ensure
|
||||
redirect_to admin_pending_accounts_path(current_params)
|
||||
end
|
||||
|
||||
def approve_all
|
||||
Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'approve').save
|
||||
redirect_to admin_pending_accounts_path(current_params)
|
||||
end
|
||||
|
||||
def reject_all
|
||||
Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'reject').save
|
||||
redirect_to admin_pending_accounts_path(current_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_accounts
|
||||
@accounts = Account.joins(:user).merge(User.pending.recent).includes(user: :invite_request).page(params[:page])
|
||||
end
|
||||
|
||||
def form_account_batch_params
|
||||
params.require(:form_account_batch).permit(:action, account_ids: [])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:approve]
|
||||
'approve'
|
||||
elsif params[:reject]
|
||||
'reject'
|
||||
end
|
||||
end
|
||||
|
||||
def current_params
|
||||
params.slice(:page).permit(:page)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,25 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class RelationshipsController < BaseController
|
||||
before_action :set_account
|
||||
|
||||
PER_PAGE = 40
|
||||
|
||||
def index
|
||||
authorize :account, :index?
|
||||
|
||||
@accounts = RelationshipFilter.new(@account, filter_params).results.page(params[:page]).per(PER_PAGE)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(*RelationshipFilter::KEYS).permit(*RelationshipFilter::KEYS)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,63 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class RelaysController < BaseController
|
||||
before_action :set_relay, except: [:index, :new, :create]
|
||||
before_action :require_signatures_enabled!, only: [:new, :create, :enable]
|
||||
|
||||
def index
|
||||
authorize :relay, :update?
|
||||
@relays = Relay.all
|
||||
end
|
||||
|
||||
def new
|
||||
authorize :relay, :update?
|
||||
@relay = Relay.new
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :relay, :update?
|
||||
|
||||
@relay = Relay.new(resource_params)
|
||||
|
||||
if @relay.save
|
||||
@relay.enable!
|
||||
redirect_to admin_relays_path
|
||||
else
|
||||
render action: :new
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize :relay, :update?
|
||||
@relay.destroy
|
||||
redirect_to admin_relays_path
|
||||
end
|
||||
|
||||
def enable
|
||||
authorize :relay, :update?
|
||||
@relay.enable!
|
||||
redirect_to admin_relays_path
|
||||
end
|
||||
|
||||
def disable
|
||||
authorize :relay, :update?
|
||||
@relay.disable!
|
||||
redirect_to admin_relays_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_relay
|
||||
@relay = Relay.find(params[:id])
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:relay).permit(:inbox_url)
|
||||
end
|
||||
|
||||
def require_signatures_enabled!
|
||||
redirect_to admin_relays_path, alert: I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,55 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class ReportNotesController < BaseController
|
||||
before_action :set_report_note, only: [:destroy]
|
||||
|
||||
def create
|
||||
authorize :report_note, :create?
|
||||
|
||||
@report_note = current_account.report_notes.new(resource_params)
|
||||
@report = @report_note.report
|
||||
|
||||
if @report_note.save
|
||||
if params[:create_and_resolve]
|
||||
@report.resolve!(current_account)
|
||||
log_action :resolve, @report
|
||||
|
||||
redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg')
|
||||
return
|
||||
end
|
||||
|
||||
if params[:create_and_unresolve]
|
||||
@report.unresolve!
|
||||
log_action :reopen, @report
|
||||
end
|
||||
|
||||
redirect_to admin_report_path(@report), notice: I18n.t('admin.report_notes.created_msg')
|
||||
else
|
||||
@report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
|
||||
@form = Form::StatusBatch.new
|
||||
|
||||
render template: 'admin/reports/show'
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @report_note, :destroy?
|
||||
@report_note.destroy!
|
||||
redirect_to admin_report_path(@report_note.report_id), notice: I18n.t('admin.report_notes.destroyed_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:report_note).permit(
|
||||
:content,
|
||||
:report_id
|
||||
)
|
||||
end
|
||||
|
||||
def set_report_note
|
||||
@report_note = ReportNote.find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,20 +3,31 @@
|
|||
module Admin
|
||||
class ReportedStatusesController < BaseController
|
||||
before_action :set_report
|
||||
before_action :set_status, only: [:update, :destroy]
|
||||
|
||||
def create
|
||||
authorize :status, :update?
|
||||
|
||||
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account))
|
||||
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
|
||||
|
||||
redirect_to admin_report_path(@report)
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @status, :update?
|
||||
@status.update!(status_params)
|
||||
log_action :update, @status
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @status, :destroy?
|
||||
RemovalWorker.perform_async(@status.id)
|
||||
log_action :destroy, @status
|
||||
render json: @status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def status_params
|
||||
|
@ -24,21 +35,15 @@ module Admin
|
|||
end
|
||||
|
||||
def form_status_batch_params
|
||||
params.require(:form_status_batch).permit(status_ids: [])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:nsfw_on]
|
||||
'nsfw_on'
|
||||
elsif params[:nsfw_off]
|
||||
'nsfw_off'
|
||||
elsif params[:delete]
|
||||
'delete'
|
||||
end
|
||||
params.require(:form_status_batch).permit(:action, status_ids: [])
|
||||
end
|
||||
|
||||
def set_report
|
||||
@report = Report.find(params[:report_id])
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = @report.statuses.find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,48 +11,66 @@ module Admin
|
|||
|
||||
def show
|
||||
authorize @report, :show?
|
||||
|
||||
@report_note = @report.notes.new
|
||||
@report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
|
||||
@form = Form::StatusBatch.new
|
||||
@form = Form::StatusBatch.new
|
||||
end
|
||||
|
||||
def assign_to_self
|
||||
def update
|
||||
authorize @report, :update?
|
||||
@report.update!(assigned_account_id: current_account.id)
|
||||
log_action :assigned_to_self, @report
|
||||
process_report
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
def unassign
|
||||
authorize @report, :update?
|
||||
@report.update!(assigned_account_id: nil)
|
||||
log_action :unassigned, @report
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
def reopen
|
||||
authorize @report, :update?
|
||||
@report.unresolve!
|
||||
log_action :reopen, @report
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
def resolve
|
||||
authorize @report, :update?
|
||||
@report.resolve!(current_account)
|
||||
log_action :resolve, @report
|
||||
redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_report
|
||||
case params[:outcome].to_s
|
||||
when 'resolve'
|
||||
@report.update!(action_taken_by_current_attributes)
|
||||
log_action :resolve, @report
|
||||
when 'suspend'
|
||||
Admin::SuspensionWorker.perform_async(@report.target_account.id)
|
||||
log_action :resolve, @report
|
||||
log_action :suspend, @report.target_account
|
||||
resolve_all_target_account_reports
|
||||
when 'silence'
|
||||
@report.target_account.update!(silenced: true)
|
||||
log_action :resolve, @report
|
||||
log_action :silence, @report.target_account
|
||||
resolve_all_target_account_reports
|
||||
else
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
|
||||
def action_taken_by_current_attributes
|
||||
{ action_taken: true, action_taken_by_account_id: current_account.id }
|
||||
end
|
||||
|
||||
def resolve_all_target_account_reports
|
||||
unresolved_reports_for_target_account.update_all(
|
||||
action_taken_by_current_attributes
|
||||
)
|
||||
end
|
||||
|
||||
def unresolved_reports_for_target_account
|
||||
Report.where(
|
||||
target_account: @report.target_account
|
||||
).unresolved
|
||||
end
|
||||
|
||||
def filtered_reports
|
||||
ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account)
|
||||
ReportFilter.new(filter_params).results.order(id: :desc).includes(
|
||||
:account,
|
||||
:target_account
|
||||
)
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(*ReportFilter::KEYS).permit(*ReportFilter::KEYS)
|
||||
params.permit(
|
||||
:account_id,
|
||||
:resolved,
|
||||
:target_account_id
|
||||
)
|
||||
end
|
||||
|
||||
def set_report
|
||||
|
|
|
@ -10,5 +10,11 @@ module Admin
|
|||
log_action :reset_password, @user
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,5 +17,11 @@ module Admin
|
|||
log_action :demote, @user
|
||||
redirect_to admin_account_path(@user.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,29 +2,76 @@
|
|||
|
||||
module Admin
|
||||
class SettingsController < BaseController
|
||||
ADMIN_SETTINGS = %w(
|
||||
site_contact_username
|
||||
site_contact_email
|
||||
site_title
|
||||
site_description
|
||||
site_extended_description
|
||||
site_terms
|
||||
open_registrations
|
||||
closed_registrations_message
|
||||
open_deletion
|
||||
timeline_preview
|
||||
show_staff_badge
|
||||
bootstrap_timeline_accounts
|
||||
thumbnail
|
||||
hero
|
||||
min_invite_role
|
||||
activity_api_enabled
|
||||
peers_api_enabled
|
||||
show_known_fediverse_at_about_page
|
||||
).freeze
|
||||
|
||||
BOOLEAN_SETTINGS = %w(
|
||||
open_registrations
|
||||
open_deletion
|
||||
timeline_preview
|
||||
show_staff_badge
|
||||
activity_api_enabled
|
||||
peers_api_enabled
|
||||
show_known_fediverse_at_about_page
|
||||
).freeze
|
||||
|
||||
UPLOAD_SETTINGS = %w(
|
||||
thumbnail
|
||||
hero
|
||||
).freeze
|
||||
|
||||
def edit
|
||||
authorize :settings, :show?
|
||||
|
||||
@admin_settings = Form::AdminSettings.new
|
||||
end
|
||||
|
||||
def update
|
||||
authorize :settings, :update?
|
||||
|
||||
@admin_settings = Form::AdminSettings.new(settings_params)
|
||||
|
||||
if @admin_settings.save
|
||||
flash[:notice] = I18n.t('generic.changes_saved_msg')
|
||||
redirect_to edit_admin_settings_path
|
||||
else
|
||||
render :edit
|
||||
settings_params.each do |key, value|
|
||||
if UPLOAD_SETTINGS.include?(key)
|
||||
upload = SiteUpload.where(var: key).first_or_initialize(var: key)
|
||||
upload.update(file: value)
|
||||
else
|
||||
setting = Setting.where(var: key).first_or_initialize(var: key)
|
||||
setting.update(value: value_for_update(key, value))
|
||||
end
|
||||
end
|
||||
|
||||
flash[:notice] = I18n.t('generic.changes_saved_msg')
|
||||
redirect_to edit_admin_settings_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def settings_params
|
||||
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
|
||||
params.require(:form_admin_settings).permit(ADMIN_SETTINGS)
|
||||
end
|
||||
|
||||
def value_for_update(key, value)
|
||||
if BOOLEAN_SETTINGS.include?(key)
|
||||
value == '1'
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
27
app/controllers/admin/silences_controller.rb
Normal file
27
app/controllers/admin/silences_controller.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class SilencesController < BaseController
|
||||
before_action :set_account
|
||||
|
||||
def create
|
||||
authorize @account, :silence?
|
||||
@account.update!(silenced: true)
|
||||
log_action :silence, @account
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @account, :unsilence?
|
||||
@account.update!(silenced: false)
|
||||
log_action :unsilence, @account
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class SiteUploadsController < BaseController
|
||||
before_action :set_site_upload
|
||||
|
||||
def destroy
|
||||
authorize :settings, :destroy?
|
||||
|
||||
@site_upload.destroy!
|
||||
|
||||
redirect_to edit_admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_site_upload
|
||||
@site_upload = SiteUpload.find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,16 +5,17 @@ module Admin
|
|||
helper_method :current_params
|
||||
|
||||
before_action :set_account
|
||||
before_action :set_status, only: [:update, :destroy]
|
||||
|
||||
PER_PAGE = 20
|
||||
|
||||
def index
|
||||
authorize :status, :index?
|
||||
|
||||
@statuses = @account.statuses.where(visibility: [:public, :unlisted])
|
||||
@statuses = @account.statuses
|
||||
|
||||
if params[:media]
|
||||
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).group(:status_id)
|
||||
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
||||
@statuses.merge!(Status.where(id: account_media_status_ids))
|
||||
end
|
||||
|
||||
|
@ -22,34 +23,43 @@ module Admin
|
|||
@form = Form::StatusBatch.new
|
||||
end
|
||||
|
||||
def show
|
||||
authorize :status, :index?
|
||||
|
||||
@statuses = @account.statuses.where(id: params[:id])
|
||||
authorize @statuses.first, :show?
|
||||
|
||||
@form = Form::StatusBatch.new
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :status, :update?
|
||||
|
||||
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account))
|
||||
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
|
||||
|
||||
redirect_to admin_account_statuses_path(@account.id, current_params)
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @status, :update?
|
||||
@status.update!(status_params)
|
||||
log_action :update, @status
|
||||
redirect_to admin_account_statuses_path(@account.id, current_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @status, :destroy?
|
||||
RemovalWorker.perform_async(@status.id)
|
||||
log_action :destroy, @status
|
||||
render json: @status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def status_params
|
||||
params.require(:status).permit(:sensitive)
|
||||
end
|
||||
|
||||
def form_status_batch_params
|
||||
params.require(:form_status_batch).permit(:action, status_ids: [])
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:id])
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
@ -62,15 +72,5 @@ module Admin
|
|||
page: page > 1 && page,
|
||||
}.select { |_, value| value.present? }
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:nsfw_on]
|
||||
'nsfw_on'
|
||||
elsif params[:nsfw_off]
|
||||
'nsfw_off'
|
||||
elsif params[:delete]
|
||||
'delete'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
27
app/controllers/admin/suspensions_controller.rb
Normal file
27
app/controllers/admin/suspensions_controller.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class SuspensionsController < BaseController
|
||||
before_action :set_account
|
||||
|
||||
def create
|
||||
authorize @account, :suspend?
|
||||
Admin::SuspensionWorker.perform_async(@account.id)
|
||||
log_action :suspend, @account
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @account, :unsuspend?
|
||||
@account.unsuspend!
|
||||
log_action :unsuspend, @account
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,103 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class TagsController < BaseController
|
||||
before_action :set_tag, except: [:index, :batch, :approve_all, :reject_all]
|
||||
before_action :set_usage_by_domain, except: [:index, :batch, :approve_all, :reject_all]
|
||||
before_action :set_counters, except: [:index, :batch, :approve_all, :reject_all]
|
||||
|
||||
def index
|
||||
authorize :tag, :index?
|
||||
|
||||
@tags = filtered_tags.page(params[:page])
|
||||
@form = Form::TagBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
@form = Form::TagBatch.new(form_tag_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
ensure
|
||||
redirect_to admin_tags_path(filter_params)
|
||||
end
|
||||
|
||||
def approve_all
|
||||
Form::TagBatch.new(current_account: current_account, tag_ids: Tag.pending_review.pluck(:id), action: 'approve').save
|
||||
redirect_to admin_tags_path(filter_params)
|
||||
end
|
||||
|
||||
def reject_all
|
||||
Form::TagBatch.new(current_account: current_account, tag_ids: Tag.pending_review.pluck(:id), action: 'reject').save
|
||||
redirect_to admin_tags_path(filter_params)
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @tag, :show?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @tag, :update?
|
||||
|
||||
if @tag.update(tag_params.merge(reviewed_at: Time.now.utc))
|
||||
redirect_to admin_tag_path(@tag.id), notice: I18n.t('admin.tags.updated_msg')
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_tag
|
||||
@tag = Tag.find(params[:id])
|
||||
end
|
||||
|
||||
def set_usage_by_domain
|
||||
@usage_by_domain = @tag.statuses
|
||||
.with_public_visibility
|
||||
.excluding_silenced_accounts
|
||||
.where(Status.arel_table[:id].gteq(Mastodon::Snowflake.id_at(Time.now.utc.beginning_of_day)))
|
||||
.joins(:account)
|
||||
.group('accounts.domain')
|
||||
.reorder('statuses_count desc')
|
||||
.pluck('accounts.domain, count(*) AS statuses_count')
|
||||
end
|
||||
|
||||
def set_counters
|
||||
@accounts_today = @tag.history.first[:accounts]
|
||||
@accounts_week = Redis.current.pfcount(*current_week_days.map { |day| "activity:tags:#{@tag.id}:#{day}:accounts" })
|
||||
end
|
||||
|
||||
def filtered_tags
|
||||
TagFilter.new(filter_params).results
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(:page, *TagFilter::KEYS).permit(:page, *TagFilter::KEYS)
|
||||
end
|
||||
|
||||
def tag_params
|
||||
params.require(:tag).permit(:name, :trendable, :usable, :listable)
|
||||
end
|
||||
|
||||
def current_week_days
|
||||
now = Time.now.utc.beginning_of_day.to_date
|
||||
|
||||
(Date.commercial(now.cwyear, now.cweek)..now).map do |date|
|
||||
date.to_time(:utc).beginning_of_day.to_i
|
||||
end
|
||||
end
|
||||
|
||||
def form_tag_batch_params
|
||||
params.require(:form_tag_batch).permit(:action, tag_ids: [])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:approve]
|
||||
'approve'
|
||||
elsif params[:reject]
|
||||
'reject'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,19 +2,18 @@
|
|||
|
||||
module Admin
|
||||
class TwoFactorAuthenticationsController < BaseController
|
||||
before_action :set_target_user
|
||||
before_action :set_user
|
||||
|
||||
def destroy
|
||||
authorize @user, :disable_2fa?
|
||||
@user.disable_two_factor!
|
||||
log_action :disable_2fa, @user
|
||||
UserMailer.two_factor_disabled(@user).deliver_later!
|
||||
redirect_to admin_accounts_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_target_user
|
||||
def set_user
|
||||
@user = User.find(params[:user_id])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class WarningPresetsController < BaseController
|
||||
before_action :set_warning_preset, except: [:index, :create]
|
||||
|
||||
def index
|
||||
authorize :account_warning_preset, :index?
|
||||
|
||||
@warning_presets = AccountWarningPreset.alphabetic
|
||||
@warning_preset = AccountWarningPreset.new
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :account_warning_preset, :create?
|
||||
|
||||
@warning_preset = AccountWarningPreset.new(warning_preset_params)
|
||||
|
||||
if @warning_preset.save
|
||||
redirect_to admin_warning_presets_path
|
||||
else
|
||||
@warning_presets = AccountWarningPreset.alphabetic
|
||||
render :index
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @warning_preset, :update?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @warning_preset, :update?
|
||||
|
||||
if @warning_preset.update(warning_preset_params)
|
||||
redirect_to admin_warning_presets_path
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @warning_preset, :destroy?
|
||||
|
||||
@warning_preset.destroy!
|
||||
redirect_to admin_warning_presets_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_warning_preset
|
||||
@warning_preset = AccountWarningPreset.find(params[:id])
|
||||
end
|
||||
|
||||
def warning_preset_params
|
||||
params.require(:account_warning_preset).permit(:title, :text)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,23 +7,12 @@ class Api::BaseController < ApplicationController
|
|||
include RateLimitHeaders
|
||||
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
||||
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
|
||||
before_action :set_cache_headers
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
skip_around_action :set_locale
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotUnique do
|
||||
render json: { error: 'Duplicate record' }, status: 422
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render json: { error: 'Record not found' }, status: 404
|
||||
end
|
||||
|
@ -40,18 +29,6 @@ class Api::BaseController < ApplicationController
|
|||
render json: { error: 'This action is not allowed' }, status: 403
|
||||
end
|
||||
|
||||
rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight do
|
||||
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
|
||||
end
|
||||
|
||||
rescue_from Mastodon::RateLimitExceededError do
|
||||
render json: { error: I18n.t('errors.429') }, status: 429
|
||||
end
|
||||
|
||||
rescue_from ActionController::ParameterMissing do |e|
|
||||
render json: { error: e.to_s }, status: 400
|
||||
end
|
||||
|
||||
def doorkeeper_unauthorized_render_options(error: nil)
|
||||
{ json: { error: (error.try(:description) || 'Not authorized') } }
|
||||
end
|
||||
|
@ -71,12 +48,11 @@ class Api::BaseController < ApplicationController
|
|||
|
||||
def limit_param(default_limit)
|
||||
return default_limit unless params[:limit]
|
||||
|
||||
[params[:limit].to_i.abs, default_limit * 2].min
|
||||
end
|
||||
|
||||
def params_slice(*keys)
|
||||
params.slice(*keys).permit(*keys)
|
||||
def truthy_param?(key)
|
||||
ActiveModel::Type::Boolean.new.cast(params[key])
|
||||
end
|
||||
|
||||
def current_resource_owner
|
||||
|
@ -89,37 +65,15 @@ class Api::BaseController < ApplicationController
|
|||
nil
|
||||
end
|
||||
|
||||
def require_authenticated_user!
|
||||
render json: { error: 'This method requires an authenticated user' }, status: 401 unless current_user
|
||||
end
|
||||
|
||||
def require_user!
|
||||
if !current_user
|
||||
render json: { error: 'This method requires an authenticated user' }, status: 422
|
||||
elsif !current_user.confirmed?
|
||||
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
|
||||
elsif !current_user.approved?
|
||||
render json: { error: 'Your login is currently pending approval' }, status: 403
|
||||
elsif !current_user.functional?
|
||||
render json: { error: 'Your login is currently disabled' }, status: 403
|
||||
if current_user
|
||||
set_user_activity
|
||||
else
|
||||
update_user_sign_in
|
||||
render json: { error: 'This method requires an authenticated user' }, status: 422
|
||||
end
|
||||
end
|
||||
|
||||
def render_empty
|
||||
render json: {}, status: 200
|
||||
end
|
||||
|
||||
def authorize_if_got_token!(*scopes)
|
||||
doorkeeper_authorize!(*scopes) if doorkeeper_token
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
end
|
||||
|
||||
def disallow_unauthenticated_api_access?
|
||||
authorized_fetch_mode?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,25 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::OEmbedController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!
|
||||
|
||||
before_action :set_status
|
||||
before_action :require_public_status!
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@status = status_finder.status
|
||||
render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = status_finder.status
|
||||
end
|
||||
|
||||
def require_public_status!
|
||||
not_found if @status.hidden?
|
||||
end
|
||||
|
||||
def status_finder
|
||||
StatusFinder.new(params[:url])
|
||||
end
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::ProofsController < Api::BaseController
|
||||
include AccountOwnedConcern
|
||||
|
||||
skip_before_action :require_authenticated_user!
|
||||
|
||||
before_action :set_provider
|
||||
|
||||
def index
|
||||
render json: @account, serializer: @provider.serializer_class
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_provider
|
||||
@provider = ProofProvider.find(params[:provider]) || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
def username_param
|
||||
params[:username]
|
||||
end
|
||||
end
|
73
app/controllers/api/push_controller.rb
Normal file
73
app/controllers/api/push_controller.rb
Normal file
|
@ -0,0 +1,73 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::PushController < Api::BaseController
|
||||
include SignatureVerification
|
||||
|
||||
def update
|
||||
response, status = process_push_request
|
||||
render plain: response, status: status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_push_request
|
||||
case hub_mode
|
||||
when 'subscribe'
|
||||
Pubsubhubbub::SubscribeService.new.call(account_from_topic, hub_callback, hub_secret, hub_lease_seconds, verified_domain)
|
||||
when 'unsubscribe'
|
||||
Pubsubhubbub::UnsubscribeService.new.call(account_from_topic, hub_callback)
|
||||
else
|
||||
["Unknown mode: #{hub_mode}", 422]
|
||||
end
|
||||
end
|
||||
|
||||
def hub_mode
|
||||
params['hub.mode']
|
||||
end
|
||||
|
||||
def hub_topic
|
||||
params['hub.topic']
|
||||
end
|
||||
|
||||
def hub_callback
|
||||
params['hub.callback']
|
||||
end
|
||||
|
||||
def hub_lease_seconds
|
||||
params['hub.lease_seconds']
|
||||
end
|
||||
|
||||
def hub_secret
|
||||
params['hub.secret']
|
||||
end
|
||||
|
||||
def account_from_topic
|
||||
if hub_topic.present? && local_domain? && account_feed_path?
|
||||
Account.find_local(hub_topic_params[:username])
|
||||
end
|
||||
end
|
||||
|
||||
def hub_topic_params
|
||||
@_hub_topic_params ||= Rails.application.routes.recognize_path(hub_topic_uri.path)
|
||||
end
|
||||
|
||||
def hub_topic_uri
|
||||
@_hub_topic_uri ||= Addressable::URI.parse(hub_topic).normalize
|
||||
end
|
||||
|
||||
def local_domain?
|
||||
TagManager.instance.web_domain?(hub_topic_domain)
|
||||
end
|
||||
|
||||
def verified_domain
|
||||
return signed_request_account.domain if signed_request_account
|
||||
end
|
||||
|
||||
def hub_topic_domain
|
||||
hub_topic_uri.host + (hub_topic_uri.port ? ":#{hub_topic_uri.port}" : '')
|
||||
end
|
||||
|
||||
def account_feed_path?
|
||||
hub_topic_params[:controller] == 'accounts' && hub_topic_params[:action] == 'show' && hub_topic_params[:format] == 'atom'
|
||||
end
|
||||
end
|
37
app/controllers/api/salmon_controller.rb
Normal file
37
app/controllers/api/salmon_controller.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::SalmonController < Api::BaseController
|
||||
include SignatureVerification
|
||||
|
||||
before_action :set_account
|
||||
respond_to :txt
|
||||
|
||||
def update
|
||||
if verify_payload?
|
||||
process_salmon
|
||||
head 202
|
||||
elsif payload.present?
|
||||
render plain: signature_verification_failure_reason, status: 401
|
||||
else
|
||||
head 400
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
|
||||
def 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
|
51
app/controllers/api/subscriptions_controller.rb
Normal file
51
app/controllers/api/subscriptions_controller.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::SubscriptionsController < Api::BaseController
|
||||
before_action :set_account
|
||||
respond_to :txt
|
||||
|
||||
def show
|
||||
if subscription.valid?(params['hub.topic'])
|
||||
@account.update(subscription_expires_at: future_expires)
|
||||
render plain: encoded_challenge, status: 200
|
||||
else
|
||||
head 404
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if subscription.verify(body, request.headers['HTTP_X_HUB_SIGNATURE'])
|
||||
ProcessingWorker.perform_async(@account.id, body.force_encoding('UTF-8'))
|
||||
end
|
||||
|
||||
head 200
|
||||
end
|
||||
|
||||
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'] || 1.day).to_i.seconds
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
end
|
|
@ -1,8 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::CredentialsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, except: [:update]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:update]
|
||||
before_action -> { doorkeeper_authorize! :read }, except: [:update]
|
||||
before_action -> { doorkeeper_authorize! :write }, only: [:update]
|
||||
before_action :require_user!
|
||||
|
||||
def show
|
||||
|
@ -13,7 +13,6 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
|||
def update
|
||||
@account = current_account
|
||||
UpdateAccountService.new.call(@account, account_params, raise_error: true)
|
||||
UserSettingsDecorator.new(current_user).update(user_settings_params) if user_settings_params
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
||||
end
|
||||
|
@ -21,18 +20,6 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
|||
private
|
||||
|
||||
def account_params
|
||||
params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
|
||||
end
|
||||
|
||||
def user_settings_params
|
||||
return nil if params[:source].blank?
|
||||
|
||||
source_params = params.require(:source)
|
||||
|
||||
{
|
||||
'setting_default_privacy' => source_params.fetch(:privacy, @account.user.setting_default_privacy),
|
||||
'setting_default_sensitive' => source_params.fetch(:sensitive, @account.user.setting_default_sensitive),
|
||||
'setting_default_language' => source_params.fetch(:language, @account.user.setting_default_language),
|
||||
}
|
||||
params.permit(:display_name, :note, :avatar, :header, :locked)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::FeaturedTagsController < Api::BaseController
|
||||
before_action :set_account
|
||||
before_action :set_featured_tags
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
render json: @featured_tags, each_serializer: REST::FeaturedTagSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def set_featured_tags
|
||||
@featured_tags = @account.suspended? ? [] : @account.featured_tags
|
||||
end
|
||||
end
|
|
@ -1,10 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :set_account
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@accounts = load_accounts
|
||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||
|
@ -17,19 +19,11 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
|||
end
|
||||
|
||||
def load_accounts
|
||||
return [] if hide_results?
|
||||
|
||||
scope = default_accounts
|
||||
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
|
||||
scope.merge(paginated_follows).to_a
|
||||
end
|
||||
|
||||
def hide_results?
|
||||
@account.suspended? || (@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
|
||||
default_accounts.merge(paginated_follows).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
Account.includes(:active_relationships, :account_stat).references(:active_relationships)
|
||||
Account.includes(:active_relationships).references(:active_relationships)
|
||||
end
|
||||
|
||||
def paginated_follows
|
||||
|
@ -69,6 +63,6 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
|||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :set_account
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@accounts = load_accounts
|
||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||
|
@ -17,19 +19,11 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
|||
end
|
||||
|
||||
def load_accounts
|
||||
return [] if hide_results?
|
||||
|
||||
scope = default_accounts
|
||||
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
|
||||
scope.merge(paginated_follows).to_a
|
||||
end
|
||||
|
||||
def hide_results?
|
||||
@account.suspended? || (@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
|
||||
default_accounts.merge(paginated_follows).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
Account.includes(:passive_relationships, :account_stat).references(:passive_relationships)
|
||||
Account.includes(:passive_relationships).references(:passive_relationships)
|
||||
end
|
||||
|
||||
def paginated_follows
|
||||
|
@ -69,6 +63,6 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
|||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::IdentityProofsController < Api::BaseController
|
||||
before_action :require_user!
|
||||
before_action :set_account
|
||||
|
||||
def index
|
||||
@proofs = @account.suspended? ? [] : @account.identity_proofs.active
|
||||
render json: @proofs, each_serializer: REST::IdentityProofSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
end
|
|
@ -1,12 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::ListsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:lists' }
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :require_user!
|
||||
before_action :set_account
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@lists = @account.suspended? ? [] : @account.lists.where(account: current_account)
|
||||
@lists = @account.lists.where(account: current_account)
|
||||
render json: @lists, each_serializer: REST::ListSerializer
|
||||
end
|
||||
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::NotesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
|
||||
before_action :require_user!
|
||||
before_action :set_account
|
||||
|
||||
def create
|
||||
if params[:comment].blank?
|
||||
AccountNote.find_by(account: current_account, target_account: @account)&.destroy
|
||||
else
|
||||
@note = AccountNote.find_or_initialize_by(account: current_account, target_account: @account)
|
||||
@note.comment = params[:comment]
|
||||
@note.save! if @note.changed?
|
||||
end
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def relationships_presenter
|
||||
AccountRelationshipsPresenter.new([@account.id], current_user.account_id)
|
||||
end
|
||||
end
|
|
@ -1,30 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::PinsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
|
||||
before_action :require_user!
|
||||
before_action :set_account
|
||||
|
||||
def create
|
||||
AccountPin.create!(account: current_account, target_account: @account)
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
|
||||
end
|
||||
|
||||
def destroy
|
||||
pin = AccountPin.find_by(account: current_account, target_account: @account)
|
||||
pin&.destroy!
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def relationships_presenter
|
||||
AccountRelationshipsPresenter.new([@account.id], current_user.account_id)
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue