Merge upstream tag 'v3.5.2'
This commit is contained in:
commit
d161ca885c
2205 changed files with 91260 additions and 41616 deletions
|
@ -1,187 +1,155 @@
|
||||||
version: 2
|
version: 2.1
|
||||||
|
|
||||||
aliases:
|
orbs:
|
||||||
- &defaults
|
ruby: circleci/ruby@1.4.1
|
||||||
|
node: circleci/node@5.0.1
|
||||||
|
|
||||||
|
executors:
|
||||||
|
default:
|
||||||
|
parameters:
|
||||||
|
ruby-version:
|
||||||
|
type: string
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/ruby:2.7-buster-node
|
- image: cimg/ruby:<< parameters.ruby-version >>
|
||||||
environment: &ruby_environment
|
environment:
|
||||||
BUNDLE_JOBS: 3
|
BUNDLE_JOBS: 3
|
||||||
BUNDLE_RETRY: 3
|
BUNDLE_RETRY: 3
|
||||||
BUNDLE_APP_CONFIG: ./.bundle/
|
CONTINUOUS_INTEGRATION: true
|
||||||
BUNDLE_PATH: ./vendor/bundle/
|
|
||||||
DB_HOST: localhost
|
DB_HOST: localhost
|
||||||
DB_USER: root
|
DB_USER: root
|
||||||
RAILS_ENV: test
|
|
||||||
ALLOW_NOPAM: true
|
|
||||||
CONTINUOUS_INTEGRATION: true
|
|
||||||
DISABLE_SIMPLECOV: true
|
DISABLE_SIMPLECOV: true
|
||||||
PAM_ENABLED: true
|
RAILS_ENV: test
|
||||||
PAM_DEFAULT_SERVICE: pam_test
|
- image: cimg/postgres:14.0
|
||||||
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:
|
environment:
|
||||||
POSTGRES_USER: root
|
POSTGRES_USER: root
|
||||||
POSTGRES_HOST_AUTH_METHOD: trust
|
POSTGRES_HOST_AUTH_METHOD: trust
|
||||||
- image: circleci/redis:5-alpine
|
- image: cimg/redis:6.2
|
||||||
|
|
||||||
|
commands:
|
||||||
|
install-system-dependencies:
|
||||||
steps:
|
steps:
|
||||||
- *attach_workspace
|
|
||||||
- *install_system_dependencies
|
|
||||||
- run:
|
- run:
|
||||||
name: Create database
|
name: Install system dependencies
|
||||||
|
command: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libicu-dev libidn11-dev
|
||||||
|
install-ruby-dependencies:
|
||||||
|
parameters:
|
||||||
|
ruby-version:
|
||||||
|
type: string
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
command: |
|
||||||
|
bundle config clean 'true'
|
||||||
|
bundle config frozen 'true'
|
||||||
|
bundle config without 'development production'
|
||||||
|
name: Set bundler settings
|
||||||
|
- ruby/install-deps:
|
||||||
|
bundler-version: '2.3.8'
|
||||||
|
key: ruby<< parameters.ruby-version >>-gems-v1
|
||||||
|
wait-db:
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
command: dockerize -wait tcp://localhost:5432 -wait tcp://localhost:6379 -timeout 1m
|
||||||
|
name: Wait for PostgreSQL and Redis
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
docker:
|
||||||
|
- image: cimg/ruby:3.0-node
|
||||||
|
environment:
|
||||||
|
RAILS_ENV: test
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- install-system-dependencies
|
||||||
|
- install-ruby-dependencies:
|
||||||
|
ruby-version: '3.0'
|
||||||
|
- node/install-packages:
|
||||||
|
cache-version: v1
|
||||||
|
pkg-manager: yarn
|
||||||
|
- run:
|
||||||
|
command: ./bin/rails assets:precompile
|
||||||
|
name: Precompile assets
|
||||||
|
- persist_to_workspace:
|
||||||
|
paths:
|
||||||
|
- public/assets
|
||||||
|
- public/packs-test
|
||||||
|
root: .
|
||||||
|
|
||||||
|
test:
|
||||||
|
parameters:
|
||||||
|
ruby-version:
|
||||||
|
type: string
|
||||||
|
executor:
|
||||||
|
name: default
|
||||||
|
ruby-version: << parameters.ruby-version >>
|
||||||
|
environment:
|
||||||
|
ALLOW_NOPAM: true
|
||||||
|
PAM_ENABLED: true
|
||||||
|
PAM_DEFAULT_SERVICE: pam_test
|
||||||
|
PAM_CONTROLLED_SERVICE: pam_test_controlled
|
||||||
|
parallelism: 4
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- install-system-dependencies
|
||||||
|
- run:
|
||||||
|
command: sudo apt-get install -y ffmpeg imagemagick libpam-dev
|
||||||
|
name: Install additional system dependencies
|
||||||
|
- run:
|
||||||
|
command: bundle config with 'pam_authentication'
|
||||||
|
name: Enable PAM authentication
|
||||||
|
- install-ruby-dependencies:
|
||||||
|
ruby-version: << parameters.ruby-version >>
|
||||||
|
- attach_workspace:
|
||||||
|
at: .
|
||||||
|
- wait-db
|
||||||
|
- run:
|
||||||
|
command: ./bin/rails db:create db:schema:load db:seed
|
||||||
|
name: Load database schema
|
||||||
|
- ruby/rspec-test
|
||||||
|
|
||||||
|
test-migrations:
|
||||||
|
executor:
|
||||||
|
name: default
|
||||||
|
ruby-version: '3.0'
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- install-system-dependencies
|
||||||
|
- install-ruby-dependencies:
|
||||||
|
ruby-version: '3.0'
|
||||||
|
- wait-db
|
||||||
|
- run:
|
||||||
command: ./bin/rails db:create
|
command: ./bin/rails db:create
|
||||||
|
name: Create database
|
||||||
- run:
|
- run:
|
||||||
command: ./bin/rails db:migrate VERSION=20171010025614
|
command: ./bin/rails db:migrate VERSION=20171010025614
|
||||||
name: Run migrations up to v2.0.0
|
name: Run migrations up to v2.0.0
|
||||||
- run:
|
- run:
|
||||||
command: ./bin/rails tests:migrations:populate_v2
|
command: ./bin/rails tests:migrations:populate_v2
|
||||||
name: Populate database with test data
|
name: Populate database with test data
|
||||||
|
- run:
|
||||||
|
command: ./bin/rails db:migrate VERSION=20180514140000
|
||||||
|
name: Run migrations up to v2.4.0
|
||||||
|
- run:
|
||||||
|
command: ./bin/rails tests:migrations:populate_v2_4
|
||||||
|
name: Populate database with test data
|
||||||
- run:
|
- run:
|
||||||
command: ./bin/rails db:migrate
|
command: ./bin/rails db:migrate
|
||||||
name: Run all remaining migrations
|
name: Run all remaining migrations
|
||||||
|
- run:
|
||||||
|
command: ./bin/rails tests:migrations:check_database
|
||||||
|
name: Check migration result
|
||||||
|
|
||||||
test-two-step-migrations:
|
test-two-step-migrations:
|
||||||
<<: *defaults
|
executor:
|
||||||
docker:
|
name: default
|
||||||
- image: circleci/ruby:2.7-buster-node
|
ruby-version: '3.0'
|
||||||
environment: *ruby_environment
|
|
||||||
- image: circleci/postgres:12.2
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: root
|
|
||||||
POSTGRES_HOST_AUTH_METHOD: trust
|
|
||||||
- image: circleci/redis:5-alpine
|
|
||||||
steps:
|
steps:
|
||||||
- *attach_workspace
|
- checkout
|
||||||
- *install_system_dependencies
|
- install-system-dependencies
|
||||||
|
- install-ruby-dependencies:
|
||||||
|
ruby-version: '3.0'
|
||||||
|
- wait-db
|
||||||
- run:
|
- run:
|
||||||
command: ./bin/rails db:create
|
command: ./bin/rails db:create
|
||||||
name: Create database
|
name: Create database
|
||||||
|
@ -191,99 +159,51 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
command: ./bin/rails tests:migrations:populate_v2
|
command: ./bin/rails tests:migrations:populate_v2
|
||||||
name: Populate database with test data
|
name: Populate database with test data
|
||||||
|
- run:
|
||||||
|
command: ./bin/rails db:migrate VERSION=20180514140000
|
||||||
|
name: Run pre-deployment migrations up to v2.4.0
|
||||||
|
environment:
|
||||||
|
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
|
||||||
|
- run:
|
||||||
|
command: ./bin/rails tests:migrations:populate_v2_4
|
||||||
|
name: Populate database with test data
|
||||||
- run:
|
- run:
|
||||||
command: ./bin/rails db:migrate
|
command: ./bin/rails db:migrate
|
||||||
name: Run all pre-deployment migrations
|
name: Run all pre-deployment migrations
|
||||||
evironment:
|
environment:
|
||||||
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
|
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
|
||||||
- run:
|
- run:
|
||||||
command: ./bin/rails db:migrate
|
command: ./bin/rails db:migrate
|
||||||
name: Run all post-deployment remaining migrations
|
name: Run all post-deployment remaining migrations
|
||||||
|
|
||||||
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:
|
- run:
|
||||||
name: Run jest
|
command: ./bin/rails tests:migrations:check_database
|
||||||
command: yarn test:jest
|
name: Check migration result
|
||||||
|
|
||||||
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:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
build-and-test:
|
build-and-test:
|
||||||
jobs:
|
jobs:
|
||||||
- install
|
- build
|
||||||
- install-ruby2.7:
|
- test:
|
||||||
|
matrix:
|
||||||
|
parameters:
|
||||||
|
ruby-version:
|
||||||
|
- '2.7'
|
||||||
|
- '3.0'
|
||||||
|
name: test-ruby<< matrix.ruby-version >>
|
||||||
requires:
|
requires:
|
||||||
- install
|
- build
|
||||||
- install-ruby2.6:
|
|
||||||
requires:
|
|
||||||
- install
|
|
||||||
- install-ruby2.7
|
|
||||||
- build:
|
|
||||||
requires:
|
|
||||||
- install-ruby2.7
|
|
||||||
- test-migrations:
|
- test-migrations:
|
||||||
requires:
|
requires:
|
||||||
- install-ruby2.7
|
- build
|
||||||
- test-two-step-migrations:
|
- test-two-step-migrations:
|
||||||
requires:
|
requires:
|
||||||
- install-ruby2.7
|
|
||||||
- test-ruby2.7:
|
|
||||||
requires:
|
|
||||||
- install-ruby2.7
|
|
||||||
- build
|
- build
|
||||||
- test-ruby2.6:
|
- node/run:
|
||||||
|
cache-version: v1
|
||||||
|
name: test-webui
|
||||||
|
pkg-manager: yarn
|
||||||
requires:
|
requires:
|
||||||
- install-ruby2.6
|
|
||||||
- build
|
- build
|
||||||
- test-webui:
|
version: lts
|
||||||
requires:
|
yarn-run: test:jest
|
||||||
- install
|
|
||||||
- check-i18n:
|
|
||||||
requires:
|
|
||||||
- install-ruby2.7
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
version: "2"
|
version: '2'
|
||||||
checks:
|
checks:
|
||||||
argument-count:
|
argument-count:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
@ -30,9 +30,12 @@ plugins:
|
||||||
channel: eslint-7
|
channel: eslint-7
|
||||||
rubocop:
|
rubocop:
|
||||||
enabled: true
|
enabled: true
|
||||||
channel: rubocop-0-92
|
channel: rubocop-1-9-1
|
||||||
sass-lint:
|
sass-lint:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude_patterns:
|
exclude_patterns:
|
||||||
- spec/
|
- spec/
|
||||||
- vendor/asset
|
- vendor/asset/
|
||||||
|
|
||||||
|
- app/javascript/mastodon/locales/**/*.json
|
||||||
|
- config/locales/**/*.yml
|
||||||
|
|
23
.deepsource.toml
Normal file
23
.deepsource.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
test_patterns = ["app/javascript/mastodon/**/__tests__/**"]
|
||||||
|
|
||||||
|
exclude_patterns = [
|
||||||
|
"db/migrate/**",
|
||||||
|
"db/post_migrate/**"
|
||||||
|
]
|
||||||
|
|
||||||
|
[[analyzers]]
|
||||||
|
name = "ruby"
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[[analyzers]]
|
||||||
|
name = "javascript"
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[analyzers.meta]
|
||||||
|
environment = [
|
||||||
|
"browser",
|
||||||
|
"jest",
|
||||||
|
"nodejs"
|
||||||
|
]
|
24
.devcontainer/Dockerfile
Normal file
24
.devcontainer/Dockerfile
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# [Choice] Ruby version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.1, 3.0, 2, 2.7, 2.6, 3-bullseye, 3.1-bullseye, 3.0-bullseye, 2-bullseye, 2.7-bullseye, 2.6-bullseye, 3-buster, 3.1-buster, 3.0-buster, 2-buster, 2.7-buster, 2.6-buster
|
||||||
|
ARG VARIANT=3.1-bullseye
|
||||||
|
FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT}
|
||||||
|
|
||||||
|
# Install Rails
|
||||||
|
# RUN gem install rails webdrivers
|
||||||
|
|
||||||
|
# Default value to allow debug server to serve content over GitHub Codespace's port forwarding service
|
||||||
|
# The value is a comma-separated list of allowed domains
|
||||||
|
ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev"
|
||||||
|
|
||||||
|
# [Choice] Node.js version: lts/*, 16, 14, 12, 10
|
||||||
|
ARG NODE_VERSION="lts/*"
|
||||||
|
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
|
||||||
|
|
||||||
|
# [Optional] Uncomment this section to install additional OS packages.
|
||||||
|
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||||
|
&& apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg imagemagick libpam-dev
|
||||||
|
|
||||||
|
# [Optional] Uncomment this line to install additional gems.
|
||||||
|
RUN gem install foreman
|
||||||
|
|
||||||
|
# [Optional] Uncomment this line to install global node packages.
|
||||||
|
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1
|
26
.devcontainer/devcontainer.json
Normal file
26
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "Mastodon",
|
||||||
|
"dockerComposeFile": "docker-compose.yml",
|
||||||
|
"service": "app",
|
||||||
|
"workspaceFolder": "/workspaces/mastodon",
|
||||||
|
|
||||||
|
// Set *default* container specific settings.json values on container create.
|
||||||
|
"settings": {},
|
||||||
|
|
||||||
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
|
"extensions": [
|
||||||
|
"EditorConfig.EditorConfig",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"rebornix.Ruby"
|
||||||
|
],
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// This can be used to network with other containers or the host.
|
||||||
|
"forwardPorts": [3000, 4000],
|
||||||
|
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
"postCreateCommand": "bundle install --path vendor/bundle && yarn install && ./bin/rails db:setup",
|
||||||
|
|
||||||
|
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||||
|
"remoteUser": "vscode"
|
||||||
|
}
|
83
.devcontainer/docker-compose.yml
Normal file
83
.devcontainer/docker-compose.yml
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
# Update 'VARIANT' to pick a version of Ruby: 3, 3.1, 3.0, 2, 2.7, 2.6
|
||||||
|
# Append -bullseye or -buster to pin to an OS version.
|
||||||
|
# Use -bullseye variants on local arm64/Apple Silicon.
|
||||||
|
VARIANT: '3.0-bullseye'
|
||||||
|
# Optional Node.js version to install
|
||||||
|
NODE_VERSION: '14'
|
||||||
|
volumes:
|
||||||
|
- ..:/workspaces/mastodon:cached
|
||||||
|
environment:
|
||||||
|
RAILS_ENV: development
|
||||||
|
NODE_ENV: development
|
||||||
|
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: '6379'
|
||||||
|
DB_HOST: db
|
||||||
|
DB_USER: postgres
|
||||||
|
DB_PASS: postgres
|
||||||
|
DB_PORT: '5432'
|
||||||
|
ES_ENABLED: 'true'
|
||||||
|
ES_HOST: es
|
||||||
|
ES_PORT: '9200'
|
||||||
|
# Overrides default command so things don't shut down after the process ends.
|
||||||
|
command: sleep infinity
|
||||||
|
networks:
|
||||||
|
- external_network
|
||||||
|
- internal_network
|
||||||
|
user: vscode
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:14-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_DB: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_HOST_AUTH_METHOD: trust
|
||||||
|
networks:
|
||||||
|
- internal_network
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:6-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
networks:
|
||||||
|
- internal_network
|
||||||
|
|
||||||
|
es:
|
||||||
|
image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
ES_JAVA_OPTS: -Xms512m -Xmx512m
|
||||||
|
cluster.name: es-mastodon
|
||||||
|
discovery.type: single-node
|
||||||
|
bootstrap.memory_lock: 'true'
|
||||||
|
volumes:
|
||||||
|
- es-data:/usr/share/elasticsearch/data
|
||||||
|
networks:
|
||||||
|
- internal_network
|
||||||
|
ulimits:
|
||||||
|
memlock:
|
||||||
|
soft: -1
|
||||||
|
hard: -1
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
redis-data:
|
||||||
|
es-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
external_network:
|
||||||
|
internal_network:
|
||||||
|
internal: true
|
|
@ -1,6 +1,10 @@
|
||||||
.bundle
|
.bundle
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
.git
|
||||||
|
.gitattributes
|
||||||
|
.gitignore
|
||||||
|
.github
|
||||||
public/system
|
public/system
|
||||||
public/assets
|
public/assets
|
||||||
public/packs
|
public/packs
|
||||||
|
@ -11,5 +15,7 @@ vendor/bundle
|
||||||
*.swp
|
*.swp
|
||||||
*~
|
*~
|
||||||
postgres
|
postgres
|
||||||
|
postgres14
|
||||||
redis
|
redis
|
||||||
elasticsearch
|
elasticsearch
|
||||||
|
chart
|
||||||
|
|
|
@ -13,7 +13,7 @@ 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
|
# Optional Elasticsearch configuration
|
||||||
ES_ENABLED=true
|
ES_ENABLED=true
|
||||||
ES_HOST=$DATA_ELASTIC_HOST
|
ES_HOST=$DATA_ELASTIC_HOST
|
||||||
ES_PORT=9200
|
ES_PORT=9200
|
||||||
|
@ -202,10 +202,6 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||||
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
|
# 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
|
# 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) :
|
# Optional CAS authentication (cf. omniauth-cas) :
|
||||||
# CAS_ENABLED=true
|
# CAS_ENABLED=true
|
||||||
# CAS_URL=https://sso.myserver.com/
|
# CAS_URL=https://sso.myserver.com/
|
||||||
|
@ -228,6 +224,7 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||||
# CAS_LOCATION_KEY='location'
|
# CAS_LOCATION_KEY='location'
|
||||||
# CAS_IMAGE_KEY='image'
|
# CAS_IMAGE_KEY='image'
|
||||||
# CAS_PHONE_KEY='phone'
|
# CAS_PHONE_KEY='phone'
|
||||||
|
# CAS_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
|
||||||
|
|
||||||
# Optional SAML authentication (cf. omniauth-saml)
|
# Optional SAML authentication (cf. omniauth-saml)
|
||||||
# SAML_ENABLED=true
|
# SAML_ENABLED=true
|
||||||
|
|
|
@ -29,11 +29,14 @@ DB_NAME=mastodon_production
|
||||||
DB_PASS=
|
DB_PASS=
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
|
|
||||||
# ElasticSearch (optional)
|
# Elasticsearch (optional)
|
||||||
# ------------------------
|
# ------------------------
|
||||||
ES_ENABLED=true
|
ES_ENABLED=true
|
||||||
ES_HOST=localhost
|
ES_HOST=localhost
|
||||||
ES_PORT=9200
|
ES_PORT=9200
|
||||||
|
# Authentication for ES (optional)
|
||||||
|
ES_USER=elastic
|
||||||
|
ES_PASS=password
|
||||||
|
|
||||||
# Secrets
|
# Secrets
|
||||||
# -------
|
# -------
|
||||||
|
@ -55,7 +58,7 @@ SMTP_SERVER=smtp.mailgun.org
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_LOGIN=
|
SMTP_LOGIN=
|
||||||
SMTP_PASSWORD=
|
SMTP_PASSWORD=
|
||||||
SMTP_FROM_ADDRESS=notificatons@example.com
|
SMTP_FROM_ADDRESS=notifications@example.com
|
||||||
|
|
||||||
# File storage (optional)
|
# File storage (optional)
|
||||||
# -----------------------
|
# -----------------------
|
||||||
|
|
|
@ -79,6 +79,11 @@ module.exports = {
|
||||||
'no-irregular-whitespace': 'error',
|
'no-irregular-whitespace': 'error',
|
||||||
'no-mixed-spaces-and-tabs': 'warn',
|
'no-mixed-spaces-and-tabs': 'warn',
|
||||||
'no-nested-ternary': 'warn',
|
'no-nested-ternary': 'warn',
|
||||||
|
'no-restricted-properties': [
|
||||||
|
'error',
|
||||||
|
{ property: 'substring', message: 'Use .slice instead of .substring.' },
|
||||||
|
{ property: 'substr', message: 'Use .slice instead of .substr.' },
|
||||||
|
],
|
||||||
'no-trailing-spaces': 'warn',
|
'no-trailing-spaces': 'warn',
|
||||||
'no-undef': 'error',
|
'no-undef': 'error',
|
||||||
'no-unreachable': 'error',
|
'no-unreachable': 'error',
|
||||||
|
|
32
.github/CODEOWNERS
vendored
32
.github/CODEOWNERS
vendored
|
@ -1,32 +0,0 @@
|
||||||
# CODEOWNERS for tootsuite/mastodon
|
|
||||||
|
|
||||||
# Translators
|
|
||||||
# To add translator, copy these lines, replace `fr` with appropriate language code and replace `@żelipapą` with user's GitHub nickname preceded by `@` sign or e-mail address.
|
|
||||||
# /app/javascript/mastodon/locales/fr.json @żelipapą
|
|
||||||
# /app/views/user_mailer/*.fr.html.erb @żelipapą
|
|
||||||
# /app/views/user_mailer/*.fr.text.erb @żelipapą
|
|
||||||
# /config/locales/*.fr.yml @żelipapą
|
|
||||||
# /config/locales/fr.yml @żelipapą
|
|
||||||
|
|
||||||
# Polish
|
|
||||||
/app/javascript/mastodon/locales/pl.json @m4sk1n
|
|
||||||
/app/views/user_mailer/*.pl.html.erb @m4sk1n
|
|
||||||
/app/views/user_mailer/*.pl.text.erb @m4sk1n
|
|
||||||
/config/locales/*.pl.yml @m4sk1n
|
|
||||||
/config/locales/pl.yml @m4sk1n
|
|
||||||
|
|
||||||
# French
|
|
||||||
/app/javascript/mastodon/locales/fr.json @aldarone
|
|
||||||
/app/javascript/mastodon/locales/whitelist_fr.json @aldarone
|
|
||||||
/app/views/user_mailer/*.fr.html.erb @aldarone
|
|
||||||
/app/views/user_mailer/*.fr.text.erb @aldarone
|
|
||||||
/config/locales/*.fr.yml @aldarone
|
|
||||||
/config/locales/fr.yml @aldarone
|
|
||||||
|
|
||||||
# Dutch
|
|
||||||
/app/javascript/mastodon/locales/nl.json @jeroenpraat
|
|
||||||
/app/javascript/mastodon/locales/whitelist_nl.json @jeroenpraat
|
|
||||||
/app/views/user_mailer/*.nl.html.erb @jeroenpraat
|
|
||||||
/app/views/user_mailer/*.nl.text.erb @jeroenpraat
|
|
||||||
/config/locales/*.nl.yml @jeroenpraat
|
|
||||||
/config/locales/nl.yml @jeroenpraat
|
|
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1,3 +1,3 @@
|
||||||
patreon: mastodon
|
patreon: mastodon
|
||||||
open_collective: mastodon
|
open_collective: mastodon
|
||||||
github: [Gargron]
|
custom: https://sponsor.joinmastodon.org
|
||||||
|
|
42
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
Normal file
42
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
name: Bug Report
|
||||||
|
description: If something isn't working as expected
|
||||||
|
labels: bug
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
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.
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce the problem
|
||||||
|
description: What were you trying to do?
|
||||||
|
value: |
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Expected behaviour
|
||||||
|
description: What should have happened?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Actual behaviour
|
||||||
|
description: What happened?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Specifications
|
||||||
|
description: |
|
||||||
|
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?
|
||||||
|
validations:
|
||||||
|
required: true
|
22
.github/ISSUE_TEMPLATE/2.feature_request.yml
vendored
Normal file
22
.github/ISSUE_TEMPLATE/2.feature_request.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
name: Feature Request
|
||||||
|
description: I have a suggestion
|
||||||
|
labels: suggestion
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
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?
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Pitch
|
||||||
|
description: Describe your idea for a feature. Make sure it has not already been suggested/implemented/turned down before.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Motivation
|
||||||
|
description: Why do you think this feature is needed? Who would benefit from it?
|
||||||
|
validations:
|
||||||
|
required: true
|
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? -->
|
|
7
.github/ISSUE_TEMPLATE/config.yml
vendored
7
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,5 +1,8 @@
|
||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Mastodon Meta Discussion Board
|
- name: GitHub Discussions
|
||||||
url: https://discourse.joinmastodon.org/
|
url: https://github.com/mastodon/mastodon/discussions
|
||||||
about: Please ask and answer questions here.
|
about: Please ask and answer questions here.
|
||||||
|
- name: Bug Bounty Program
|
||||||
|
url: https://app.intigriti.com/programs/mastodon/mastodonio/detail
|
||||||
|
about: Please report security vulnerabilities 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
|
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
|
@ -6,7 +6,7 @@
|
||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/"
|
directory: '/'
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
open-pull-requests-limit: 99
|
open-pull-requests-limit: 99
|
||||||
|
@ -14,7 +14,7 @@ updates:
|
||||||
- dependency-type: direct
|
- dependency-type: direct
|
||||||
|
|
||||||
- package-ecosystem: bundler
|
- package-ecosystem: bundler
|
||||||
directory: "/"
|
directory: '/'
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
open-pull-requests-limit: 99
|
open-pull-requests-limit: 99
|
||||||
|
|
18
.github/workflows/build-image.yml
vendored
18
.github/workflows/build-image.yml
vendored
|
@ -3,32 +3,40 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "main"
|
- 'main'
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- '*'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- .github/workflows/build-image.yml
|
||||||
|
- Dockerfile
|
||||||
jobs:
|
jobs:
|
||||||
build-image:
|
build-image:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- uses: docker/setup-qemu-action@v1
|
||||||
- uses: docker/setup-buildx-action@v1
|
- uses: docker/setup-buildx-action@v1
|
||||||
- uses: docker/login-action@v1
|
- uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
- uses: docker/metadata-action@v3
|
- uses: docker/metadata-action@v3
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
images: tootsuite/mastodon
|
images: tootsuite/mastodon
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=false
|
latest=auto
|
||||||
tags: |
|
tags: |
|
||||||
type=edge,branch=main
|
type=edge,branch=main
|
||||||
type=semver,pattern={{ raw }}
|
type=match,pattern=v(.*),group=0
|
||||||
|
type=ref,event=pr
|
||||||
- uses: docker/build-push-action@v2
|
- uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
cache-from: type=registry,ref=tootsuite/mastodon:latest
|
cache-from: type=registry,ref=tootsuite/mastodon:latest
|
||||||
cache-to: type=inline
|
cache-to: type=inline
|
||||||
|
|
34
.github/workflows/check-i18n.yml
vendored
Normal file
34
.github/workflows/check-i18n.yml
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
name: Check i18n
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
env:
|
||||||
|
RAILS_ENV: test
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-i18n:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install system dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libicu-dev libidn11-dev
|
||||||
|
- name: Set up Ruby
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: '3.0'
|
||||||
|
bundler-cache: true
|
||||||
|
- name: Check locale file normalization
|
||||||
|
run: bundle exec i18n-tasks check-normalized
|
||||||
|
- name: Check for unused strings
|
||||||
|
run: bundle exec i18n-tasks unused -l en
|
||||||
|
- name: Check for wrong string interpolations
|
||||||
|
run: bundle exec i18n-tasks check-consistent-interpolations
|
||||||
|
- name: Check that all required locale files exist
|
||||||
|
run: bundle exec rake repo:check_locales_files
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -40,13 +40,12 @@
|
||||||
|
|
||||||
# Ignore postgres + redis + elasticsearch volume optionally created by docker-compose
|
# Ignore postgres + redis + elasticsearch volume optionally created by docker-compose
|
||||||
/postgres
|
/postgres
|
||||||
|
/postgres14
|
||||||
/redis
|
/redis
|
||||||
/elasticsearch
|
/elasticsearch
|
||||||
|
|
||||||
# ignore Helm lockfile, dependency charts, and local values file
|
# ignore Helm dependency charts
|
||||||
/chart/Chart.lock
|
|
||||||
/chart/charts/*.tgz
|
/chart/charts/*.tgz
|
||||||
/chart/values.yaml
|
|
||||||
|
|
||||||
# Ignore Apple files
|
# Ignore Apple files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
||||||
12
|
14
|
||||||
|
|
78
.prettierignore
Normal file
78
.prettierignore
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
# 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 and downloaded libraries.
|
||||||
|
/.bundle
|
||||||
|
/vendor/bundle
|
||||||
|
|
||||||
|
# Ignore the default SQLite database.
|
||||||
|
/db/*.sqlite3
|
||||||
|
/db/*.sqlite3-journal
|
||||||
|
|
||||||
|
# Ignore all logfiles and tempfiles.
|
||||||
|
.eslintcache
|
||||||
|
/log/*
|
||||||
|
!/log/.keep
|
||||||
|
/tmp
|
||||||
|
/coverage
|
||||||
|
/public/system
|
||||||
|
/public/assets
|
||||||
|
/public/packs
|
||||||
|
/public/packs-test
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
.env.development
|
||||||
|
/node_modules/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Ignore Vagrant files
|
||||||
|
.vagrant/
|
||||||
|
|
||||||
|
# Ignore Capistrano customizations
|
||||||
|
/config/deploy/*
|
||||||
|
|
||||||
|
# Ignore IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Ignore postgres + redis + elasticsearch volume optionally created by docker-compose
|
||||||
|
/postgres
|
||||||
|
/postgres14
|
||||||
|
/redis
|
||||||
|
/elasticsearch
|
||||||
|
|
||||||
|
# ignore Helm dependency charts
|
||||||
|
/chart/charts/*.tgz
|
||||||
|
|
||||||
|
# Ignore Apple files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Ignore vim files
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Ignore npm debug log
|
||||||
|
npm-debug.log
|
||||||
|
|
||||||
|
# Ignore yarn log files
|
||||||
|
yarn-error.log
|
||||||
|
yarn-debug.log
|
||||||
|
|
||||||
|
# Ignore vagrant log files
|
||||||
|
*-cloudimg-console.log
|
||||||
|
|
||||||
|
# Ignore Docker option files
|
||||||
|
docker-compose.override.yml
|
||||||
|
|
||||||
|
# Ignore Helm files
|
||||||
|
/chart
|
||||||
|
|
||||||
|
# Ignore emoji map file
|
||||||
|
/app/javascript/mastodon/features/emoji/emoji_map.json
|
||||||
|
|
||||||
|
# Ignore locale files
|
||||||
|
/app/javascript/mastodon/locales
|
||||||
|
/config/locales
|
3
.prettierrc.js
Normal file
3
.prettierrc.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
singleQuote: true
|
||||||
|
}
|
33
.rubocop.yml
33
.rubocop.yml
|
@ -2,19 +2,20 @@ require:
|
||||||
- rubocop-rails
|
- rubocop-rails
|
||||||
|
|
||||||
AllCops:
|
AllCops:
|
||||||
TargetRubyVersion: 2.4
|
TargetRubyVersion: 2.5
|
||||||
|
NewCops: disable
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/**/*'
|
- 'spec/**/*'
|
||||||
- 'db/**/*'
|
- 'db/**/*'
|
||||||
- 'app/views/**/*'
|
- 'app/views/**/*'
|
||||||
- 'config/**/*'
|
- 'config/**/*'
|
||||||
- 'bin/*'
|
- 'bin/*'
|
||||||
- 'Rakefile'
|
- 'Rakefile'
|
||||||
- 'node_modules/**/*'
|
- 'node_modules/**/*'
|
||||||
- 'Vagrantfile'
|
- 'Vagrantfile'
|
||||||
- 'vendor/**/*'
|
- 'vendor/**/*'
|
||||||
- 'lib/json_ld/*'
|
- 'lib/json_ld/*'
|
||||||
- 'lib/templates/**/*'
|
- 'lib/templates/**/*'
|
||||||
|
|
||||||
Bundler/OrderedGems:
|
Bundler/OrderedGems:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
@ -28,13 +29,17 @@ Layout/EmptyLineAfterMagicComment:
|
||||||
Layout/EmptyLineAfterGuardClause:
|
Layout/EmptyLineAfterGuardClause:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
Layout/EmptyLineBetweenDefs:
|
||||||
|
AllowAdjacentOneLineDefs: true
|
||||||
|
|
||||||
Layout/EmptyLinesAroundAttributeAccessor:
|
Layout/EmptyLinesAroundAttributeAccessor:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
||||||
|
Layout/FirstHashElementIndentation:
|
||||||
|
EnforcedStyle: consistent
|
||||||
|
|
||||||
Layout/HashAlignment:
|
Layout/HashAlignment:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
# EnforcedHashRocketStyle: table
|
|
||||||
# EnforcedColonStyle: table
|
|
||||||
|
|
||||||
Layout/SpaceAroundMethodCallOperator:
|
Layout/SpaceAroundMethodCallOperator:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
2.7.2
|
3.0.3
|
||||||
|
|
796
AUTHORS.md
796
AUTHORS.md
File diff suppressed because it is too large
Load diff
4
Aptfile
4
Aptfile
|
@ -4,10 +4,8 @@ libicu-dev
|
||||||
libidn11
|
libidn11
|
||||||
libidn11-dev
|
libidn11-dev
|
||||||
libpq-dev
|
libpq-dev
|
||||||
libprotobuf-dev
|
|
||||||
libxdamage1
|
libxdamage1
|
||||||
libxfixes3
|
libxfixes3
|
||||||
protobuf-compiler
|
|
||||||
zlib1g-dev
|
zlib1g-dev
|
||||||
libcairo2
|
libcairo2
|
||||||
libcroco3
|
libcroco3
|
||||||
|
@ -22,7 +20,7 @@ libpixman-1-0
|
||||||
librsvg2-2
|
librsvg2-2
|
||||||
libthai-data
|
libthai-data
|
||||||
libthai0
|
libthai0
|
||||||
libvpx5
|
libvpx[5-9]
|
||||||
libxcb-render0
|
libxcb-render0
|
||||||
libxcb-shm0
|
libxcb-shm0
|
||||||
libxrender1
|
libxrender1
|
||||||
|
|
2976
CHANGELOG.md
2976
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
@ -14,7 +14,7 @@ If your contributions are accepted into Mastodon, you can request to be paid thr
|
||||||
|
|
||||||
## Bug reports
|
## Bug reports
|
||||||
|
|
||||||
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.
|
Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitHub Issues](https://github.com/mastodon/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
|
## Translations
|
||||||
|
|
||||||
|
@ -24,9 +24,17 @@ You can submit translations via [Crowdin](https://crowdin.com/project/mastodon).
|
||||||
|
|
||||||
## Pull requests
|
## 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.
|
**Please use clean, concise titles for your pull requests.** Unless the pull request is about refactoring code, updating dependencies or other internal tasks, assume that the person reading the pull request title is not a programmer or Mastodon developer, but instead a Mastodon user or server administrator, and **try to describe your change or fix from their perspective**. We use commit squashing, so the final commit in the main branch will carry the title of the pull request, and commits from the main branch are fed into the changelog. The changelog is separated into [keepachangelog.com categories](https://keepachangelog.com/en/1.0.0/), and while that spec does not prescribe how the entries ought to be named, for easier sorting, start your pull request titles using one of the verbs "Add", "Change", "Deprecate", "Remove", or "Fix" (present tense).
|
||||||
|
|
||||||
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.
|
Example:
|
||||||
|
|
||||||
|
|Not ideal|Better|
|
||||||
|
|---|----|
|
||||||
|
|Fixed NoMethodError in RemovalWorker|Fix nil error when removing statuses caused by race condition|
|
||||||
|
|
||||||
|
It is not always possible to phrase every change in such a manner, but it is desired.
|
||||||
|
|
||||||
|
**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:
|
**Pull requests that do not pass automated checks may not be reviewed**. In particular, you need to keep in mind:
|
||||||
|
|
||||||
|
@ -36,4 +44,4 @@ The smaller the set of changes in the pull request is, the quicker it can be rev
|
||||||
|
|
||||||
## Documentation
|
## 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).
|
The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation).
|
||||||
|
|
89
Dockerfile
89
Dockerfile
|
@ -1,10 +1,11 @@
|
||||||
FROM ubuntu:20.04 as build-dep
|
FROM ubuntu:20.04 as build-dep
|
||||||
|
|
||||||
# Use bash for the shell
|
# Use bash for the shell
|
||||||
SHELL ["bash", "-c"]
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||||
|
|
||||||
# Install Node v12 (LTS)
|
# Install Node v16 (LTS)
|
||||||
ENV NODE_VER="12.20.0"
|
ENV NODE_VER="16.14.2"
|
||||||
RUN ARCH= && \
|
RUN ARCH= && \
|
||||||
dpkgArch="$(dpkg --print-architecture)" && \
|
dpkgArch="$(dpkg --print-architecture)" && \
|
||||||
case "${dpkgArch##*-}" in \
|
case "${dpkgArch##*-}" in \
|
||||||
|
@ -17,35 +18,19 @@ RUN ARCH= && \
|
||||||
*) echo "unsupported architecture"; exit 1 ;; \
|
*) echo "unsupported architecture"; exit 1 ;; \
|
||||||
esac && \
|
esac && \
|
||||||
echo "Etc/UTC" > /etc/localtime && \
|
echo "Etc/UTC" > /etc/localtime && \
|
||||||
apt update && \
|
apt-get update && \
|
||||||
apt -y install wget python && \
|
apt-get install -y --no-install-recommends ca-certificates wget python apt-utils && \
|
||||||
cd ~ && \
|
cd ~ && \
|
||||||
wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \
|
wget -q 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 && \
|
tar xf node-v$NODE_VER-linux-$ARCH.tar.gz && \
|
||||||
rm 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
|
mv node-v$NODE_VER-linux-$ARCH /opt/node
|
||||||
|
|
||||||
# Install jemalloc
|
# Install Ruby 3.0
|
||||||
ENV JE_VER="5.2.1"
|
ENV RUBY_VER="3.0.3"
|
||||||
RUN apt update && \
|
RUN apt-get update && \
|
||||||
apt -y install make autoconf gcc g++ && \
|
apt-get install -y --no-install-recommends build-essential \
|
||||||
cd ~ && \
|
bison libyaml-dev libgdbm-dev libreadline-dev libjemalloc-dev \
|
||||||
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 && \
|
libncurses5-dev libffi-dev zlib1g-dev libssl-dev && \
|
||||||
cd ~ && \
|
cd ~ && \
|
||||||
wget https://cache.ruby-lang.org/pub/ruby/${RUBY_VER%.*}/ruby-$RUBY_VER.tar.gz && \
|
wget https://cache.ruby-lang.org/pub/ruby/${RUBY_VER%.*}/ruby-$RUBY_VER.tar.gz && \
|
||||||
|
@ -55,25 +40,26 @@ RUN apt update && \
|
||||||
--with-jemalloc \
|
--with-jemalloc \
|
||||||
--with-shared \
|
--with-shared \
|
||||||
--disable-install-doc && \
|
--disable-install-doc && \
|
||||||
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
|
make -j"$(nproc)" > /dev/null && \
|
||||||
make -j$(nproc) > /dev/null && \
|
|
||||||
make install && \
|
make install && \
|
||||||
cd .. && rm -rf ruby-$RUBY_VER.tar.gz ruby-$RUBY_VER
|
rm -rf ../ruby-$RUBY_VER.tar.gz ../ruby-$RUBY_VER
|
||||||
|
|
||||||
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin"
|
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin"
|
||||||
|
|
||||||
RUN npm install -g yarn && \
|
RUN npm install -g npm@latest && \
|
||||||
|
npm install -g yarn && \
|
||||||
gem install bundler && \
|
gem install bundler && \
|
||||||
apt update && \
|
apt-get update && \
|
||||||
apt -y install git libicu-dev libidn11-dev \
|
apt-get install -y --no-install-recommends git libicu-dev libidn11-dev \
|
||||||
libpq-dev libprotobuf-dev protobuf-compiler
|
libpq-dev shared-mime-info
|
||||||
|
|
||||||
COPY Gemfile* package.json yarn.lock /opt/mastodon/
|
COPY Gemfile* package.json yarn.lock /opt/mastodon/
|
||||||
|
|
||||||
RUN cd /opt/mastodon && \
|
RUN cd /opt/mastodon && \
|
||||||
bundle config set --local deployment 'true' && \
|
bundle config set --local deployment 'true' && \
|
||||||
bundle config set --local without 'development test' && \
|
bundle config set --local without 'development test' && \
|
||||||
bundle install -j$(nproc) && \
|
bundle config set silence_root_warning true && \
|
||||||
|
bundle install -j"$(nproc)" && \
|
||||||
yarn install --pure-lockfile
|
yarn install --pure-lockfile
|
||||||
|
|
||||||
FROM ubuntu:20.04
|
FROM ubuntu:20.04
|
||||||
|
@ -81,7 +67,6 @@ FROM ubuntu:20.04
|
||||||
# Copy over all the langs needed for runtime
|
# Copy over all the langs needed for runtime
|
||||||
COPY --from=build-dep /opt/node /opt/node
|
COPY --from=build-dep /opt/node /opt/node
|
||||||
COPY --from=build-dep /opt/ruby /opt/ruby
|
COPY --from=build-dep /opt/ruby /opt/ruby
|
||||||
COPY --from=build-dep /opt/jemalloc /opt/jemalloc
|
|
||||||
|
|
||||||
# Add more PATHs to the PATH
|
# Add more PATHs to the PATH
|
||||||
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
|
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
|
||||||
|
@ -89,35 +74,27 @@ ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
|
||||||
# Create the mastodon user
|
# Create the mastodon user
|
||||||
ARG UID=991
|
ARG UID=991
|
||||||
ARG GID=991
|
ARG GID=991
|
||||||
RUN apt update && \
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
RUN apt-get update && \
|
||||||
echo "Etc/UTC" > /etc/localtime && \
|
echo "Etc/UTC" > /etc/localtime && \
|
||||||
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
|
apt-get install -y --no-install-recommends whois wget && \
|
||||||
apt install -y whois wget && \
|
|
||||||
addgroup --gid $GID mastodon && \
|
addgroup --gid $GID mastodon && \
|
||||||
useradd -m -u $UID -g $GID -d /opt/mastodon 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
|
echo "mastodon:$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 24 | mkpasswd -s -m sha-256)" | chpasswd && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install mastodon runtime deps
|
# Install mastodon runtime deps
|
||||||
RUN apt -y --no-install-recommends install \
|
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||||
libssl1.1 libpq5 imagemagick ffmpeg \
|
RUN apt-get update && \
|
||||||
libicu66 libprotobuf17 libidn11 libyaml-0-2 \
|
apt-get -y --no-install-recommends install \
|
||||||
file ca-certificates tzdata libreadline8 && \
|
libssl1.1 libpq5 imagemagick ffmpeg libjemalloc2 \
|
||||||
apt -y install gcc && \
|
libicu66 libidn11 libyaml-0-2 \
|
||||||
|
file ca-certificates tzdata libreadline8 gcc tini apt-utils && \
|
||||||
ln -s /opt/mastodon /mastodon && \
|
ln -s /opt/mastodon /mastodon && \
|
||||||
gem install bundler && \
|
gem install bundler && \
|
||||||
rm -rf /var/cache && \
|
rm -rf /var/cache && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Copy over mastodon source, and dependencies from building, and set permissions
|
# Copy over mastodon source, and dependencies from building, and set permissions
|
||||||
COPY --chown=mastodon:mastodon . /opt/mastodon
|
COPY --chown=mastodon:mastodon . /opt/mastodon
|
||||||
COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon
|
COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon
|
||||||
|
@ -140,5 +117,5 @@ RUN cd ~ && \
|
||||||
|
|
||||||
# Set the work dir and the container entry point
|
# Set the work dir and the container entry point
|
||||||
WORKDIR /opt/mastodon
|
WORKDIR /opt/mastodon
|
||||||
ENTRYPOINT ["/tini", "--"]
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
EXPOSE 3000 4000
|
EXPOSE 3000 4000
|
||||||
|
|
30
FEDERATION.md
Normal file
30
FEDERATION.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
## ActivityPub federation in Mastodon
|
||||||
|
|
||||||
|
Mastodon largely follows the ActivityPub server-to-server specification but it makes uses of some non-standard extensions, some of which are required for interacting with Mastodon at all.
|
||||||
|
|
||||||
|
Supported vocabulary: https://docs.joinmastodon.org/spec/activitypub/
|
||||||
|
|
||||||
|
### Required extensions
|
||||||
|
|
||||||
|
#### Webfinger
|
||||||
|
|
||||||
|
In Mastodon, users are identified by a `username` and `domain` pair (e.g., `Gargron@mastodon.social`).
|
||||||
|
This is used both for discovery and for unambiguously mentioning users across the fediverse. Furthermore, this is part of Mastodon's database design from its very beginnings.
|
||||||
|
|
||||||
|
As a result, Mastodon requires that each ActivityPub actor uniquely maps back to an `acct:` URI that can be resolved via WebFinger.
|
||||||
|
|
||||||
|
More information and examples are available at: https://docs.joinmastodon.org/spec/webfinger/
|
||||||
|
|
||||||
|
#### HTTP Signatures
|
||||||
|
|
||||||
|
In order to authenticate activities, Mastodon relies on HTTP Signatures, signing every `POST` and `GET` request to other ActivityPub implementations on behalf of the user authoring an activity (for `POST` requests) or an actor representing the Mastodon server itself (for most `GET` requests).
|
||||||
|
|
||||||
|
Mastodon requires all `POST` requests to be signed, and MAY require `GET` requests to be signed, depending on the configuration of the Mastodon server.
|
||||||
|
|
||||||
|
More information on HTTP Signatures, as well as examples, can be found here: https://docs.joinmastodon.org/spec/security/#http
|
||||||
|
|
||||||
|
### Optional extensions
|
||||||
|
|
||||||
|
- Linked-Data Signatures: https://docs.joinmastodon.org/spec/security/#ld
|
||||||
|
- Bearcaps: https://docs.joinmastodon.org/spec/bearcaps/
|
||||||
|
- Followers collection synchronization: https://git.activitypub.dev/ActivityPubDev/Fediverse-Enhancement-Proposals/src/branch/main/feps/fep-8fcf.md
|
136
Gemfile
136
Gemfile
|
@ -1,113 +1,110 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
ruby '>= 2.5.0', '< 3.0.0'
|
ruby '>= 2.6.0', '< 3.1.0'
|
||||||
|
|
||||||
gem 'pkg-config', '~> 1.4'
|
gem 'pkg-config', '~> 1.4'
|
||||||
|
gem 'rexml', '~> 3.2'
|
||||||
|
|
||||||
gem 'puma', '~> 5.0'
|
gem 'puma', '~> 5.6'
|
||||||
gem 'rails', '~> 5.2.4.4'
|
gem 'rails', '~> 6.1.5'
|
||||||
gem 'sprockets', '~> 3.7.2'
|
gem 'sprockets', '~> 3.7.2'
|
||||||
gem 'thor', '~> 1.0'
|
gem 'thor', '~> 1.2'
|
||||||
gem 'rack', '~> 2.2.3'
|
gem 'rack', '~> 2.2.3'
|
||||||
|
|
||||||
gem 'hamlit-rails', '~> 0.2'
|
gem 'hamlit-rails', '~> 0.2'
|
||||||
gem 'pg', '~> 1.2'
|
gem 'pg', '~> 1.3'
|
||||||
gem 'makara', '~> 0.4'
|
gem 'makara', '~> 0.5'
|
||||||
gem 'pghero', '~> 2.7'
|
gem 'pghero', '~> 2.8'
|
||||||
gem 'dotenv-rails', '~> 2.7'
|
gem 'dotenv-rails', '~> 2.7'
|
||||||
|
|
||||||
gem 'aws-sdk-s3', '~> 1.85', require: false
|
gem 'aws-sdk-s3', '~> 1.113', require: false
|
||||||
gem 'fog-core', '<= 2.1.0'
|
gem 'fog-core', '<= 2.1.0'
|
||||||
gem 'fog-openstack', '~> 0.3', require: false
|
gem 'fog-openstack', '~> 0.3', require: false
|
||||||
gem 'paperclip', '~> 6.0'
|
gem 'kt-paperclip', '~> 7.1'
|
||||||
gem 'blurhash', '~> 0.1'
|
gem 'blurhash', '~> 0.1'
|
||||||
|
|
||||||
gem 'active_model_serializers', '~> 0.10'
|
gem 'active_model_serializers', '~> 0.10'
|
||||||
gem 'addressable', '~> 2.7'
|
gem 'addressable', '~> 2.8'
|
||||||
gem 'bootsnap', '~> 1.5', require: false
|
gem 'bootsnap', '~> 1.11.1', require: false
|
||||||
gem 'browser'
|
gem 'browser'
|
||||||
gem 'charlock_holmes', '~> 0.7.7'
|
gem 'charlock_holmes', '~> 0.7.7'
|
||||||
gem 'iso-639'
|
gem 'chewy', '~> 7.2'
|
||||||
gem 'chewy', '~> 5.1'
|
gem 'devise', '~> 4.8'
|
||||||
gem 'cld3', '~> 3.3.0'
|
gem 'devise-two-factor', '~> 4.0'
|
||||||
gem 'devise', '~> 4.7'
|
|
||||||
gem 'devise-two-factor', '~> 3.1'
|
|
||||||
|
|
||||||
group :pam_authentication, optional: true do
|
group :pam_authentication, optional: true do
|
||||||
gem 'devise_pam_authenticatable2', '~> 9.2'
|
gem 'devise_pam_authenticatable2', '~> 9.2'
|
||||||
end
|
end
|
||||||
|
|
||||||
gem 'net-ldap', '~> 0.16'
|
gem 'net-ldap', '~> 0.17'
|
||||||
gem 'omniauth-cas', '~> 2.0'
|
gem 'omniauth-cas', '~> 2.0'
|
||||||
gem 'omniauth-saml', '~> 1.10'
|
gem 'omniauth-saml', '~> 1.10'
|
||||||
|
gem 'gitlab-omniauth-openid-connect', '~>0.9.1', require: 'omniauth_openid_connect'
|
||||||
gem 'omniauth', '~> 1.9'
|
gem 'omniauth', '~> 1.9'
|
||||||
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
||||||
|
|
||||||
gem 'color_diff', '~> 0.1'
|
gem 'color_diff', '~> 0.1'
|
||||||
gem 'discard', '~> 1.2'
|
gem 'discard', '~> 1.2'
|
||||||
gem 'doorkeeper', '~> 5.4'
|
gem 'doorkeeper', '~> 5.5'
|
||||||
gem 'ed25519', '~> 1.2'
|
gem 'ed25519', '~> 1.3'
|
||||||
gem 'fast_blank', '~> 1.0'
|
gem 'fast_blank', '~> 1.0'
|
||||||
gem 'fastimage'
|
gem 'fastimage'
|
||||||
gem 'hiredis', '~> 0.6'
|
gem 'hiredis', '~> 0.6'
|
||||||
gem 'redis-namespace', '~> 1.8'
|
gem 'redis-namespace', '~> 1.8'
|
||||||
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
|
|
||||||
gem 'htmlentities', '~> 4.3'
|
gem 'htmlentities', '~> 4.3'
|
||||||
gem 'http', '~> 4.4'
|
gem 'http', '~> 5.0'
|
||||||
gem 'http_accept_language', '~> 2.1'
|
gem 'http_accept_language', '~> 2.1'
|
||||||
gem 'httplog', '~> 1.4.3'
|
gem 'httplog', '~> 1.5.0'
|
||||||
gem 'idn-ruby', require: 'idn'
|
gem 'idn-ruby', require: 'idn'
|
||||||
gem 'kaminari', '~> 1.2'
|
gem 'kaminari', '~> 1.2'
|
||||||
gem 'link_header', '~> 0.0'
|
gem 'link_header', '~> 0.0'
|
||||||
gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
|
gem 'mime-types', '~> 3.4.1', require: 'mime/types/columnar'
|
||||||
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
|
gem 'nokogiri', '~> 1.13'
|
||||||
gem 'nokogiri', '~> 1.10'
|
|
||||||
gem 'nsa', '~> 0.2'
|
gem 'nsa', '~> 0.2'
|
||||||
gem 'oj', '~> 3.10'
|
gem 'oj', '~> 3.13'
|
||||||
gem 'ox', '~> 2.13'
|
gem 'ox', '~> 2.14'
|
||||||
gem 'parslet'
|
gem 'parslet'
|
||||||
gem 'parallel', '~> 1.20'
|
|
||||||
gem 'posix-spawn'
|
gem 'posix-spawn'
|
||||||
gem 'pundit', '~> 2.1'
|
gem 'pundit', '~> 2.2'
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
gem 'rack-attack', '~> 6.3'
|
gem 'rack-attack', '~> 6.6'
|
||||||
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
||||||
gem 'rails-i18n', '~> 5.1'
|
gem 'rails-i18n', '~> 6.0'
|
||||||
gem 'rails-settings-cached', '~> 0.6'
|
gem 'rails-settings-cached', '~> 0.6'
|
||||||
gem 'redis', '~> 4.2', require: ['redis', 'redis/connection/hiredis']
|
gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
|
||||||
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
||||||
gem 'rqrcode', '~> 1.1'
|
gem 'rqrcode', '~> 2.1'
|
||||||
gem 'ruby-progressbar', '~> 1.10'
|
gem 'ruby-progressbar', '~> 1.11'
|
||||||
gem 'sanitize', '~> 5.2'
|
gem 'sanitize', '~> 6.0'
|
||||||
gem 'scenic', '~> 1.5'
|
gem 'scenic', '~> 1.6'
|
||||||
gem 'sidekiq', '~> 6.1'
|
gem 'sidekiq', '~> 6.4'
|
||||||
gem 'sidekiq-scheduler', '~> 3.0'
|
gem 'sidekiq-scheduler', '~> 3.2'
|
||||||
gem 'sidekiq-unique-jobs', '~> 6.0'
|
gem 'sidekiq-unique-jobs', '~> 7.1'
|
||||||
gem 'sidekiq-bulk', '~>0.2.0'
|
gem 'sidekiq-bulk', '~>0.2.0'
|
||||||
gem 'simple-navigation', '~> 4.1'
|
gem 'simple-navigation', '~> 4.3'
|
||||||
gem 'simple_form', '~> 5.0'
|
gem 'simple_form', '~> 5.1'
|
||||||
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
|
||||||
gem 'stoplight', '~> 2.2.1'
|
gem 'stoplight', '~> 2.2.1'
|
||||||
gem 'strong_migrations', '~> 0.7'
|
gem 'strong_migrations', '~> 0.7'
|
||||||
gem 'tty-prompt', '~> 0.22', require: false
|
gem 'tty-prompt', '~> 0.23', require: false
|
||||||
gem 'twitter-text', '~> 1.14'
|
gem 'twitter-text', '~> 3.1.0'
|
||||||
gem 'tzinfo-data', '~> 1.2020'
|
gem 'tzinfo-data', '~> 1.2022'
|
||||||
gem 'webpacker', '~> 5.2'
|
gem 'webpacker', '~> 5.4'
|
||||||
gem 'webpush'
|
gem 'webpush', '~> 0.3'
|
||||||
gem 'webauthn', '~> 3.0.0.alpha1'
|
gem 'webauthn', '~> 3.0.0.alpha1'
|
||||||
|
|
||||||
gem 'json-ld'
|
gem 'json-ld'
|
||||||
gem 'json-ld-preloaded', '~> 3.1'
|
gem 'json-ld-preloaded', '~> 3.2'
|
||||||
gem 'rdf-normalize', '~> 0.4'
|
gem 'rdf-normalize', '~> 0.5'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'fabrication', '~> 2.21'
|
gem 'fabrication', '~> 2.28'
|
||||||
gem 'fuubar', '~> 2.5'
|
gem 'fuubar', '~> 2.5'
|
||||||
gem 'i18n-tasks', '~> 0.9', require: false
|
gem 'i18n-tasks', '~> 1.0', require: false
|
||||||
gem 'pry-byebug', '~> 3.9'
|
gem 'pry-byebug', '~> 3.9'
|
||||||
gem 'pry-rails', '~> 0.3'
|
gem 'pry-rails', '~> 0.3'
|
||||||
gem 'rspec-rails', '~> 4.0'
|
gem 'rspec-rails', '~> 5.1'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :production, :test do
|
group :production, :test do
|
||||||
|
@ -115,33 +112,32 @@ group :production, :test do
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 3.33'
|
gem 'capybara', '~> 3.36'
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control', '~> 0.2'
|
||||||
gem 'faker', '~> 2.14'
|
gem 'faker', '~> 2.20'
|
||||||
gem 'microformats', '~> 4.2'
|
gem 'microformats', '~> 4.2'
|
||||||
gem 'rails-controller-testing', '~> 1.0'
|
gem 'rails-controller-testing', '~> 1.0'
|
||||||
gem 'rspec-sidekiq', '~> 3.1'
|
gem 'rspec-sidekiq', '~> 3.1'
|
||||||
gem 'simplecov', '~> 0.19', require: false
|
gem 'simplecov', '~> 0.21', require: false
|
||||||
gem 'webmock', '~> 3.10'
|
gem 'webmock', '~> 3.14'
|
||||||
gem 'parallel_tests', '~> 3.4'
|
gem 'rspec_junit_formatter', '~> 0.5'
|
||||||
gem 'rspec_junit_formatter', '~> 0.4'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem 'active_record_query_trace', '~> 1.8'
|
gem 'active_record_query_trace', '~> 1.8'
|
||||||
gem 'annotate', '~> 3.1'
|
gem 'annotate', '~> 3.2'
|
||||||
gem 'better_errors', '~> 2.9'
|
gem 'better_errors', '~> 2.9'
|
||||||
gem 'binding_of_caller', '~> 0.7'
|
gem 'binding_of_caller', '~> 1.0'
|
||||||
gem 'bullet', '~> 6.1'
|
gem 'bullet', '~> 7.0'
|
||||||
gem 'letter_opener', '~> 1.7'
|
gem 'letter_opener', '~> 1.8'
|
||||||
gem 'letter_opener_web', '~> 1.4'
|
gem 'letter_opener_web', '~> 2.0'
|
||||||
gem 'memory_profiler'
|
gem 'memory_profiler'
|
||||||
gem 'rubocop', '~> 1.3', require: false
|
gem 'rubocop', '~> 1.27', require: false
|
||||||
gem 'rubocop-rails', '~> 2.8', require: false
|
gem 'rubocop-rails', '~> 2.14', require: false
|
||||||
gem 'brakeman', '~> 4.10', require: false
|
gem 'brakeman', '~> 5.2', require: false
|
||||||
gem 'bundler-audit', '~> 0.7', require: false
|
gem 'bundler-audit', '~> 0.9', require: false
|
||||||
|
|
||||||
gem 'capistrano', '~> 3.14'
|
gem 'capistrano', '~> 3.17'
|
||||||
gem 'capistrano-rails', '~> 1.6'
|
gem 'capistrano-rails', '~> 1.6'
|
||||||
gem 'capistrano-rbenv', '~> 2.2'
|
gem 'capistrano-rbenv', '~> 2.2'
|
||||||
gem 'capistrano-yarn', '~> 2.0'
|
gem 'capistrano-yarn', '~> 2.0'
|
||||||
|
@ -150,12 +146,10 @@ group :development do
|
||||||
end
|
end
|
||||||
|
|
||||||
group :production do
|
group :production do
|
||||||
gem 'lograge', '~> 0.11'
|
gem 'lograge', '~> 0.12'
|
||||||
gem 'redis-rails', '~> 5.0'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
gem 'concurrent-ruby', require: false
|
gem 'concurrent-ruby', require: false
|
||||||
gem 'connection_pool', require: false
|
gem 'connection_pool', require: false
|
||||||
|
|
||||||
gem 'xorcist', '~> 1.1'
|
gem 'xorcist', '~> 1.1'
|
||||||
gem 'pluck_each', '~> 0.1.3'
|
|
||||||
|
|
846
Gemfile.lock
846
Gemfile.lock
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
||||||
web: env PORT=3000 bundle exec puma -C config/puma.rb
|
web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb
|
||||||
sidekiq: env PORT=3000 bundle exec sidekiq
|
sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq
|
||||||
stream: env PORT=4000 yarn run start
|
stream: env PORT=4000 yarn run start
|
||||||
webpack: ./bin/webpack-dev-server --listen-host 0.0.0.0
|
webpack: ./bin/webpack-dev-server --listen-host 0.0.0.0
|
||||||
|
|
56
README.md
56
README.md
|
@ -1,15 +1,15 @@
|
||||||
![Mastodon](https://i.imgur.com/NhZc40l.png)
|
![Mastodon](https://i.imgur.com/NhZc40l.png)
|
||||||
========
|
========
|
||||||
|
|
||||||
[![GitHub release](https://img.shields.io/github/release/tootsuite/mastodon.svg)][releases]
|
[![GitHub release](https://img.shields.io/github/release/mastodon/mastodon.svg)][releases]
|
||||||
[![Build Status](https://img.shields.io/circleci/project/github/tootsuite/mastodon.svg)][circleci]
|
[![Build Status](https://img.shields.io/circleci/project/github/mastodon/mastodon.svg)][circleci]
|
||||||
[![Code Climate](https://img.shields.io/codeclimate/maintainability/tootsuite/mastodon.svg)][code_climate]
|
[![Code Climate](https://img.shields.io/codeclimate/maintainability/mastodon/mastodon.svg)][code_climate]
|
||||||
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)][crowdin]
|
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)][crowdin]
|
||||||
[![Docker Pulls](https://img.shields.io/docker/pulls/tootsuite/mastodon.svg)][docker]
|
[![Docker Pulls](https://img.shields.io/docker/pulls/tootsuite/mastodon.svg)][docker]
|
||||||
|
|
||||||
[releases]: https://github.com/tootsuite/mastodon/releases
|
[releases]: https://github.com/mastodon/mastodon/releases
|
||||||
[circleci]: https://circleci.com/gh/tootsuite/mastodon
|
[circleci]: https://circleci.com/gh/mastodon/mastodon
|
||||||
[code_climate]: https://codeclimate.com/github/tootsuite/mastodon
|
[code_climate]: https://codeclimate.com/github/mastodon/mastodon
|
||||||
[crowdin]: https://crowdin.com/project/mastodon
|
[crowdin]: https://crowdin.com/project/mastodon
|
||||||
[docker]: https://hub.docker.com/r/tootsuite/mastodon/
|
[docker]: https://hub.docker.com/r/tootsuite/mastodon/
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ Click below to **learn more** in a video:
|
||||||
- [View sponsors](https://joinmastodon.org/sponsors)
|
- [View sponsors](https://joinmastodon.org/sponsors)
|
||||||
- [Blog](https://blog.joinmastodon.org)
|
- [Blog](https://blog.joinmastodon.org)
|
||||||
- [Documentation](https://docs.joinmastodon.org)
|
- [Documentation](https://docs.joinmastodon.org)
|
||||||
- [Browse Mastodon servers](https://joinmastodon.org/#getting-started)
|
- [Browse Mastodon servers](https://joinmastodon.org/communities)
|
||||||
- [Browse Mastodon apps](https://joinmastodon.org/apps)
|
- [Browse Mastodon apps](https://joinmastodon.org/apps)
|
||||||
|
|
||||||
[patreon]: https://www.patreon.com/mastodon
|
[patreon]: https://www.patreon.com/mastodon
|
||||||
|
@ -37,56 +37,62 @@ Click below to **learn more** in a video:
|
||||||
|
|
||||||
<img src="https://docs.joinmastodon.org/elephant.svg" align="right" width="30%" />
|
<img src="https://docs.joinmastodon.org/elephant.svg" align="right" width="30%" />
|
||||||
|
|
||||||
**No vendor lock-in: Fully interoperable with any conforming platform**
|
### 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 is part of the social network! [Learn more](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/)
|
||||||
|
|
||||||
**Real-time, chronological timeline updates**
|
### Real-time, chronological 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!
|
Updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well!
|
||||||
|
|
||||||
**Media attachments like images and short videos**
|
### 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!
|
Upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos loop continuously!
|
||||||
|
|
||||||
**Safety and moderation tools**
|
### 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/)
|
Mastodon includes 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**
|
### 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 REST and Streaming APIs. This results in a rich app ecosystem with a lot of choices!
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
**Tech stack:**
|
### Tech stack:
|
||||||
|
|
||||||
- **Ruby on Rails** powers the REST API and other web pages
|
- **Ruby on Rails** powers the REST API and other web pages
|
||||||
- **React.js** and Redux are used for the dynamic parts of the interface
|
- **React.js** and Redux are used for the dynamic parts of the interface
|
||||||
- **Node.js** powers the streaming API
|
- **Node.js** powers the streaming API
|
||||||
|
|
||||||
**Requirements:**
|
### Requirements:
|
||||||
|
|
||||||
- **PostgreSQL** 9.5+
|
- **PostgreSQL** 9.5+
|
||||||
- **Redis** 4+
|
- **Redis** 4+
|
||||||
- **Ruby** 2.5+
|
- **Ruby** 2.5+
|
||||||
- **Node.js** 10.13+
|
- **Node.js** 12+
|
||||||
|
|
||||||
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.
|
The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
|
||||||
|
|
||||||
A **Vagrant** configuration is included for development purposes.
|
A **Vagrant** configuration is included for development purposes. To use it, complete following steps:
|
||||||
|
|
||||||
|
- Install Vagrant and Virtualbox
|
||||||
|
- Install the `vagrant-hostsupdater` plugin: `vagrant plugin install vagrant-hostsupdater`
|
||||||
|
- Run `vagrant up`
|
||||||
|
- Run `vagrant ssh -c "cd /vagrant && foreman start"`
|
||||||
|
- Open `http://mastodon.local` in your browser
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Mastodon is **free, open-source software** licensed under **AGPLv3**.
|
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 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).
|
||||||
|
|
||||||
**IRC channel**: #mastodon on irc.freenode.net
|
**IRC channel**: #mastodon on irc.libera.chat
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (C) 2016-2020 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md))
|
Copyright (C) 2016-2022 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](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 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.
|
||||||
|
|
||||||
|
|
18
SECURITY.md
18
SECURITY.md
|
@ -1,12 +1,20 @@
|
||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
|
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you should submit the report through our [Bug Bounty Program][bug-bounty]. Alternatively, you can reach us at <hello@joinmastodon.org>.
|
||||||
|
|
||||||
|
You should *not* report such issues on GitHub or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
A "vulnerability in Mastodon" is a vulnerability in the code distributed through our main source code repository on GitHub. Vulnerabilities that are specific to a given installation (e.g. misconfiguration) should be reported to the owner of that installation and not us.
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| 3.1.x | :white_check_mark: |
|
| 3.5.x | Yes |
|
||||||
| < 3.1 | :x: |
|
| 3.4.x | Yes |
|
||||||
|
| 3.3.x | Yes |
|
||||||
|
| < 3.3 | No |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
[bug-bounty]: https://app.intigriti.com/programs/mastodon/mastodonio/detail
|
||||||
|
|
||||||
hello@joinmastodon.org
|
|
||||||
|
|
18
Vagrantfile
vendored
18
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'
|
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
|
||||||
|
|
||||||
# Add repo for NodeJS
|
# Add repo for NodeJS
|
||||||
curl -sL https://deb.nodesource.com/setup_10.x | sudo bash -
|
curl -sL https://deb.nodesource.com/setup_14.x | sudo bash -
|
||||||
|
|
||||||
# Add firewall rule to redirect 80 to PORT and save
|
# 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"]}
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]}
|
||||||
|
@ -33,11 +33,9 @@ sudo apt-get install \
|
||||||
redis-tools \
|
redis-tools \
|
||||||
postgresql \
|
postgresql \
|
||||||
postgresql-contrib \
|
postgresql-contrib \
|
||||||
protobuf-compiler \
|
|
||||||
yarn \
|
yarn \
|
||||||
libicu-dev \
|
libicu-dev \
|
||||||
libidn11-dev \
|
libidn11-dev \
|
||||||
libprotobuf-dev \
|
|
||||||
libreadline-dev \
|
libreadline-dev \
|
||||||
libpam0g-dev \
|
libpam0g-dev \
|
||||||
-y
|
-y
|
||||||
|
@ -45,16 +43,8 @@ sudo apt-get install \
|
||||||
# Install rvm
|
# Install rvm
|
||||||
read RUBY_VERSION < .ruby-version
|
read RUBY_VERSION < .ruby-version
|
||||||
|
|
||||||
gpg_command="gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB"
|
curl -sSL https://rvm.io/mpapis.asc | gpg --import
|
||||||
$($gpg_command)
|
curl -sSL https://rvm.io/pkuczynski.asc | gpg --import
|
||||||
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
|
|
||||||
|
|
||||||
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
|
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
|
||||||
source /home/vagrant/.rvm/scripts/rvm
|
source /home/vagrant/.rvm/scripts/rvm
|
||||||
|
@ -72,10 +62,12 @@ bundle install
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
# Build Mastodon
|
# Build Mastodon
|
||||||
|
export RAILS_ENV=development
|
||||||
export $(cat ".env.vagrant" | xargs)
|
export $(cat ".env.vagrant" | xargs)
|
||||||
bundle exec rails db:setup
|
bundle exec rails db:setup
|
||||||
|
|
||||||
# Configure automatic loading of environment variable
|
# Configure automatic loading of environment variable
|
||||||
|
echo 'export RAILS_ENV=development' >> ~/.bash_profile
|
||||||
echo 'export $(cat "/vagrant/.env.vagrant" | xargs)' >> ~/.bash_profile
|
echo 'export $(cat "/vagrant/.env.vagrant" | xargs)' >> ~/.bash_profile
|
||||||
|
|
||||||
SCRIPT
|
SCRIPT
|
||||||
|
|
9
app.json
9
app.json
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "Mastodon",
|
"name": "Mastodon",
|
||||||
"description": "A GNU Social-compatible microblogging server",
|
"description": "A GNU Social-compatible microblogging server",
|
||||||
"repository": "https://github.com/tootsuite/mastodon",
|
"repository": "https://github.com/mastodon/mastodon",
|
||||||
"logo": "https://github.com/tootsuite.png",
|
"logo": "https://github.com/mastodon.png",
|
||||||
"env": {
|
"env": {
|
||||||
"HEROKU": {
|
"HEROKU": {
|
||||||
"description": "Leave this as true",
|
"description": "Leave this as true",
|
||||||
|
@ -95,8 +95,5 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postdeploy": "bundle exec rails db:migrate && bundle exec rails db:seed"
|
"postdeploy": "bundle exec rails db:migrate && bundle exec rails db:seed"
|
||||||
},
|
},
|
||||||
"addons": [
|
"addons": ["heroku-postgresql", "heroku-redis"]
|
||||||
"heroku-postgresql",
|
|
||||||
"heroku-redis"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,21 +23,21 @@ class AccountsIndex < Chewy::Index
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
define_type ::Account.searchable.includes(:account_stat), delete_if: ->(account) { account.destroyed? || !account.searchable? } do
|
index_scope ::Account.searchable.includes(:account_stat), delete_if: ->(account) { account.destroyed? || !account.searchable? }
|
||||||
root date_detection: false do
|
|
||||||
field :id, type: 'long'
|
|
||||||
|
|
||||||
field :display_name, type: 'text', analyzer: 'content' do
|
root date_detection: false do
|
||||||
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
|
field :id, type: 'long'
|
||||||
end
|
|
||||||
|
|
||||||
field :acct, type: 'text', analyzer: 'content', value: ->(account) { [account.username, account.domain].compact.join('@') } do
|
field :display_name, type: 'text', analyzer: 'content' do
|
||||||
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
|
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
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class StatusesIndex < Chewy::Index
|
class StatusesIndex < Chewy::Index
|
||||||
|
include FormattingHelper
|
||||||
|
|
||||||
settings index: { refresh_interval: '15m' }, analysis: {
|
settings index: { refresh_interval: '15m' }, analysis: {
|
||||||
filter: {
|
filter: {
|
||||||
english_stop: {
|
english_stop: {
|
||||||
|
@ -31,36 +33,41 @@ class StatusesIndex < Chewy::Index
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
define_type ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preloadable_poll) do
|
index_scope ::Status.unscoped.kept.without_reblogs.includes(:media_attachments, :preloadable_poll)
|
||||||
crutch :mentions do |collection|
|
|
||||||
data = ::Mention.where(status_id: collection.map(&:id)).where(account: Account.local, silent: false).pluck(:status_id, :account_id)
|
crutch :mentions do |collection|
|
||||||
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
data = ::Mention.where(status_id: collection.map(&:id)).where(account: Account.local, silent: false).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.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.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
||||||
|
end
|
||||||
|
|
||||||
|
crutch :votes do |collection|
|
||||||
|
data = ::PollVote.joins(:poll).where(poll: { status_id: collection.map(&:id) }).where(account: Account.local).pluck(:status_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.searchable_text } do
|
||||||
|
field :stemmed, type: 'text', analyzer: 'content'
|
||||||
end
|
end
|
||||||
|
|
||||||
crutch :favourites do |collection|
|
field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }
|
||||||
data = ::Favourite.where(status_id: collection.map(&:id)).where(account: Account.local).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.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 :stemmed, type: 'text', analyzer: 'content'
|
|
||||||
end
|
|
||||||
|
|
||||||
field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,15 +23,15 @@ class TagsIndex < Chewy::Index
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
define_type ::Tag.listable, delete_if: ->(tag) { tag.destroyed? || !tag.listable? } do
|
index_scope ::Tag.listable, delete_if: ->(tag) { tag.destroyed? || !tag.listable? }
|
||||||
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? }
|
root date_detection: false do
|
||||||
field :usage, type: 'long', value: ->(tag) { tag.history.reduce(0) { |total, day| total + day[:accounts].to_i } }
|
field :name, type: 'text', analyzer: 'content' do
|
||||||
field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at }
|
field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? }
|
||||||
|
field :usage, type: 'long', value: ->(tag) { tag.history.reduce(0) { |total, day| total + day.accounts } }
|
||||||
|
field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,6 +20,7 @@ class AboutController < ApplicationController
|
||||||
|
|
||||||
toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description)
|
toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description)
|
||||||
|
|
||||||
|
@rules = Rule.ordered
|
||||||
@contents = toc_generator.html
|
@contents = toc_generator.html
|
||||||
@table_of_contents = toc_generator.toc
|
@table_of_contents = toc_generator.toc
|
||||||
@blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
|
@blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
|
||||||
|
|
|
@ -28,7 +28,7 @@ class AccountsController < ApplicationController
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
|
@pinned_statuses = cached_filtered_status_pins if show_pinned_statuses?
|
||||||
@statuses = cached_filtered_status_page
|
@statuses = cached_filtered_status_page
|
||||||
@rss_url = rss_url
|
@rss_url = rss_url
|
||||||
|
|
||||||
|
@ -64,6 +64,10 @@ class AccountsController < ApplicationController
|
||||||
[replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
|
[replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def filtered_pinned_statuses
|
||||||
|
@account.pinned_statuses.where(visibility: [:public, :unlisted])
|
||||||
|
end
|
||||||
|
|
||||||
def filtered_statuses
|
def filtered_statuses
|
||||||
default_statuses.tap do |statuses|
|
default_statuses.tap do |statuses|
|
||||||
statuses.merge!(hashtag_scope) if tag_requested?
|
statuses.merge!(hashtag_scope) if tag_requested?
|
||||||
|
@ -77,11 +81,7 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def only_media_scope
|
def only_media_scope
|
||||||
Status.where(id: account_media_status_ids)
|
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
|
||||||
end
|
|
||||||
|
|
||||||
def account_media_status_ids
|
|
||||||
@account.media_attachments.attached.reorder(nil).select(:status_id).group(:status_id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def no_replies_scope
|
def no_replies_scope
|
||||||
|
@ -135,15 +135,22 @@ class AccountsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def media_requested?
|
def media_requested?
|
||||||
request.path.split('.').first.ends_with?('/media') && !tag_requested?
|
request.path.split('.').first.end_with?('/media') && !tag_requested?
|
||||||
end
|
end
|
||||||
|
|
||||||
def replies_requested?
|
def replies_requested?
|
||||||
request.path.split('.').first.ends_with?('/with_replies') && !tag_requested?
|
request.path.split('.').first.end_with?('/with_replies') && !tag_requested?
|
||||||
end
|
end
|
||||||
|
|
||||||
def tag_requested?
|
def tag_requested?
|
||||||
request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
|
request.path.split('.').first.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_filtered_status_pins
|
||||||
|
cache_collection(
|
||||||
|
filtered_pinned_statuses,
|
||||||
|
Status
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cached_filtered_status_page
|
def cached_filtered_status_page
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
class ActivityPub::BaseController < Api::BaseController
|
class ActivityPub::BaseController < Api::BaseController
|
||||||
skip_before_action :require_authenticated_user!
|
skip_before_action :require_authenticated_user!
|
||||||
|
skip_around_action :set_locale
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
|
||||||
case params[:id]
|
case params[:id]
|
||||||
when 'featured'
|
when 'featured'
|
||||||
@items = for_signed_account { cache_collection(@account.pinned_statuses, Status) }
|
@items = for_signed_account { cache_collection(@account.pinned_statuses, Status) }
|
||||||
|
@items = @items.map { |item| item.distributable? ? item : ActivityPub::TagManager.instance.uri_for(item) }
|
||||||
when 'tags'
|
when 'tags'
|
||||||
@items = for_signed_account { @account.featured_tags }
|
@items = for_signed_account { @account.featured_tags }
|
||||||
when 'devices'
|
when 'devices'
|
||||||
|
|
|
@ -11,7 +11,11 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||||
before_action :set_cache_headers
|
before_action :set_cache_headers
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?))
|
if page_requested?
|
||||||
|
expires_in(1.minute, public: public_fetch_mode? && signed_request_account.nil?)
|
||||||
|
else
|
||||||
|
expires_in(3.minutes, public: public_fetch_mode?)
|
||||||
|
end
|
||||||
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -20,7 +24,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||||
def outbox_presenter
|
def outbox_presenter
|
||||||
if page_requested?
|
if page_requested?
|
||||||
ActivityPub::CollectionPresenter.new(
|
ActivityPub::CollectionPresenter.new(
|
||||||
id: outbox_url(page_params),
|
id: outbox_url(**page_params),
|
||||||
type: :ordered,
|
type: :ordered,
|
||||||
part_of: outbox_url,
|
part_of: outbox_url,
|
||||||
prev: prev_page,
|
prev: prev_page,
|
||||||
|
@ -58,7 +62,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||||
return unless page_requested?
|
return unless page_requested?
|
||||||
|
|
||||||
@statuses = cache_collection_paginated_by_id(
|
@statuses = cache_collection_paginated_by_id(
|
||||||
@account.statuses.permitted_for(@account, signed_request_account),
|
AccountStatusesFilter.new(@account, signed_request_account).results,
|
||||||
Status,
|
Status,
|
||||||
LIMIT,
|
LIMIT,
|
||||||
params_slice(:max_id, :min_id, :since_id)
|
params_slice(:max_id, :min_id, :since_id)
|
||||||
|
@ -76,4 +80,8 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||||
def set_account
|
def set_account
|
||||||
@account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
|
@account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache_headers
|
||||||
|
response.headers['Vary'] = 'Signature' if authorized_fetch_mode? || page_requested?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -63,15 +63,29 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def next_page
|
def next_page
|
||||||
only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)
|
if only_other_accounts?
|
||||||
|
# Only consider remote accounts
|
||||||
|
return nil if @replies.size < DESCENDANTS_LIMIT
|
||||||
|
|
||||||
account_status_replies_url(
|
account_status_replies_url(
|
||||||
@account,
|
@account,
|
||||||
@status,
|
@status,
|
||||||
page: true,
|
page: true,
|
||||||
min_id: only_other_accounts && !only_other_accounts? ? nil : @replies&.last&.id,
|
min_id: @replies&.last&.id,
|
||||||
only_other_accounts: only_other_accounts
|
only_other_accounts: true
|
||||||
)
|
)
|
||||||
|
else
|
||||||
|
# For now, we're serving only self-replies, but next page might be other accounts
|
||||||
|
next_only_other_accounts = @replies&.last&.account_id != @account.id || @replies.size < DESCENDANTS_LIMIT
|
||||||
|
|
||||||
|
account_status_replies_url(
|
||||||
|
@account,
|
||||||
|
@status,
|
||||||
|
page: true,
|
||||||
|
min_id: next_only_other_accounts ? nil : @replies&.last&.id,
|
||||||
|
only_other_accounts: next_only_other_accounts
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def page_params
|
def page_params
|
||||||
|
|
|
@ -14,7 +14,7 @@ module Admin
|
||||||
else
|
else
|
||||||
@account = @account_moderation_note.target_account
|
@account = @account_moderation_note.target_account
|
||||||
@moderation_notes = @account.targeted_moderation_notes.latest
|
@moderation_notes = @account.targeted_moderation_notes.latest
|
||||||
@warnings = @account.targeted_account_warnings.latest.custom
|
@warnings = @account.strikes.custom.latest
|
||||||
|
|
||||||
render template: 'admin/accounts/show'
|
render template: 'admin/accounts/show'
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,13 +2,24 @@
|
||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class AccountsController < BaseController
|
class AccountsController < BaseController
|
||||||
before_action :set_account, except: [:index]
|
before_action :set_account, except: [:index, :batch]
|
||||||
before_action :require_remote_account!, only: [:redownload]
|
before_action :require_remote_account!, only: [:redownload]
|
||||||
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
|
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :account, :index?
|
authorize :account, :index?
|
||||||
|
|
||||||
@accounts = filtered_accounts.page(params[:page])
|
@accounts = filtered_accounts.page(params[:page])
|
||||||
|
@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_accounts_path(filter_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@ -17,7 +28,7 @@ module Admin
|
||||||
@deletion_request = @account.deletion_request
|
@deletion_request = @account.deletion_request
|
||||||
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
|
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
|
||||||
@moderation_notes = @account.targeted_moderation_notes.latest
|
@moderation_notes = @account.targeted_moderation_notes.latest
|
||||||
@warnings = @account.targeted_account_warnings.latest.custom
|
@warnings = @account.strikes.includes(:target_account, :account, :appeal).latest
|
||||||
@domain_block = DomainBlock.rule_for(@account.domain)
|
@domain_block = DomainBlock.rule_for(@account.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -38,13 +49,13 @@ module Admin
|
||||||
def approve
|
def approve
|
||||||
authorize @account.user, :approve?
|
authorize @account.user, :approve?
|
||||||
@account.user.approve!
|
@account.user.approve!
|
||||||
redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
|
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject
|
def reject
|
||||||
authorize @account.user, :reject?
|
authorize @account.user, :reject?
|
||||||
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
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)
|
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
@ -106,6 +117,16 @@ module Admin
|
||||||
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), notice: I18n.t('admin.accounts.removed_header_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unblock_email
|
||||||
|
authorize @account, :unblock_email?
|
||||||
|
|
||||||
|
CanonicalEmailBlock.where(reference_account: @account).delete_all
|
||||||
|
|
||||||
|
log_action :unblock_email, @account
|
||||||
|
|
||||||
|
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unblocked_email_msg', username: @account.acct)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
|
@ -121,11 +142,25 @@ module Admin
|
||||||
end
|
end
|
||||||
|
|
||||||
def filtered_accounts
|
def filtered_accounts
|
||||||
AccountFilter.new(filter_params).results
|
AccountFilter.new(filter_params.with_defaults(order: 'recent')).results
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_params
|
def filter_params
|
||||||
params.slice(*AccountFilter::KEYS).permit(*AccountFilter::KEYS)
|
params.slice(:page, *AccountFilter::KEYS).permit(:page, *AccountFilter::KEYS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def form_account_batch_params
|
||||||
|
params.require(:form_account_batch).permit(:action, account_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def action_from_button
|
||||||
|
if params[:suspend]
|
||||||
|
'suspend'
|
||||||
|
elsif params[:approve]
|
||||||
|
'approve'
|
||||||
|
elsif params[:reject]
|
||||||
|
'reject'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,55 +1,26 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
require 'sidekiq/api'
|
|
||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class DashboardController < BaseController
|
class DashboardController < BaseController
|
||||||
|
include Redisable
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@users_count = User.count
|
@system_checks = Admin::SystemCheck.perform
|
||||||
|
@time_period = (29.days.ago.to_date...Time.now.utc.to_date)
|
||||||
@pending_users_count = User.pending.count
|
@pending_users_count = User.pending.count
|
||||||
@registrations_week = Redis.current.get("activity:accounts:local:#{current_week}") || 0
|
@pending_reports_count = Report.unresolved.count
|
||||||
@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
|
@pending_tags_count = Tag.pending_review.count
|
||||||
@authorized_fetch = authorized_fetch_mode?
|
@pending_appeals_count = Appeal.pending.count
|
||||||
@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
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def current_week
|
|
||||||
@current_week ||= Time.now.utc.to_date.cweek
|
|
||||||
end
|
|
||||||
|
|
||||||
def redis_info
|
def redis_info
|
||||||
@redis_info ||= begin
|
@redis_info ||= begin
|
||||||
if Redis.current.is_a?(Redis::Namespace)
|
if redis.is_a?(Redis::Namespace)
|
||||||
Redis.current.redis.info
|
redis.redis.info
|
||||||
else
|
else
|
||||||
Redis.current.info
|
redis.info
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
40
app/controllers/admin/disputes/appeals_controller.rb
Normal file
40
app/controllers/admin/disputes/appeals_controller.rb
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::Disputes::AppealsController < Admin::BaseController
|
||||||
|
before_action :set_appeal, except: :index
|
||||||
|
|
||||||
|
def index
|
||||||
|
authorize :appeal, :index?
|
||||||
|
|
||||||
|
@appeals = filtered_appeals.page(params[:page])
|
||||||
|
end
|
||||||
|
|
||||||
|
def approve
|
||||||
|
authorize @appeal, :approve?
|
||||||
|
log_action :approve, @appeal
|
||||||
|
ApproveAppealService.new.call(@appeal, current_account)
|
||||||
|
redirect_to disputes_strike_path(@appeal.strike)
|
||||||
|
end
|
||||||
|
|
||||||
|
def reject
|
||||||
|
authorize @appeal, :approve?
|
||||||
|
log_action :reject, @appeal
|
||||||
|
@appeal.reject!(current_account)
|
||||||
|
UserMailer.appeal_rejected(@appeal.account.user, @appeal)
|
||||||
|
redirect_to disputes_strike_path(@appeal.strike)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def filtered_appeals
|
||||||
|
Admin::AppealFilter.new(filter_params.with_defaults(status: 'pending')).results.includes(strike: :account)
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_params
|
||||||
|
params.slice(:page, *Admin::AppealFilter::KEYS).permit(:page, *Admin::AppealFilter::KEYS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_appeal
|
||||||
|
@appeal = Appeal.find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -22,7 +22,7 @@ module Admin
|
||||||
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
|
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
|
||||||
@domain_block.save
|
@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
|
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
|
@domain_block.errors.delete(:domain)
|
||||||
render :new
|
render :new
|
||||||
else
|
else
|
||||||
if existing_domain_block.present?
|
if existing_domain_block.present?
|
||||||
|
@ -56,10 +56,6 @@ module Admin
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
|
||||||
authorize @domain_block, :show?
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
authorize @domain_block, :destroy?
|
authorize @domain_block, :destroy?
|
||||||
UnblockDomainService.new.call(@domain_block)
|
UnblockDomainService.new.call(@domain_block)
|
||||||
|
|
|
@ -6,7 +6,20 @@ module Admin
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :email_domain_block, :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.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page])
|
||||||
|
@form = Form::EmailDomainBlockBatch.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def batch
|
||||||
|
@form = Form::EmailDomainBlockBatch.new(form_email_domain_block_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||||
|
@form.save
|
||||||
|
rescue ActionController::ParameterMissing
|
||||||
|
flash[:alert] = I18n.t('admin.email_domain_blocks.no_email_domain_block_selected')
|
||||||
|
rescue Mastodon::NotPermittedError
|
||||||
|
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
|
||||||
|
ensure
|
||||||
|
redirect_to admin_email_domain_blocks_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
|
@ -19,41 +32,27 @@ module Admin
|
||||||
|
|
||||||
@email_domain_block = EmailDomainBlock.new(resource_params)
|
@email_domain_block = EmailDomainBlock.new(resource_params)
|
||||||
|
|
||||||
if @email_domain_block.save
|
if action_from_button == 'save'
|
||||||
log_action :create, @email_domain_block
|
EmailDomainBlock.transaction do
|
||||||
|
@email_domain_block.save!
|
||||||
|
log_action :create, @email_domain_block
|
||||||
|
|
||||||
if @email_domain_block.with_dns_records?
|
(@email_domain_block.other_domains || []).uniq.each do |domain|
|
||||||
hostnames = []
|
next if EmailDomainBlock.where(domain: domain).exists?
|
||||||
ips = []
|
|
||||||
|
|
||||||
Resolv::DNS.open do |dns|
|
other_email_domain_block = EmailDomainBlock.create!(domain: domain, parent: @email_domain_block)
|
||||||
dns.timeouts = 5
|
log_action :create, other_email_domain_block
|
||||||
|
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
|
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
|
||||||
else
|
else
|
||||||
|
set_resolved_records
|
||||||
render :new
|
render :new
|
||||||
end
|
end
|
||||||
end
|
rescue ActiveRecord::RecordInvalid
|
||||||
|
set_resolved_records
|
||||||
def destroy
|
render :new
|
||||||
authorize @email_domain_block, :destroy?
|
|
||||||
@email_domain_block.destroy!
|
|
||||||
log_action :destroy, @email_domain_block
|
|
||||||
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -62,8 +61,27 @@ module Admin
|
||||||
@email_domain_block = EmailDomainBlock.find(params[:id])
|
@email_domain_block = EmailDomainBlock.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_resolved_records
|
||||||
|
Resolv::DNS.open do |dns|
|
||||||
|
dns.timeouts = 5
|
||||||
|
@resolved_records = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params.require(:email_domain_block).permit(:domain, :with_dns_records)
|
params.require(:email_domain_block).permit(:domain, other_domains: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def form_email_domain_block_batch_params
|
||||||
|
params.require(:form_email_domain_block_batch).permit(email_domain_block_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def action_from_button
|
||||||
|
if params[:delete]
|
||||||
|
'delete'
|
||||||
|
elsif params[:save]
|
||||||
|
'save'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
53
app/controllers/admin/follow_recommendations_controller.rb
Normal file
53
app/controllers/admin/follow_recommendations_controller.rb
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
class FollowRecommendationsController < BaseController
|
||||||
|
before_action :set_language
|
||||||
|
|
||||||
|
def show
|
||||||
|
authorize :follow_recommendation, :show?
|
||||||
|
|
||||||
|
@form = Form::AccountBatch.new
|
||||||
|
@accounts = filtered_follow_recommendations
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@form = Form::AccountBatch.new(form_account_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||||
|
@form.save
|
||||||
|
rescue ActionController::ParameterMissing
|
||||||
|
# Do nothing
|
||||||
|
ensure
|
||||||
|
redirect_to admin_follow_recommendations_path(filter_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_language
|
||||||
|
@language = follow_recommendation_filter.language
|
||||||
|
end
|
||||||
|
|
||||||
|
def filtered_follow_recommendations
|
||||||
|
follow_recommendation_filter.results
|
||||||
|
end
|
||||||
|
|
||||||
|
def follow_recommendation_filter
|
||||||
|
@follow_recommendation_filter ||= FollowRecommendationFilter.new(filter_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def form_account_batch_params
|
||||||
|
params.require(:form_account_batch).permit(:action, account_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_params
|
||||||
|
params.slice(*FollowRecommendationFilter::KEYS).permit(*FollowRecommendationFilter::KEYS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def action_from_button
|
||||||
|
if params[:suppress]
|
||||||
|
'suppress_follow_recommendation'
|
||||||
|
elsif params[:unsuppress]
|
||||||
|
'unsuppress_follow_recommendation'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,14 +3,47 @@
|
||||||
module Admin
|
module Admin
|
||||||
class InstancesController < BaseController
|
class InstancesController < BaseController
|
||||||
before_action :set_instances, only: :index
|
before_action :set_instances, only: :index
|
||||||
before_action :set_instance, only: :show
|
before_action :set_instance, except: :index
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :instance, :index?
|
authorize :instance, :index?
|
||||||
|
preload_delivery_failures!
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
authorize :instance, :show?
|
authorize :instance, :show?
|
||||||
|
@time_period = (6.days.ago.to_date...Time.now.utc.to_date)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
authorize :instance, :destroy?
|
||||||
|
Admin::DomainPurgeWorker.perform_async(@instance.domain)
|
||||||
|
log_action :destroy, @instance
|
||||||
|
redirect_to admin_instances_path, notice: I18n.t('admin.instances.destroyed_msg', domain: @instance.domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_delivery_errors
|
||||||
|
authorize :delivery, :clear_delivery_errors?
|
||||||
|
@instance.delivery_failure_tracker.clear_failures!
|
||||||
|
redirect_to admin_instance_path(@instance.domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
def restart_delivery
|
||||||
|
authorize :delivery, :restart_delivery?
|
||||||
|
|
||||||
|
if @instance.unavailable?
|
||||||
|
@instance.delivery_failure_tracker.track_success!
|
||||||
|
log_action :destroy, @instance.unavailable_domain
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to admin_instance_path(@instance.domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop_delivery
|
||||||
|
authorize :delivery, :stop_delivery?
|
||||||
|
unavailable_domain = UnavailableDomain.create!(domain: @instance.domain)
|
||||||
|
log_action :create, unavailable_domain
|
||||||
|
redirect_to admin_instance_path(@instance.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -23,6 +56,14 @@ module Admin
|
||||||
@instances = filtered_instances.page(params[:page])
|
@instances = filtered_instances.page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def preload_delivery_failures!
|
||||||
|
warning_domains_map = DeliveryFailureTracker.warning_domains_map
|
||||||
|
|
||||||
|
@instances.each do |instance|
|
||||||
|
instance.failure_days = warning_domains_map[instance.domain]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def filtered_instances
|
def filtered_instances
|
||||||
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
|
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
|
||||||
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
|
|
|
@ -9,7 +9,8 @@ module Admin
|
||||||
def index
|
def index
|
||||||
authorize :account, :index?
|
authorize :account, :index?
|
||||||
|
|
||||||
@accounts = RelationshipFilter.new(@account, filter_params).results.page(params[:page]).per(PER_PAGE)
|
@accounts = RelationshipFilter.new(@account, filter_params).results.includes(:account_stat, user: [:ips, :invite_request]).page(params[:page]).per(PER_PAGE)
|
||||||
|
@form = Form::AccountBatch.new
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -14,20 +14,17 @@ module Admin
|
||||||
if params[:create_and_resolve]
|
if params[:create_and_resolve]
|
||||||
@report.resolve!(current_account)
|
@report.resolve!(current_account)
|
||||||
log_action :resolve, @report
|
log_action :resolve, @report
|
||||||
|
elsif params[:create_and_unresolve]
|
||||||
redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if params[:create_and_unresolve]
|
|
||||||
@report.unresolve!
|
@report.unresolve!
|
||||||
log_action :reopen, @report
|
log_action :reopen, @report
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to admin_report_path(@report), notice: I18n.t('admin.report_notes.created_msg')
|
redirect_to after_create_redirect_path, notice: I18n.t('admin.report_notes.created_msg')
|
||||||
else
|
else
|
||||||
@report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
|
@report_notes = @report.notes.includes(:account).order(id: :desc)
|
||||||
@form = Form::StatusBatch.new
|
@action_logs = @report.history.includes(:target)
|
||||||
|
@form = Admin::StatusBatchAction.new
|
||||||
|
@statuses = @report.statuses.with_includes
|
||||||
|
|
||||||
render template: 'admin/reports/show'
|
render template: 'admin/reports/show'
|
||||||
end
|
end
|
||||||
|
@ -41,6 +38,14 @@ module Admin
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def after_create_redirect_path
|
||||||
|
if params[:create_and_resolve]
|
||||||
|
admin_reports_path
|
||||||
|
else
|
||||||
|
admin_report_path(@report)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params.require(:report_note).permit(
|
params.require(:report_note).permit(
|
||||||
:content,
|
:content,
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Admin
|
|
||||||
class ReportedStatusesController < BaseController
|
|
||||||
before_action :set_report
|
|
||||||
|
|
||||||
def create
|
|
||||||
authorize :status, :update?
|
|
||||||
|
|
||||||
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
|
|
||||||
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')
|
|
||||||
|
|
||||||
redirect_to admin_report_path(@report)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def status_params
|
|
||||||
params.require(:status).permit(:sensitive)
|
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_report
|
|
||||||
@report = Report.find(params[:report_id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
52
app/controllers/admin/reports/actions_controller.rb
Normal file
52
app/controllers/admin/reports/actions_controller.rb
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::Reports::ActionsController < Admin::BaseController
|
||||||
|
before_action :set_report
|
||||||
|
|
||||||
|
def create
|
||||||
|
authorize @report, :show?
|
||||||
|
|
||||||
|
case action_from_button
|
||||||
|
when 'delete', 'mark_as_sensitive'
|
||||||
|
status_batch_action = Admin::StatusBatchAction.new(
|
||||||
|
type: action_from_button,
|
||||||
|
status_ids: @report.status_ids,
|
||||||
|
current_account: current_account,
|
||||||
|
report_id: @report.id,
|
||||||
|
send_email_notification: !@report.spam?
|
||||||
|
)
|
||||||
|
|
||||||
|
status_batch_action.save!
|
||||||
|
when 'silence', 'suspend'
|
||||||
|
account_action = Admin::AccountAction.new(
|
||||||
|
type: action_from_button,
|
||||||
|
report_id: @report.id,
|
||||||
|
target_account: @report.target_account,
|
||||||
|
current_account: current_account,
|
||||||
|
send_email_notification: !@report.spam?
|
||||||
|
)
|
||||||
|
|
||||||
|
account_action.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to admin_reports_path
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_report
|
||||||
|
@report = Report.find(params[:report_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def action_from_button
|
||||||
|
if params[:delete]
|
||||||
|
'delete'
|
||||||
|
elsif params[:mark_as_sensitive]
|
||||||
|
'mark_as_sensitive'
|
||||||
|
elsif params[:silence]
|
||||||
|
'silence'
|
||||||
|
elsif params[:suspend]
|
||||||
|
'suspend'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,8 +13,10 @@ module Admin
|
||||||
authorize @report, :show?
|
authorize @report, :show?
|
||||||
|
|
||||||
@report_note = @report.notes.new
|
@report_note = @report.notes.new
|
||||||
@report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
|
@report_notes = @report.notes.includes(:account).order(id: :desc)
|
||||||
@form = Form::StatusBatch.new
|
@action_logs = @report.history.includes(:target)
|
||||||
|
@form = Admin::StatusBatchAction.new
|
||||||
|
@statuses = @report.statuses.with_includes
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign_to_self
|
def assign_to_self
|
||||||
|
|
|
@ -6,9 +6,9 @@ module Admin
|
||||||
|
|
||||||
def create
|
def create
|
||||||
authorize @user, :reset_password?
|
authorize @user, :reset_password?
|
||||||
@user.send_reset_password_instructions
|
@user.reset_password!
|
||||||
log_action :reset_password, @user
|
log_action :reset_password, @user
|
||||||
redirect_to admin_accounts_path
|
redirect_to admin_account_path(@user.account_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
59
app/controllers/admin/rules_controller.rb
Normal file
59
app/controllers/admin/rules_controller.rb
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
class RulesController < BaseController
|
||||||
|
before_action :set_rule, except: [:index, :create]
|
||||||
|
|
||||||
|
def index
|
||||||
|
authorize :rule, :index?
|
||||||
|
|
||||||
|
@rules = Rule.ordered
|
||||||
|
@rule = Rule.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
authorize :rule, :create?
|
||||||
|
|
||||||
|
@rule = Rule.new(resource_params)
|
||||||
|
|
||||||
|
if @rule.save
|
||||||
|
redirect_to admin_rules_path
|
||||||
|
else
|
||||||
|
@rules = Rule.ordered
|
||||||
|
render :index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
authorize @rule, :update?
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
authorize @rule, :update?
|
||||||
|
|
||||||
|
if @rule.update(resource_params)
|
||||||
|
redirect_to admin_rules_path
|
||||||
|
else
|
||||||
|
render :edit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
authorize @rule, :destroy?
|
||||||
|
|
||||||
|
@rule.discard
|
||||||
|
|
||||||
|
redirect_to admin_rules_path
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_rule
|
||||||
|
@rule = Rule.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource_params
|
||||||
|
params.require(:rule).permit(:text, :priority)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,72 +2,62 @@
|
||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class StatusesController < BaseController
|
class StatusesController < BaseController
|
||||||
helper_method :current_params
|
|
||||||
|
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
before_action :set_statuses
|
||||||
|
|
||||||
PER_PAGE = 20
|
PER_PAGE = 20
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :status, :index?
|
authorize :status, :index?
|
||||||
|
|
||||||
@statuses = @account.statuses.where(visibility: [:public, :unlisted])
|
@status_batch_action = Admin::StatusBatchAction.new
|
||||||
|
|
||||||
if params[:media]
|
|
||||||
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).group(:status_id)
|
|
||||||
@statuses.merge!(Status.where(id: account_media_status_ids))
|
|
||||||
end
|
|
||||||
|
|
||||||
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
|
|
||||||
@form = Form::StatusBatch.new
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def batch
|
||||||
authorize :status, :index?
|
@status_batch_action = Admin::StatusBatchAction.new(admin_status_batch_action_params.merge(current_account: current_account, report_id: params[:report_id], type: action_from_button))
|
||||||
|
@status_batch_action.save!
|
||||||
@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))
|
|
||||||
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
|
rescue ActionController::ParameterMissing
|
||||||
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
|
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
|
||||||
|
ensure
|
||||||
redirect_to admin_account_statuses_path(@account.id, current_params)
|
redirect_to after_create_redirect_path
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def form_status_batch_params
|
def admin_status_batch_action_params
|
||||||
params.require(:form_status_batch).permit(:action, status_ids: [])
|
params.require(:admin_status_batch_action).permit(status_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def after_create_redirect_path
|
||||||
|
report_id = @status_batch_action&.report_id || params[:report_id]
|
||||||
|
if report_id.present?
|
||||||
|
admin_report_path(report_id)
|
||||||
|
else
|
||||||
|
admin_account_statuses_path(params[:account_id], current_params)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
@account = Account.find(params[:account_id])
|
@account = Account.find(params[:account_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_params
|
def set_statuses
|
||||||
page = (params[:page] || 1).to_i
|
@statuses = Admin::StatusFilter.new(@account, filter_params).results.preload(:application, :preloadable_poll, :media_attachments, active_mentions: :account, reblog: [:account, :application, :preloadable_poll, :media_attachments, active_mentions: :account]).page(params[:page]).per(PER_PAGE)
|
||||||
|
end
|
||||||
|
|
||||||
{
|
def filter_params
|
||||||
media: params[:media],
|
params.slice(*Admin::StatusFilter::KEYS).permit(*Admin::StatusFilter::KEYS)
|
||||||
page: page > 1 && page,
|
end
|
||||||
}.select { |_, value| value.present? }
|
|
||||||
|
def current_params
|
||||||
|
params.slice(:media, :page).permit(:media, :page)
|
||||||
end
|
end
|
||||||
|
|
||||||
def action_from_button
|
def action_from_button
|
||||||
if params[:nsfw_on]
|
if params[:report]
|
||||||
'nsfw_on'
|
'report'
|
||||||
elsif params[:nsfw_off]
|
elsif params[:remove_from_report]
|
||||||
'nsfw_off'
|
'remove_from_report'
|
||||||
elsif params[:delete]
|
elsif params[:delete]
|
||||||
'delete'
|
'delete'
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,38 +2,12 @@
|
||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class TagsController < BaseController
|
class TagsController < BaseController
|
||||||
before_action :set_tag, except: [:index, :batch, :approve_all, :reject_all]
|
before_action :set_tag
|
||||||
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
|
def show
|
||||||
authorize @tag, :show?
|
authorize @tag, :show?
|
||||||
|
|
||||||
|
@time_period = (6.days.ago.to_date...Time.now.utc.to_date)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
@ -52,52 +26,8 @@ module Admin
|
||||||
@tag = Tag.find(params[:id])
|
@tag = Tag.find(params[:id])
|
||||||
end
|
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
|
def tag_params
|
||||||
params.require(:tag).permit(:name, :trendable, :usable, :listable)
|
params.require(:tag).permit(:name, :trendable, :usable, :listable)
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::Trends::Links::PreviewCardProvidersController < Admin::BaseController
|
||||||
|
def index
|
||||||
|
authorize :preview_card_provider, :index?
|
||||||
|
|
||||||
|
@preview_card_providers = filtered_preview_card_providers.page(params[:page])
|
||||||
|
@form = Trends::PreviewCardProviderBatch.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def batch
|
||||||
|
@form = Trends::PreviewCardProviderBatch.new(trends_preview_card_provider_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_trends_links_preview_card_providers_path(filter_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def filtered_preview_card_providers
|
||||||
|
Trends::PreviewCardProviderFilter.new(filter_params).results
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_params
|
||||||
|
params.slice(:page, *Trends::PreviewCardProviderFilter::KEYS).permit(:page, *Trends::PreviewCardProviderFilter::KEYS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def trends_preview_card_provider_batch_params
|
||||||
|
params.require(:trends_preview_card_provider_batch).permit(:action, preview_card_provider_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def action_from_button
|
||||||
|
if params[:approve]
|
||||||
|
'approve'
|
||||||
|
elsif params[:reject]
|
||||||
|
'reject'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
45
app/controllers/admin/trends/links_controller.rb
Normal file
45
app/controllers/admin/trends/links_controller.rb
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::Trends::LinksController < Admin::BaseController
|
||||||
|
def index
|
||||||
|
authorize :preview_card, :index?
|
||||||
|
|
||||||
|
@preview_cards = filtered_preview_cards.page(params[:page])
|
||||||
|
@form = Trends::PreviewCardBatch.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def batch
|
||||||
|
@form = Trends::PreviewCardBatch.new(trends_preview_card_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_trends_links_path(filter_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def filtered_preview_cards
|
||||||
|
Trends::PreviewCardFilter.new(filter_params.with_defaults(trending: 'all')).results
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_params
|
||||||
|
params.slice(:page, *Trends::PreviewCardFilter::KEYS).permit(:page, *Trends::PreviewCardFilter::KEYS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def trends_preview_card_batch_params
|
||||||
|
params.require(:trends_preview_card_batch).permit(:action, preview_card_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def action_from_button
|
||||||
|
if params[:approve]
|
||||||
|
'approve'
|
||||||
|
elsif params[:approve_providers]
|
||||||
|
'approve_providers'
|
||||||
|
elsif params[:reject]
|
||||||
|
'reject'
|
||||||
|
elsif params[:reject_providers]
|
||||||
|
'reject_providers'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
45
app/controllers/admin/trends/statuses_controller.rb
Normal file
45
app/controllers/admin/trends/statuses_controller.rb
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::Trends::StatusesController < Admin::BaseController
|
||||||
|
def index
|
||||||
|
authorize :status, :index?
|
||||||
|
|
||||||
|
@statuses = filtered_statuses.page(params[:page])
|
||||||
|
@form = Trends::StatusBatch.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def batch
|
||||||
|
@form = Trends::StatusBatch.new(trends_status_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_trends_statuses_path(filter_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def filtered_statuses
|
||||||
|
Trends::StatusFilter.new(filter_params.with_defaults(trending: 'all')).results.includes(:account, :media_attachments, :active_mentions)
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_params
|
||||||
|
params.slice(:page, *Trends::StatusFilter::KEYS).permit(:page, *Trends::StatusFilter::KEYS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def trends_status_batch_params
|
||||||
|
params.require(:trends_status_batch).permit(:action, status_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def action_from_button
|
||||||
|
if params[:approve]
|
||||||
|
'approve'
|
||||||
|
elsif params[:approve_accounts]
|
||||||
|
'approve_accounts'
|
||||||
|
elsif params[:reject]
|
||||||
|
'reject'
|
||||||
|
elsif params[:reject_accounts]
|
||||||
|
'reject_accounts'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
41
app/controllers/admin/trends/tags_controller.rb
Normal file
41
app/controllers/admin/trends/tags_controller.rb
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::Trends::TagsController < Admin::BaseController
|
||||||
|
def index
|
||||||
|
authorize :tag, :index?
|
||||||
|
|
||||||
|
@tags = filtered_tags.page(params[:page])
|
||||||
|
@form = Trends::TagBatch.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def batch
|
||||||
|
@form = Trends::TagBatch.new(trends_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_trends_tags_path(filter_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def filtered_tags
|
||||||
|
Trends::TagFilter.new(filter_params).results
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_params
|
||||||
|
params.slice(:page, *Trends::TagFilter::KEYS).permit(:page, *Trends::TagFilter::KEYS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def trends_tag_batch_params
|
||||||
|
params.require(:trends_tag_batch).permit(:action, tag_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def action_from_button
|
||||||
|
if params[:approve]
|
||||||
|
'approve'
|
||||||
|
elsif params[:reject]
|
||||||
|
'reject'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -9,7 +9,7 @@ module Admin
|
||||||
@user.disable_two_factor!
|
@user.disable_two_factor!
|
||||||
log_action :disable_2fa, @user
|
log_action :disable_2fa, @user
|
||||||
UserMailer.two_factor_disabled(@user).deliver_later!
|
UserMailer.two_factor_disabled(@user).deliver_later!
|
||||||
redirect_to admin_accounts_path
|
redirect_to admin_account_path(@user.account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -5,6 +5,7 @@ class Api::BaseController < ApplicationController
|
||||||
DEFAULT_ACCOUNTS_LIMIT = 40
|
DEFAULT_ACCOUNTS_LIMIT = 40
|
||||||
|
|
||||||
include RateLimitHeaders
|
include RateLimitHeaders
|
||||||
|
include AccessTokenTrackingConcern
|
||||||
|
|
||||||
skip_before_action :store_current_location
|
skip_before_action :store_current_location
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||||
|
@ -14,8 +15,6 @@ class Api::BaseController < ApplicationController
|
||||||
|
|
||||||
protect_from_forgery with: :null_session
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
skip_around_action :set_locale
|
|
||||||
|
|
||||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||||
render json: { error: e.to_s }, status: 422
|
render json: { error: e.to_s }, status: 422
|
||||||
end
|
end
|
||||||
|
@ -40,7 +39,12 @@ class Api::BaseController < ApplicationController
|
||||||
render json: { error: 'This action is not allowed' }, status: 403
|
render json: { error: 'This action is not allowed' }, status: 403
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight do
|
rescue_from Seahorse::Client::NetworkingError do |e|
|
||||||
|
Rails.logger.warn "Storage server error: #{e}"
|
||||||
|
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from Mastodon::RaceConditionError, Stoplight::Error::RedLight do
|
||||||
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
|
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
|
||||||
end
|
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
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Accounts::FamiliarFollowersController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read, :'read:follows' }
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_accounts
|
||||||
|
|
||||||
|
def index
|
||||||
|
render json: familiar_followers.accounts, each_serializer: REST::FamiliarFollowersSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_accounts
|
||||||
|
@accounts = Account.without_suspended.where(id: account_ids).select('id, hide_collections').index_by(&:id).values_at(*account_ids).compact
|
||||||
|
end
|
||||||
|
|
||||||
|
def familiar_followers
|
||||||
|
FamiliarFollowersPresenter.new(@accounts, current_user.account_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_ids
|
||||||
|
Array(params[:id]).map(&:to_i)
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,8 +5,7 @@ class Api::V1::Accounts::IdentityProofsController < Api::BaseController
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@proofs = @account.suspended? ? [] : @account.identity_proofs.active
|
render json: []
|
||||||
render json: @proofs, each_serializer: REST::IdentityProofSerializer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
18
app/controllers/api/v1/accounts/lookup_controller.rb
Normal file
18
app/controllers/api/v1/accounts/lookup_controller.rb
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Accounts::LookupController < Api::BaseController
|
||||||
|
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
|
||||||
|
before_action :set_account
|
||||||
|
|
||||||
|
def show
|
||||||
|
render json: @account, serializer: REST::AccountSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = ResolveAccountService.new.call(params[:acct], skip_webfinger: true) || raise(ActiveRecord::RecordNotFound)
|
||||||
|
rescue Addressable::URI::InvalidURIError
|
||||||
|
raise(ActiveRecord::RecordNotFound)
|
||||||
|
end
|
||||||
|
end
|
|
@ -22,55 +22,16 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def cached_account_statuses
|
def cached_account_statuses
|
||||||
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
|
|
||||||
|
|
||||||
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
|
|
||||||
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
|
|
||||||
statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
|
|
||||||
statuses.merge!(hashtag_scope) if params[:tagged].present?
|
|
||||||
|
|
||||||
cache_collection_paginated_by_id(
|
cache_collection_paginated_by_id(
|
||||||
statuses,
|
AccountStatusesFilter.new(@account, current_account, params).results,
|
||||||
Status,
|
Status,
|
||||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||||
params_slice(:max_id, :since_id, :min_id)
|
params_slice(:max_id, :since_id, :min_id)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def permitted_account_statuses
|
|
||||||
@account.statuses.permitted_for(@account, current_account)
|
|
||||||
end
|
|
||||||
|
|
||||||
def only_media_scope
|
|
||||||
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def pinned_scope
|
|
||||||
return Status.none if @account.blocking?(current_account)
|
|
||||||
|
|
||||||
@account.pinned_statuses
|
|
||||||
end
|
|
||||||
|
|
||||||
def no_replies_scope
|
|
||||||
Status.without_replies
|
|
||||||
end
|
|
||||||
|
|
||||||
def no_reblogs_scope
|
|
||||||
Status.without_reblogs
|
|
||||||
end
|
|
||||||
|
|
||||||
def hashtag_scope
|
|
||||||
tag = Tag.find_normalized(params[:tagged])
|
|
||||||
|
|
||||||
if tag
|
|
||||||
Status.tagged_with(tag.id)
|
|
||||||
else
|
|
||||||
Status.none
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
params.slice(:limit, *AccountStatusesFilter::KEYS).permit(:limit, *AccountStatusesFilter::KEYS).merge(core_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert_pagination_headers
|
def insert_pagination_headers
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::AccountsController < Api::BaseController
|
class Api::V1::AccountsController < Api::BaseController
|
||||||
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :block, :unblock, :mute, :unmute]
|
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :remove_from_followers, :block, :unblock, :mute, :unmute]
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, only: [:follow, :unfollow]
|
before_action -> { doorkeeper_authorize! :follow, :write, :'write:follows' }, only: [:follow, :unfollow, :remove_from_followers]
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'write:mutes' }, only: [:mute, :unmute]
|
before_action -> { doorkeeper_authorize! :follow, :write, :'write:mutes' }, only: [:mute, :unmute]
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, only: [:block, :unblock]
|
before_action -> { doorkeeper_authorize! :follow, :write, :'write:blocks' }, only: [:block, :unblock]
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:create]
|
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:create]
|
||||||
|
|
||||||
before_action :require_user!, except: [:show, :create]
|
before_action :require_user!, except: [:show, :create]
|
||||||
|
@ -27,13 +27,15 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
|
|
||||||
self.response_body = Oj.dump(response.body)
|
self.response_body = Oj.dump(response.body)
|
||||||
self.status = response.status
|
self.status = response.status
|
||||||
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
|
render json: ValidationErrorFormatter.new(e, :'account.username' => :username, :'invite_request.text' => :reason).as_json, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow
|
def follow
|
||||||
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: true)
|
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: true)
|
||||||
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify? } }, requested_map: { @account.id => false } }
|
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify? } }, requested_map: { @account.id => false } }
|
||||||
|
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
|
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(**options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def block
|
def block
|
||||||
|
@ -51,6 +53,11 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def remove_from_followers
|
||||||
|
RemoveFromFollowersService.new.call(current_user.account, @account)
|
||||||
|
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||||
|
end
|
||||||
|
|
||||||
def unblock
|
def unblock
|
||||||
UnblockService.new.call(current_user.account, @account)
|
UnblockService.new.call(current_user.account, @account)
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||||
|
@ -68,7 +75,7 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationships(**options)
|
def relationships(**options)
|
||||||
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options)
|
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
|
@ -76,10 +83,14 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_enabled_registrations
|
def check_enabled_registrations
|
||||||
forbidden if single_user_mode? || !allowed_registrations?
|
forbidden if single_user_mode? || omniauth_only? || !allowed_registrations?
|
||||||
end
|
end
|
||||||
|
|
||||||
def allowed_registrations?
|
def allowed_registrations?
|
||||||
Setting.registrations_mode != 'none'
|
Setting.registrations_mode != 'none'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def omniauth_only?
|
||||||
|
ENV['OMNIAUTH_ONLY'] == 'true'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Admin::AccountActionsController < Api::BaseController
|
class Api::V1::Admin::AccountActionsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }
|
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' }
|
||||||
before_action :require_staff!
|
before_action :require_staff!
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ class Api::V1::Admin::AccountsController < Api::BaseController
|
||||||
|
|
||||||
LIMIT = 100
|
LIMIT = 100
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:index, :show]
|
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:accounts' }, only: [:index, :show]
|
||||||
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }, except: [:index, :show]
|
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' }, except: [:index, :show]
|
||||||
before_action :require_staff!
|
before_action :require_staff!
|
||||||
before_action :set_accounts, only: :index
|
before_action :set_accounts, only: :index
|
||||||
before_action :set_account, except: :index
|
before_action :set_account, except: :index
|
||||||
|
@ -65,8 +65,9 @@ class Api::V1::Admin::AccountsController < Api::BaseController
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
authorize @account, :destroy?
|
authorize @account, :destroy?
|
||||||
|
json = render_to_body json: @account, serializer: REST::Admin::AccountSerializer
|
||||||
Admin::AccountDeletionWorker.perform_async(@account.id)
|
Admin::AccountDeletionWorker.perform_async(@account.id)
|
||||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
render json: json
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsensitive
|
def unsensitive
|
||||||
|
@ -94,7 +95,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_accounts
|
def set_accounts
|
||||||
@accounts = filtered_accounts.order(id: :desc).includes(user: [:invite_request, :invite]).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
@accounts = filtered_accounts.order(id: :desc).includes(user: [:invite_request, :invite, :ips]).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
|
@ -102,13 +103,27 @@ class Api::V1::Admin::AccountsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def filtered_accounts
|
def filtered_accounts
|
||||||
AccountFilter.new(filter_params).results
|
AccountFilter.new(translated_filter_params).results
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_params
|
def filter_params
|
||||||
params.permit(*FILTER_PARAMS)
|
params.permit(*FILTER_PARAMS)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def translated_filter_params
|
||||||
|
translated_params = { origin: 'local', status: 'active' }.merge(filter_params.slice(*AccountFilter::KEYS))
|
||||||
|
|
||||||
|
translated_params[:origin] = 'remote' if params[:remote].present?
|
||||||
|
|
||||||
|
%i(active pending disabled silenced suspended).each do |status|
|
||||||
|
translated_params[:status] = status.to_s if params[status].present?
|
||||||
|
end
|
||||||
|
|
||||||
|
translated_params[:permissions] = 'staff' if params[:staff].present?
|
||||||
|
|
||||||
|
translated_params
|
||||||
|
end
|
||||||
|
|
||||||
def insert_pagination_headers
|
def insert_pagination_headers
|
||||||
set_pagination_headers(next_path, prev_path)
|
set_pagination_headers(next_path, prev_path)
|
||||||
end
|
end
|
||||||
|
|
23
app/controllers/api/v1/admin/dimensions_controller.rb
Normal file
23
app/controllers/api/v1/admin/dimensions_controller.rb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Admin::DimensionsController < Api::BaseController
|
||||||
|
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||||
|
before_action :require_staff!
|
||||||
|
before_action :set_dimensions
|
||||||
|
|
||||||
|
def create
|
||||||
|
render json: @dimensions, each_serializer: REST::Admin::DimensionSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_dimensions
|
||||||
|
@dimensions = Admin::Metrics::Dimension.retrieve(
|
||||||
|
params[:keys],
|
||||||
|
params[:start_at],
|
||||||
|
params[:end_at],
|
||||||
|
params[:limit],
|
||||||
|
params
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
22
app/controllers/api/v1/admin/measures_controller.rb
Normal file
22
app/controllers/api/v1/admin/measures_controller.rb
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Admin::MeasuresController < Api::BaseController
|
||||||
|
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||||
|
before_action :require_staff!
|
||||||
|
before_action :set_measures
|
||||||
|
|
||||||
|
def create
|
||||||
|
render json: @measures, each_serializer: REST::Admin::MeasureSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_measures
|
||||||
|
@measures = Admin::Metrics::Measure.retrieve(
|
||||||
|
params[:keys],
|
||||||
|
params[:start_at],
|
||||||
|
params[:end_at],
|
||||||
|
params
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,8 +6,8 @@ class Api::V1::Admin::ReportsController < Api::BaseController
|
||||||
|
|
||||||
LIMIT = 100
|
LIMIT = 100
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:reports' }, only: [:index, :show]
|
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:reports' }, only: [:index, :show]
|
||||||
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:reports' }, except: [:index, :show]
|
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:reports' }, except: [:index, :show]
|
||||||
before_action :require_staff!
|
before_action :require_staff!
|
||||||
before_action :set_reports, only: :index
|
before_action :set_reports, only: :index
|
||||||
before_action :set_report, except: :index
|
before_action :set_report, except: :index
|
||||||
|
@ -32,6 +32,12 @@ class Api::V1::Admin::ReportsController < Api::BaseController
|
||||||
render json: @report, serializer: REST::Admin::ReportSerializer
|
render json: @report, serializer: REST::Admin::ReportSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
authorize @report, :update?
|
||||||
|
@report.update!(report_params)
|
||||||
|
render json: @report, serializer: REST::Admin::ReportSerializer
|
||||||
|
end
|
||||||
|
|
||||||
def assign_to_self
|
def assign_to_self
|
||||||
authorize @report, :update?
|
authorize @report, :update?
|
||||||
@report.update!(assigned_account_id: current_account.id)
|
@report.update!(assigned_account_id: current_account.id)
|
||||||
|
@ -74,6 +80,10 @@ class Api::V1::Admin::ReportsController < Api::BaseController
|
||||||
ReportFilter.new(filter_params).results
|
ReportFilter.new(filter_params).results
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def report_params
|
||||||
|
params.permit(:category, rule_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
def filter_params
|
def filter_params
|
||||||
params.permit(*FILTER_PARAMS)
|
params.permit(*FILTER_PARAMS)
|
||||||
end
|
end
|
||||||
|
|
21
app/controllers/api/v1/admin/retention_controller.rb
Normal file
21
app/controllers/api/v1/admin/retention_controller.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Admin::RetentionController < Api::BaseController
|
||||||
|
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||||
|
before_action :require_staff!
|
||||||
|
before_action :set_cohorts
|
||||||
|
|
||||||
|
def create
|
||||||
|
render json: @cohorts, each_serializer: REST::Admin::CohortSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_cohorts
|
||||||
|
@cohorts = Admin::Metrics::Retention.new(
|
||||||
|
params[:start_at],
|
||||||
|
params[:end_at],
|
||||||
|
params[:frequency]
|
||||||
|
).cohorts
|
||||||
|
end
|
||||||
|
end
|
17
app/controllers/api/v1/admin/trends/links_controller.rb
Normal file
17
app/controllers/api/v1/admin/trends/links_controller.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Admin::Trends::LinksController < Api::BaseController
|
||||||
|
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||||
|
before_action :require_staff!
|
||||||
|
before_action :set_links
|
||||||
|
|
||||||
|
def index
|
||||||
|
render json: @links, each_serializer: REST::Trends::LinkSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_links
|
||||||
|
@links = Trends.links.query.limit(limit_param(10))
|
||||||
|
end
|
||||||
|
end
|
17
app/controllers/api/v1/admin/trends/statuses_controller.rb
Normal file
17
app/controllers/api/v1/admin/trends/statuses_controller.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Admin::Trends::StatusesController < Api::BaseController
|
||||||
|
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||||
|
before_action :require_staff!
|
||||||
|
before_action :set_statuses
|
||||||
|
|
||||||
|
def index
|
||||||
|
render json: @statuses, each_serializer: REST::StatusSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_statuses
|
||||||
|
@statuses = cache_collection(Trends.statuses.query.limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status)
|
||||||
|
end
|
||||||
|
end
|
17
app/controllers/api/v1/admin/trends/tags_controller.rb
Normal file
17
app/controllers/api/v1/admin/trends/tags_controller.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Admin::Trends::TagsController < Api::BaseController
|
||||||
|
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||||
|
before_action :require_staff!
|
||||||
|
before_action :set_tags
|
||||||
|
|
||||||
|
def index
|
||||||
|
render json: @tags, each_serializer: REST::Admin::TagSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_tags
|
||||||
|
@tags = Trends.tags.query.limit(limit_param(10))
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::BlocksController < Api::BaseController
|
class Api::V1::BlocksController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'read:blocks' }
|
before_action -> { doorkeeper_authorize! :follow, :read, :'read:blocks' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ class Api::V1::BookmarksController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def results
|
def results
|
||||||
@_results ||= account_bookmarks.eager_load(:status).to_a_paginated_by_id(
|
@_results ||= account_bookmarks.joins(:status).eager_load(:status).to_a_paginated_by_id(
|
||||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||||
params_slice(:max_id, :since_id, :min_id)
|
params_slice(:max_id, :since_id, :min_id)
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Api::V1::Crypto::Keys::ClaimsController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_claim_results
|
def set_claim_results
|
||||||
@claim_results = devices.map { |device_params| ::Keys::ClaimService.new.call(current_account, device_params[:account_id], device_params[:device_id]) }.compact
|
@claim_results = devices.filter_map { |device_params| ::Keys::ClaimService.new.call(current_account, device_params[:account_id], device_params[:device_id]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
|
|
|
@ -17,7 +17,7 @@ class Api::V1::Crypto::Keys::QueriesController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_query_results
|
def set_query_results
|
||||||
@query_results = @accounts.map { |account| ::Keys::QueryService.new.call(account) }.compact
|
@query_results = @accounts.filter_map { |account| ::Keys::QueryService.new.call(account) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_ids
|
def account_ids
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
class Api::V1::DomainBlocksController < Api::BaseController
|
class Api::V1::DomainBlocksController < Api::BaseController
|
||||||
BLOCK_LIMIT = 100
|
BLOCK_LIMIT = 100
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'read:blocks' }, only: :show
|
before_action -> { doorkeeper_authorize! :follow, :read, :'read:blocks' }, only: :show
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, except: :show
|
before_action -> { doorkeeper_authorize! :follow, :write, :'write:blocks' }, except: :show
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
after_action :insert_pagination_headers, only: :show
|
after_action :insert_pagination_headers, only: :show
|
||||||
|
|
||||||
|
|
24
app/controllers/api/v1/emails/confirmations_controller.rb
Normal file
24
app/controllers/api/v1/emails/confirmations_controller.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Emails::ConfirmationsController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
|
||||||
|
before_action :require_user_owned_by_application!
|
||||||
|
before_action :require_user_not_confirmed!
|
||||||
|
|
||||||
|
def create
|
||||||
|
current_user.update!(email: params[:email]) if params.key?(:email)
|
||||||
|
current_user.resend_confirmation_instructions
|
||||||
|
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def require_user_owned_by_application!
|
||||||
|
render json: { error: 'This method is only available to the application the user originally signed-up with' }, status: :forbidden unless current_user && current_user.created_by_application_id == doorkeeper_token.application_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def require_user_not_confirmed!
|
||||||
|
render json: { error: 'This method is only available while the e-mail is awaiting confirmation' }, status: :forbidden unless !current_user.confirmed? || current_user.unconfirmed_email.present?
|
||||||
|
end
|
||||||
|
end
|
|
@ -21,7 +21,7 @@ class Api::V1::FavouritesController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def results
|
def results
|
||||||
@_results ||= account_favourites.eager_load(:status).to_a_paginated_by_id(
|
@_results ||= account_favourites.joins(:status).eager_load(:status).to_a_paginated_by_id(
|
||||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||||
params_slice(:max_id, :since_id, :min_id)
|
params_slice(:max_id, :since_id, :min_id)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::FollowRequestsController < Api::BaseController
|
class Api::V1::FollowRequestsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'read:follows' }, only: :index
|
before_action -> { doorkeeper_authorize! :follow, :read, :'read:follows' }, only: :index
|
||||||
before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, except: :index
|
before_action -> { doorkeeper_authorize! :follow, :write, :'write:follows' }, except: :index
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
after_action :insert_pagination_headers, only: :index
|
after_action :insert_pagination_headers, only: :index
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
|
||||||
|
|
||||||
def authorize
|
def authorize
|
||||||
AuthorizeFollowService.new.call(account, current_account)
|
AuthorizeFollowService.new.call(account, current_account)
|
||||||
NotifyService.new.call(current_account, :follow, Follow.find_by(account: account, target_account: current_account))
|
LocalNotificationWorker.perform_async(current_account.id, Follow.find_by(account: account, target_account: current_account).id, 'Follow', 'follow')
|
||||||
render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
|
render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationships(**options)
|
def relationships(**options)
|
||||||
AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, options)
|
AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_accounts
|
def load_accounts
|
||||||
|
|
|
@ -14,22 +14,21 @@ class Api::V1::Instances::ActivityController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def activity
|
def activity
|
||||||
weeks = []
|
statuses_tracker = ActivityTracker.new('activity:statuses:local', :basic)
|
||||||
|
logins_tracker = ActivityTracker.new('activity:logins', :unique)
|
||||||
|
registrations_tracker = ActivityTracker.new('activity:accounts:local', :basic)
|
||||||
|
|
||||||
12.times do |i|
|
(0...12).map do |i|
|
||||||
day = i.weeks.ago.to_date
|
start_of_week = i.weeks.ago
|
||||||
week_id = day.cweek
|
end_of_week = start_of_week + 6.days
|
||||||
week = Date.commercial(day.cwyear, week_id)
|
|
||||||
|
|
||||||
weeks << {
|
{
|
||||||
week: week.to_time.to_i.to_s,
|
week: start_of_week.to_i.to_s,
|
||||||
statuses: Redis.current.get("activity:statuses:local:#{week_id}") || '0',
|
statuses: statuses_tracker.sum(start_of_week, end_of_week).to_s,
|
||||||
logins: Redis.current.pfcount("activity:logins:#{week_id}").to_s,
|
logins: logins_tracker.sum(start_of_week, end_of_week).to_s,
|
||||||
registrations: Redis.current.get("activity:accounts:local:#{week_id}") || '0',
|
registrations: registrations_tracker.sum(start_of_week, end_of_week).to_s,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
weeks
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_enabled_api!
|
def require_enabled_api!
|
||||||
|
|
17
app/controllers/api/v1/instances/rules_controller.rb
Normal file
17
app/controllers/api/v1/instances/rules_controller.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Instances::RulesController < Api::BaseController
|
||||||
|
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||||
|
|
||||||
|
before_action :set_rules
|
||||||
|
|
||||||
|
def index
|
||||||
|
render json: @rules, each_serializer: REST::RuleSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_rules
|
||||||
|
@rules = Rule.ordered
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,7 +7,7 @@ class Api::V1::MarkersController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@markers = current_user.markers.where(timeline: Array(params[:timeline])).each_with_object({}) { |marker, h| h[marker.timeline] = marker }
|
@markers = current_user.markers.where(timeline: Array(params[:timeline])).index_by(&:timeline)
|
||||||
render json: serialize_map(@markers)
|
render json: serialize_map(@markers)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Api::V1::MediaController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@media_attachment.update!(media_attachment_params)
|
@media_attachment.update!(updateable_media_attachment_params)
|
||||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class Api::V1::MediaController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_media_attachment
|
def set_media_attachment
|
||||||
@media_attachment = current_account.media_attachments.unattached.find(params[:id])
|
@media_attachment = current_account.media_attachments.where(status_id: nil).find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_processing
|
def check_processing
|
||||||
|
@ -42,6 +42,10 @@ class Api::V1::MediaController < Api::BaseController
|
||||||
params.permit(:file, :thumbnail, :description, :focus)
|
params.permit(:file, :thumbnail, :description, :focus)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def updateable_media_attachment_params
|
||||||
|
params.permit(:thumbnail, :description, :focus)
|
||||||
|
end
|
||||||
|
|
||||||
def file_type_error
|
def file_type_error
|
||||||
{ error: 'File type of uploaded media could not be verified' }
|
{ error: 'File type of uploaded media could not be verified' }
|
||||||
end
|
end
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue