Merge branch 'master5' into features/bookmarks
This commit is contained in:
commit
d1f3182779
@ -174,8 +174,7 @@ jobs:
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- run: bundle exec i18n-tasks check-normalized
|
||||
- run: bundle exec i18n-tasks unused
|
||||
- run: bundle exec i18n-tasks missing -t plural
|
||||
- run: bundle exec i18n-tasks unused -l en
|
||||
- run: bundle exec i18n-tasks check-consistent-interpolations
|
||||
|
||||
workflows:
|
||||
|
@ -30,8 +30,8 @@ plugins:
|
||||
channel: eslint-5
|
||||
rubocop:
|
||||
enabled: true
|
||||
channel: rubocop-0-54
|
||||
scss-lint:
|
||||
channel: rubocop-0-71
|
||||
sass-lint:
|
||||
enabled: true
|
||||
exclude_patterns:
|
||||
- spec/
|
||||
|
10
.dependabot/config.yml
Normal file
10
.dependabot/config.yml
Normal file
@ -0,0 +1,10 @@
|
||||
version: 1
|
||||
|
||||
update_configs:
|
||||
- package_manager: "ruby:bundler"
|
||||
directory: "/"
|
||||
update_schedule: "weekly"
|
||||
|
||||
- package_manager: "javascript"
|
||||
directory: "/"
|
||||
update_schedule: "weekly"
|
@ -10,6 +10,7 @@ DB_NAME=postgres
|
||||
DB_PASS=
|
||||
DB_PORT=5432
|
||||
# Optional ElasticSearch configuration
|
||||
# You may also set ES_PREFIX to share the same cluster between multiple Mastodon servers (falls back to REDIS_NAMESPACE if not set)
|
||||
# ES_ENABLED=true
|
||||
# ES_HOST=es
|
||||
# ES_PORT=9200
|
||||
|
@ -41,6 +41,11 @@ module.exports = {
|
||||
'node_modules',
|
||||
'\\.(css|scss|json)$',
|
||||
],
|
||||
'import/resolver': {
|
||||
node: {
|
||||
paths: ['app/javascript'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
|
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
patreon: mastodon
|
||||
open_collective: mastodon
|
@ -1,3 +1,6 @@
|
||||
require:
|
||||
- rubocop-rails
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.3
|
||||
Exclude:
|
||||
@ -80,7 +83,10 @@ Rails/HttpStatus:
|
||||
Rails/Exit:
|
||||
Exclude:
|
||||
- 'lib/mastodon/*'
|
||||
- 'lib/cli'
|
||||
- 'lib/cli.rb'
|
||||
|
||||
Rails/HelperInstanceVariable:
|
||||
Enabled: false
|
||||
|
||||
Style/ClassAndModuleChildren:
|
||||
Enabled: false
|
||||
|
@ -1 +1 @@
|
||||
2.6.0
|
||||
2.6.1
|
||||
|
37
.sass-lint.yml
Normal file
37
.sass-lint.yml
Normal file
@ -0,0 +1,37 @@
|
||||
# Linter Documentation:
|
||||
# https://github.com/sasstools/sass-lint/tree/v1.13.1/docs/options
|
||||
|
||||
files:
|
||||
include: app/javascript/styles/**/*.scss
|
||||
ignore:
|
||||
- app/javascript/styles/mastodon/reset.scss
|
||||
|
||||
rules:
|
||||
# Disallows
|
||||
no-color-literals: 0
|
||||
no-css-comments: 0
|
||||
no-duplicate-properties: 0
|
||||
no-ids: 0
|
||||
no-important: 0
|
||||
no-mergeable-selectors: 0
|
||||
no-misspelled-properties: 0
|
||||
no-qualifying-elements: 0
|
||||
no-transition-all: 0
|
||||
no-vendor-prefixes: 0
|
||||
|
||||
# Nesting
|
||||
force-element-nesting: 0
|
||||
force-attribute-nesting: 0
|
||||
force-pseudo-nesting: 0
|
||||
|
||||
# Name Formats
|
||||
class-name-format: 0
|
||||
leading-zero: 0
|
||||
|
||||
# Style Guide
|
||||
attribute-quotes: 0
|
||||
hex-length: 0
|
||||
indentation: 0
|
||||
nesting-depth: 0
|
||||
property-sort-order: 0
|
||||
quotes: 0
|
264
.scss-lint.yml
264
.scss-lint.yml
@ -1,264 +0,0 @@
|
||||
# Linter Documentation:
|
||||
# https://github.com/brigade/scss-lint/blob/v0.42.2/lib/scss_lint/linter/README.md
|
||||
|
||||
scss_files: 'app/javascript/styles/**/*.scss'
|
||||
|
||||
exclude:
|
||||
- app/javascript/styles/reset.scss
|
||||
|
||||
linters:
|
||||
# Reports when you use improper spacing around ! (the "bang") in !default,
|
||||
# !global, !important, and !optional flags.
|
||||
BangFormat:
|
||||
enabled: false
|
||||
|
||||
# Whether or not to prefer `border: 0` over `border: none`.
|
||||
BorderZero:
|
||||
enabled: false
|
||||
|
||||
# Reports when you define a rule set using a selector with chained classes
|
||||
# (a.k.a. adjoining classes).
|
||||
ChainedClasses:
|
||||
enabled: false
|
||||
|
||||
# Prefer hexadecimal color codes over color keywords.
|
||||
# (e.g. `color: green` is a color keyword)
|
||||
ColorKeyword:
|
||||
enabled: false
|
||||
|
||||
# Prefer color literals (keywords or hexadecimal codes) to be used only in
|
||||
# variable declarations. They should be referred to via variables everywhere
|
||||
# else.
|
||||
ColorVariable:
|
||||
enabled: true
|
||||
|
||||
# Which form of comments to prefer in CSS.
|
||||
Comment:
|
||||
enabled: false
|
||||
|
||||
# Reports @debug statements (which you probably left behind accidentally).
|
||||
DebugStatement:
|
||||
enabled: false
|
||||
|
||||
# Rule sets should be ordered as follows:
|
||||
# - @extend declarations
|
||||
# - @include declarations without inner @content
|
||||
# - properties, @include declarations with inner @content
|
||||
# - nested rule sets.
|
||||
DeclarationOrder:
|
||||
enabled: false
|
||||
|
||||
# `scss-lint:disable` control comments should be preceded by a comment
|
||||
# explaining why these linters are being disabled for this file.
|
||||
# See https://github.com/brigade/scss-lint#disabling-linters-via-source for
|
||||
# more information.
|
||||
DisableLinterReason:
|
||||
enabled: true
|
||||
|
||||
# Reports when you define the same property twice in a single rule set.
|
||||
DuplicateProperty:
|
||||
enabled: false
|
||||
|
||||
# Separate rule, function, and mixin declarations with empty lines.
|
||||
EmptyLineBetweenBlocks:
|
||||
enabled: true
|
||||
|
||||
# Reports when you have an empty rule set.
|
||||
EmptyRule:
|
||||
enabled: true
|
||||
|
||||
# Reports when you have an @extend directive.
|
||||
ExtendDirective:
|
||||
enabled: false
|
||||
|
||||
# Files should always have a final newline. This results in better diffs
|
||||
# when adding lines to the file, since SCM systems such as git won't
|
||||
# think that you touched the last line.
|
||||
FinalNewline:
|
||||
enabled: false
|
||||
|
||||
# HEX colors should use three-character values where possible.
|
||||
HexLength:
|
||||
enabled: false
|
||||
|
||||
# HEX color values should use lower-case colors to differentiate between
|
||||
# letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`.
|
||||
HexNotation:
|
||||
enabled: true
|
||||
|
||||
# Avoid using ID selectors.
|
||||
IdSelector:
|
||||
enabled: false
|
||||
|
||||
# The basenames of @imported SCSS partials should not begin with an
|
||||
# underscore and should not include the filename extension.
|
||||
ImportPath:
|
||||
enabled: false
|
||||
|
||||
# Avoid using !important in properties. It is usually indicative of a
|
||||
# misunderstanding of CSS specificity and can lead to brittle code.
|
||||
ImportantRule:
|
||||
enabled: false
|
||||
|
||||
# Indentation should always be done in increments of 2 spaces.
|
||||
Indentation:
|
||||
enabled: true
|
||||
width: 2
|
||||
|
||||
# Don't write leading zeros for numeric values with a decimal point.
|
||||
LeadingZero:
|
||||
enabled: false
|
||||
|
||||
# Reports when you define the same selector twice in a single sheet.
|
||||
MergeableSelector:
|
||||
enabled: false
|
||||
|
||||
# Functions, mixins, variables, and placeholders should be declared
|
||||
# with all lowercase letters and hyphens instead of underscores.
|
||||
NameFormat:
|
||||
enabled: false
|
||||
|
||||
# Avoid nesting selectors too deeply.
|
||||
NestingDepth:
|
||||
enabled: false
|
||||
|
||||
# Always use placeholder selectors in @extend.
|
||||
PlaceholderInExtend:
|
||||
enabled: false
|
||||
|
||||
# Sort properties in a strict order.
|
||||
PropertySortOrder:
|
||||
enabled: false
|
||||
|
||||
# Reports when you use an unknown or disabled CSS property
|
||||
# (ignoring vendor-prefixed properties).
|
||||
PropertySpelling:
|
||||
enabled: false
|
||||
|
||||
# Configure which units are allowed for property values.
|
||||
PropertyUnits:
|
||||
enabled: false
|
||||
|
||||
# Pseudo-elements, like ::before, and ::first-letter, should be declared
|
||||
# with two colons. Pseudo-classes, like :hover and :first-child, should
|
||||
# be declared with one colon.
|
||||
PseudoElement:
|
||||
enabled: true
|
||||
|
||||
# Avoid qualifying elements in selectors (also known as "tag-qualifying").
|
||||
QualifyingElement:
|
||||
enabled: false
|
||||
|
||||
# Don't write selectors with a depth of applicability greater than 3.
|
||||
SelectorDepth:
|
||||
enabled: false
|
||||
|
||||
# Selectors should always use hyphenated-lowercase, rather than camelCase or
|
||||
# snake_case.
|
||||
SelectorFormat:
|
||||
enabled: false
|
||||
convention: hyphenated_lowercase
|
||||
|
||||
# Prefer the shortest shorthand form possible for properties that support it.
|
||||
Shorthand:
|
||||
enabled: true
|
||||
|
||||
# Each property should have its own line, except in the special case of
|
||||
# single line rulesets.
|
||||
SingleLinePerProperty:
|
||||
enabled: true
|
||||
allow_single_line_rule_sets: true
|
||||
|
||||
# Split selectors onto separate lines after each comma, and have each
|
||||
# individual selector occupy a single line.
|
||||
SingleLinePerSelector:
|
||||
enabled: true
|
||||
|
||||
# Commas in lists should be followed by a space.
|
||||
SpaceAfterComma:
|
||||
enabled: false
|
||||
|
||||
# Properties should be formatted with a single space separating the colon
|
||||
# from the property's value.
|
||||
SpaceAfterPropertyColon:
|
||||
enabled: true
|
||||
|
||||
# Properties should be formatted with no space between the name and the
|
||||
# colon.
|
||||
SpaceAfterPropertyName:
|
||||
enabled: true
|
||||
|
||||
# Variables should be formatted with a single space separating the colon
|
||||
# from the variable's value.
|
||||
SpaceAfterVariableColon:
|
||||
enabled: true
|
||||
|
||||
# Variables should be formatted with no space between the name and the
|
||||
# colon.
|
||||
SpaceAfterVariableName:
|
||||
enabled: false
|
||||
|
||||
# Operators should be formatted with a single space on both sides of an
|
||||
# infix operator.
|
||||
SpaceAroundOperator:
|
||||
enabled: true
|
||||
|
||||
# Opening braces should be preceded by a single space.
|
||||
SpaceBeforeBrace:
|
||||
enabled: true
|
||||
|
||||
# Parentheses should not be padded with spaces.
|
||||
SpaceBetweenParens:
|
||||
enabled: false
|
||||
|
||||
# Enforces that string literals should be written with a consistent form
|
||||
# of quotes (single or double).
|
||||
StringQuotes:
|
||||
enabled: false
|
||||
|
||||
# Property values, @extend, @include, and @import directives, and variable
|
||||
# declarations should always end with a semicolon.
|
||||
TrailingSemicolon:
|
||||
enabled: true
|
||||
|
||||
# Reports lines containing trailing whitespace.
|
||||
TrailingWhitespace:
|
||||
enabled: true
|
||||
|
||||
# Don't write trailing zeros for numeric values with a decimal point.
|
||||
TrailingZero:
|
||||
enabled: false
|
||||
|
||||
# Don't use the `all` keyword to specify transition properties.
|
||||
TransitionAll:
|
||||
enabled: false
|
||||
|
||||
# Numeric values should not contain unnecessary fractional portions.
|
||||
UnnecessaryMantissa:
|
||||
enabled: false
|
||||
|
||||
# Do not use parent selector references (&) when they would otherwise
|
||||
# be unnecessary.
|
||||
UnnecessaryParentReference:
|
||||
enabled: false
|
||||
|
||||
# URLs should be valid and not contain protocols or domain names.
|
||||
UrlFormat:
|
||||
enabled: true
|
||||
|
||||
# URLs should always be enclosed within quotes.
|
||||
UrlQuotes:
|
||||
enabled: true
|
||||
|
||||
# Properties, like color and font, are easier to read and maintain
|
||||
# when defined using variables rather than literals.
|
||||
VariableForProperty:
|
||||
enabled: false
|
||||
|
||||
# Avoid vendor prefixes. Or rather: don't write them yourself.
|
||||
VendorPrefix:
|
||||
enabled: false
|
||||
|
||||
# Omit length units on zero values, e.g. `0px` vs. `0`.
|
||||
ZeroUnit:
|
||||
enabled: true
|
@ -43,4 +43,4 @@ Gruntfile.js
|
||||
|
||||
# for specific ignore
|
||||
!.svgo.yml
|
||||
|
||||
!sass-lint/**/*.yml
|
||||
|
337
AUTHORS.md
337
AUTHORS.md
@ -6,35 +6,35 @@ and provided thanks to the work of the following contributors:
|
||||
|
||||
* [Gargron](https://github.com/Gargron)
|
||||
* [ykzts](https://github.com/ykzts)
|
||||
* [akihikodaki](https://github.com/akihikodaki)
|
||||
* [ThibG](https://github.com/ThibG)
|
||||
* [akihikodaki](https://github.com/akihikodaki)
|
||||
* [mjankowski](https://github.com/mjankowski)
|
||||
* [dependabot[bot]](https://github.com/apps/dependabot)
|
||||
* [unarist](https://github.com/unarist)
|
||||
* [m4sk1n](https://github.com/m4sk1n)
|
||||
* [dependabot[bot]](https://github.com/apps/dependabot)
|
||||
* [yiskah](https://github.com/yiskah)
|
||||
* [nolanlawson](https://github.com/nolanlawson)
|
||||
* [sorin-davidoi](https://github.com/sorin-davidoi)
|
||||
* [ysksn](https://github.com/ysksn)
|
||||
* [sorin-davidoi](https://github.com/sorin-davidoi)
|
||||
* [abcang](https://github.com/abcang)
|
||||
* [lynlynlynx](https://github.com/lynlynlynx)
|
||||
* [alpaca-tc](https://github.com/alpaca-tc)
|
||||
* [mayaeh](https://github.com/mayaeh)
|
||||
* [renatolond](https://github.com/renatolond)
|
||||
* [alpaca-tc](https://github.com/alpaca-tc)
|
||||
* [nclm](https://github.com/nclm)
|
||||
* [ineffyble](https://github.com/ineffyble)
|
||||
* [jeroenpraat](https://github.com/jeroenpraat)
|
||||
* [blackle](https://github.com/blackle)
|
||||
* [Quent-in](https://github.com/Quent-in)
|
||||
* [JantsoP](https://github.com/JantsoP)
|
||||
* [Kjwon15](https://github.com/Kjwon15)
|
||||
* [mabkenar](https://github.com/mabkenar)
|
||||
* [nullkal](https://github.com/nullkal)
|
||||
* [yookoala](https://github.com/yookoala)
|
||||
* [Kjwon15](https://github.com/Kjwon15)
|
||||
* [shuheiktgw](https://github.com/shuheiktgw)
|
||||
* [ashfurrow](https://github.com/ashfurrow)
|
||||
* [Quenty31](https://github.com/Quenty31)
|
||||
* [zunda](https://github.com/zunda)
|
||||
* [Quenty31](https://github.com/Quenty31)
|
||||
* [eramdam](https://github.com/eramdam)
|
||||
* [takayamaki](https://github.com/takayamaki)
|
||||
* [masarakki](https://github.com/masarakki)
|
||||
@ -45,30 +45,33 @@ and provided thanks to the work of the following contributors:
|
||||
* [stephenburgess8](https://github.com/stephenburgess8)
|
||||
* [Wonderfall](https://github.com/Wonderfall)
|
||||
* [matteoaquila](https://github.com/matteoaquila)
|
||||
* [rkarabut](https://github.com/rkarabut)
|
||||
* [yukimochi](https://github.com/yukimochi)
|
||||
* [rkarabut](https://github.com/rkarabut)
|
||||
* [Artoria2e5](https://github.com/Artoria2e5)
|
||||
* [nightpool](https://github.com/nightpool)
|
||||
* [marrus-sh](https://github.com/marrus-sh)
|
||||
* [krainboltgreene](https://github.com/krainboltgreene)
|
||||
* [patf](https://github.com/patf)
|
||||
* [pfigel](https://github.com/pfigel)
|
||||
* [Aldarone](https://github.com/Aldarone)
|
||||
* [BoFFire](https://github.com/BoFFire)
|
||||
* [clworld](https://github.com/clworld)
|
||||
* [dracos](https://github.com/dracos)
|
||||
* [SerCom_KC](mailto:sercom-kc@users.noreply.github.com)
|
||||
* [Sylvhem](https://github.com/Sylvhem)
|
||||
* [nightpool](https://github.com/nightpool)
|
||||
* [MasterGroosha](https://github.com/MasterGroosha)
|
||||
* [JeanGauthier](https://github.com/JeanGauthier)
|
||||
* [kschaper](https://github.com/kschaper)
|
||||
* [MaciekBaron](https://github.com/MaciekBaron)
|
||||
* [MitarashiDango](mailto:mitarashidango@users.noreply.github.com)
|
||||
* [beatrix-bitrot](https://github.com/beatrix-bitrot)
|
||||
* [Aditoo17](https://github.com/Aditoo17)
|
||||
* [adbelle](https://github.com/adbelle)
|
||||
* [evanminto](https://github.com/evanminto)
|
||||
* [MightyPork](https://github.com/MightyPork)
|
||||
* [yhirano55](https://github.com/yhirano55)
|
||||
* [rinsuki](https://github.com/rinsuki)
|
||||
* [camponez](https://github.com/camponez)
|
||||
* [hinaloe](https://github.com/hinaloe)
|
||||
* [SerCom-KC](https://github.com/SerCom-KC)
|
||||
* [aschmitz](https://github.com/aschmitz)
|
||||
* [devkral](https://github.com/devkral)
|
||||
@ -77,6 +80,7 @@ and provided thanks to the work of the following contributors:
|
||||
* [johnsudaar](https://github.com/johnsudaar)
|
||||
* [trebmuh](https://github.com/trebmuh)
|
||||
* [Rakib Hasan](mailto:rmhasan@gmail.com)
|
||||
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
|
||||
* [lindwurm](https://github.com/lindwurm)
|
||||
* [victorhck](mailto:victorhck@geeko.site)
|
||||
* [voidsatisfaction](https://github.com/voidsatisfaction)
|
||||
@ -92,20 +96,21 @@ and provided thanks to the work of the following contributors:
|
||||
* [dunn](https://github.com/dunn)
|
||||
* [xqus](https://github.com/xqus)
|
||||
* [hugogameiro](https://github.com/hugogameiro)
|
||||
* [ariasuni](https://github.com/ariasuni)
|
||||
* [pfm-eyesightjp](https://github.com/pfm-eyesightjp)
|
||||
* [fakenine](https://github.com/fakenine)
|
||||
* [tsuwatch](https://github.com/tsuwatch)
|
||||
* [victorhck](https://github.com/victorhck)
|
||||
* [ashleyhull-versent](https://github.com/ashleyhull-versent)
|
||||
* [kedamaDQ](https://github.com/kedamaDQ)
|
||||
* [puckipedia](https://github.com/puckipedia)
|
||||
* [trwnh](https://github.com/trwnh)
|
||||
* [fvh-P](https://github.com/fvh-P)
|
||||
* [contraexemplo](https://github.com/contraexemplo)
|
||||
* [Anna e só](mailto:contraexemplos@gmail.com)
|
||||
* [BenLubar](https://github.com/BenLubar)
|
||||
* [kazu9su](https://github.com/kazu9su)
|
||||
* [Komic](https://github.com/Komic)
|
||||
* [lmorchard](https://github.com/lmorchard)
|
||||
* [diomed](https://github.com/diomed)
|
||||
* [ariasuni](https://github.com/ariasuni)
|
||||
* [Neetshin](mailto:neetshin@neetsh.in)
|
||||
* [rainyday](https://github.com/rainyday)
|
||||
* [ProgVal](https://github.com/ProgVal)
|
||||
@ -114,7 +119,7 @@ and provided thanks to the work of the following contributors:
|
||||
* [goofy-bz](mailto:goofy@babelzilla.org)
|
||||
* [kadiix](https://github.com/kadiix)
|
||||
* [kodacs](https://github.com/kodacs)
|
||||
* [rtucker](https://github.com/rtucker)
|
||||
* [JMendyk](https://github.com/JMendyk)
|
||||
* [KScl](https://github.com/KScl)
|
||||
* [sterdev](https://github.com/sterdev)
|
||||
* [TheKinrar](https://github.com/TheKinrar)
|
||||
@ -125,16 +130,17 @@ and provided thanks to the work of the following contributors:
|
||||
* [fhemberger](https://github.com/fhemberger)
|
||||
* [greysteil](https://github.com/greysteil)
|
||||
* [hensmith](https://github.com/hensmith)
|
||||
* [hinaloe](https://github.com/hinaloe)
|
||||
* [d6rkaiz](https://github.com/d6rkaiz)
|
||||
* [Reverite](https://github.com/Reverite)
|
||||
* [JMendyk](https://github.com/JMendyk)
|
||||
* [JohnD28](https://github.com/JohnD28)
|
||||
* [znz](https://github.com/znz)
|
||||
* [marek-lach](https://github.com/marek-lach)
|
||||
* [Naouak](https://github.com/Naouak)
|
||||
* [pawelngei](https://github.com/pawelngei)
|
||||
* [rtucker](https://github.com/rtucker)
|
||||
* [reneklacan](https://github.com/reneklacan)
|
||||
* [ekiru](https://github.com/ekiru)
|
||||
* [noellabo](https://github.com/noellabo)
|
||||
* [tcitworld](https://github.com/tcitworld)
|
||||
* [geta6](https://github.com/geta6)
|
||||
* [happycoloredbanana](https://github.com/happycoloredbanana)
|
||||
@ -144,9 +150,8 @@ and provided thanks to the work of the following contributors:
|
||||
* [noraworld](https://github.com/noraworld)
|
||||
* [theboss](https://github.com/theboss)
|
||||
* [178inaba](https://github.com/178inaba)
|
||||
* [Aditoo17](https://github.com/Aditoo17)
|
||||
* [alyssais](https://github.com/alyssais)
|
||||
* [kodnaplakal](https://github.com/kodnaplakal)
|
||||
* [hiphref](https://github.com/hiphref)
|
||||
* [stalker314314](https://github.com/stalker314314)
|
||||
* [huertanix](https://github.com/huertanix)
|
||||
* [genesixx](https://github.com/genesixx)
|
||||
@ -162,11 +167,11 @@ and provided thanks to the work of the following contributors:
|
||||
* [pierreozoux](https://github.com/pierreozoux)
|
||||
* [qguv](https://github.com/qguv)
|
||||
* [Ram Lmn](mailto:ramlmn@users.noreply.github.com)
|
||||
* [sascha-sl](https://github.com/sascha-sl)
|
||||
* [harukasan](https://github.com/harukasan)
|
||||
* [stamak](https://github.com/stamak)
|
||||
* [noellabo](https://github.com/noellabo)
|
||||
* [Technowix](mailto:technowix@users.noreply.github.com)
|
||||
* [Eychics](https://github.com/Eychics)
|
||||
* [Zoeille](https://github.com/Zoeille)
|
||||
* [Thor Harald Johansen](mailto:thj@thj.no)
|
||||
* [0x70b1a5](https://github.com/0x70b1a5)
|
||||
* [gled-rs](https://github.com/gled-rs)
|
||||
@ -179,21 +184,20 @@ and provided thanks to the work of the following contributors:
|
||||
* [hoodie](mailto:hoodiekitten@outlook.com)
|
||||
* [luzi82](https://github.com/luzi82)
|
||||
* [duxovni](https://github.com/duxovni)
|
||||
* [trwnh](https://github.com/trwnh)
|
||||
* [tmm576](https://github.com/tmm576)
|
||||
* [unsmell](https://github.com/unsmell)
|
||||
* [valerauko](https://github.com/valerauko)
|
||||
* [chriswmartin](https://github.com/chriswmartin)
|
||||
* [vahnj](https://github.com/vahnj)
|
||||
* [ikuradon](https://github.com/ikuradon)
|
||||
* [AndreLewin](https://github.com/AndreLewin)
|
||||
* [rinsuki](https://github.com/rinsuki)
|
||||
* [0xflotus](https://github.com/0xflotus)
|
||||
* [redtachyons](https://github.com/redtachyons)
|
||||
* [thurloat](https://github.com/thurloat)
|
||||
* [aaribaud](https://github.com/aaribaud)
|
||||
* [pointlessone](https://github.com/pointlessone)
|
||||
* [Andrew](mailto:andrewlchronister@gmail.com)
|
||||
* [estuans](https://github.com/estuans)
|
||||
* [BenLubar](https://github.com/BenLubar)
|
||||
* [dissolve](https://github.com/dissolve)
|
||||
* [PurpleBooth](https://github.com/PurpleBooth)
|
||||
* [bradurani](https://github.com/bradurani)
|
||||
@ -216,6 +220,7 @@ and provided thanks to the work of the following contributors:
|
||||
* [ErikXXon](https://github.com/ErikXXon)
|
||||
* [ian-kelling](https://github.com/ian-kelling)
|
||||
* [immae](https://github.com/immae)
|
||||
* [J0WI](https://github.com/J0WI)
|
||||
* [foozmeat](https://github.com/foozmeat)
|
||||
* [jasonrhodes](https://github.com/jasonrhodes)
|
||||
* [Jason Snell](mailto:jason@newrelic.com)
|
||||
@ -230,6 +235,7 @@ and provided thanks to the work of the following contributors:
|
||||
* [Lorenz Diener](mailto:halcyon@icosahedron.website)
|
||||
* [alimony](https://github.com/alimony)
|
||||
* [mig5](https://github.com/mig5)
|
||||
* [moritzheiber](https://github.com/moritzheiber)
|
||||
* [ndarville](https://github.com/ndarville)
|
||||
* [Abzol](https://github.com/Abzol)
|
||||
* [pwoolcoc](https://github.com/pwoolcoc)
|
||||
@ -238,9 +244,10 @@ and provided thanks to the work of the following contributors:
|
||||
* [ignisf](https://github.com/ignisf)
|
||||
* [raymestalez](https://github.com/raymestalez)
|
||||
* [remram44](https://github.com/remram44)
|
||||
* [sascha-sl](https://github.com/sascha-sl)
|
||||
* [sts10](https://github.com/sts10)
|
||||
* [u1-liquid](https://github.com/u1-liquid)
|
||||
* [sim6](https://github.com/sim6)
|
||||
* [Sir-Boops](https://github.com/Sir-Boops)
|
||||
* [stemid](https://github.com/stemid)
|
||||
* [sumdog](https://github.com/sumdog)
|
||||
* [ThomasLeister](https://github.com/ThomasLeister)
|
||||
@ -288,6 +295,7 @@ and provided thanks to the work of the following contributors:
|
||||
* [857b](https://github.com/857b)
|
||||
* [insom](https://github.com/insom)
|
||||
* [tachyons](https://github.com/tachyons)
|
||||
* [acid-chicken](https://github.com/acid-chicken)
|
||||
* [Esteth](https://github.com/Esteth)
|
||||
* [unascribed](https://github.com/unascribed)
|
||||
* [Aguay-val](https://github.com/Aguay-val)
|
||||
@ -297,7 +305,6 @@ and provided thanks to the work of the following contributors:
|
||||
* [unleashed](https://github.com/unleashed)
|
||||
* [alxrcs](https://github.com/alxrcs)
|
||||
* [console-cowboy](https://github.com/console-cowboy)
|
||||
* [pointlessone](https://github.com/pointlessone)
|
||||
* [Alkarex](https://github.com/Alkarex)
|
||||
* [a2](https://github.com/a2)
|
||||
* [0xa](https://github.com/0xa)
|
||||
@ -310,8 +317,11 @@ and provided thanks to the work of the following contributors:
|
||||
* [Andreas Drop](mailto:andy@remline.de)
|
||||
* [andi1984](https://github.com/andi1984)
|
||||
* [schas002](https://github.com/schas002)
|
||||
* [contraexemplo](https://github.com/contraexemplo)
|
||||
* [abackstrom](https://github.com/abackstrom)
|
||||
* [armandfardeau](https://github.com/armandfardeau)
|
||||
* [jumbosushi](https://github.com/jumbosushi)
|
||||
* [aurelien-reeves](https://github.com/aurelien-reeves)
|
||||
* [ayumin](https://github.com/ayumin)
|
||||
* [BaptisteGelez](https://github.com/BaptisteGelez)
|
||||
* [bzg](https://github.com/bzg)
|
||||
@ -329,6 +339,7 @@ and provided thanks to the work of the following contributors:
|
||||
* [Motoma](https://github.com/Motoma)
|
||||
* [chriswk](https://github.com/chriswk)
|
||||
* [csu](https://github.com/csu)
|
||||
* [clarfon](https://github.com/clarfon)
|
||||
* [kklleemm](https://github.com/kklleemm)
|
||||
* [colindean](https://github.com/colindean)
|
||||
* [dachinat](https://github.com/dachinat)
|
||||
@ -351,11 +362,13 @@ and provided thanks to the work of the following contributors:
|
||||
* [eai04191](https://github.com/eai04191)
|
||||
* [d3vgru](https://github.com/d3vgru)
|
||||
* [Elizafox](https://github.com/Elizafox)
|
||||
* [enewhuis](https://github.com/enewhuis)
|
||||
* [ericblade](https://github.com/ericblade)
|
||||
* [mikoim](https://github.com/mikoim)
|
||||
* [espenronnevik](https://github.com/espenronnevik)
|
||||
* [Finariel](https://github.com/Finariel)
|
||||
* [siuying](https://github.com/siuying)
|
||||
* [zoc](https://github.com/zoc)
|
||||
* [fwenzel](https://github.com/fwenzel)
|
||||
* [GenbuHase](https://github.com/GenbuHase)
|
||||
* [hattori6789](https://github.com/hattori6789)
|
||||
@ -416,6 +429,7 @@ and provided thanks to the work of the following contributors:
|
||||
* [martymcguire](https://github.com/martymcguire)
|
||||
* [marvinkopf](https://github.com/marvinkopf)
|
||||
* [otsune](https://github.com/otsune)
|
||||
* [mbugowski](https://github.com/mbugowski)
|
||||
* [Mathias B](mailto:10813340+mathias-b@users.noreply.github.com)
|
||||
* [matt-auckland](https://github.com/matt-auckland)
|
||||
* [webroo](https://github.com/webroo)
|
||||
@ -434,10 +448,10 @@ and provided thanks to the work of the following contributors:
|
||||
* [premist](https://github.com/premist)
|
||||
* [Mnkai](https://github.com/Mnkai)
|
||||
* [mitchhentges](https://github.com/mitchhentges)
|
||||
* [moritzheiber](https://github.com/moritzheiber)
|
||||
* [mouse-reeve](https://github.com/mouse-reeve)
|
||||
* [Mozinet-fr](https://github.com/Mozinet-fr)
|
||||
* [lae](https://github.com/lae)
|
||||
* [nosada](https://github.com/nosada)
|
||||
* [Nanamachi](https://github.com/Nanamachi)
|
||||
* [orinthe](https://github.com/orinthe)
|
||||
* [NecroTechno](https://github.com/NecroTechno)
|
||||
@ -454,21 +468,22 @@ and provided thanks to the work of the following contributors:
|
||||
* [noppa](https://github.com/noppa)
|
||||
* [Otakan951](https://github.com/Otakan951)
|
||||
* [fahy](https://github.com/fahy)
|
||||
* [PatrickRWells](https://github.com/PatrickRWells)
|
||||
* [Pangoraw](https://github.com/Pangoraw)
|
||||
* [peterkeen](https://github.com/peterkeen)
|
||||
* [pgate](https://github.com/pgate)
|
||||
* [retokromer](https://github.com/retokromer)
|
||||
* [rfwatson](https://github.com/rfwatson)
|
||||
* [rfreebern](https://github.com/rfreebern)
|
||||
* [PatrickRWells](mailto:32802366+patrickrwells@users.noreply.github.com)
|
||||
* [Paul](mailto:naydex.mc+github@gmail.com)
|
||||
* [Pete Keen](mailto:pete@petekeen.net)
|
||||
* [Pierre-Morgan Gate](mailto:pgate@users.noreply.github.com)
|
||||
* [Ratmir Karabut](mailto:rkarabut@sfmodern.ru)
|
||||
* [Reto Kromer](mailto:retokromer@users.noreply.github.com)
|
||||
* [Rey Tucker](mailto:git@reytucker.us)
|
||||
* [Rob Watson](mailto:rfwatson@users.noreply.github.com)
|
||||
* [Ryan Freebern](mailto:ryan@freebern.org)
|
||||
* [Ryan Wade](mailto:ryan.wade@protonmail.com)
|
||||
* [sylph01](https://github.com/sylph01)
|
||||
* [S-H-GAMELINKS](https://github.com/S-H-GAMELINKS)
|
||||
* [staticsafe](https://github.com/staticsafe)
|
||||
* [snwh](https://github.com/snwh)
|
||||
* [sts10](https://github.com/sts10)
|
||||
* [skoji](https://github.com/skoji)
|
||||
* [ScienJus](https://github.com/ScienJus)
|
||||
* [Ryo Kajiwara](mailto:kfe-fecn6.prussian@s01.info)
|
||||
* [S.H](mailto:gamelinks007@gmail.com)
|
||||
* [Sadiq Saif](mailto:staticsafe@users.noreply.github.com)
|
||||
* [Sam Hewitt](mailto:hewittsamuel@gmail.com)
|
||||
* [Satoshi KOJIMA](mailto:skoji@mac.com)
|
||||
* [ScienJus](mailto:i@scienjus.com)
|
||||
* [Scott Larkin](mailto:scott@codeclimate.com)
|
||||
* [Sebastian Hübner](mailto:imolein@users.noreply.github.com)
|
||||
* [Sebastian Morr](mailto:sebastian@morr.cc)
|
||||
@ -480,9 +495,9 @@ and provided thanks to the work of the following contributors:
|
||||
* [Sho Kusano](mailto:rosylilly@aduca.org)
|
||||
* [Shouko Yu](mailto:imshouko@gmail.com)
|
||||
* [Sina Mashek](mailto:sina@mashek.xyz)
|
||||
* [Sir-Boops](mailto:admin@boops.me)
|
||||
* [Soshi Kato](mailto:mail@sossii.com)
|
||||
* [Spanky](mailto:2788886+spankyworks@users.noreply.github.com)
|
||||
* [Stanislas](mailto:angristan@pm.me)
|
||||
* [StefOfficiel](mailto:pichard.stephane@free.fr)
|
||||
* [Steven Tappert](mailto:admin@dark-it.net)
|
||||
* [Svetlozar Todorov](mailto:svetlik@users.noreply.github.com)
|
||||
@ -532,6 +547,7 @@ and provided thanks to the work of the following contributors:
|
||||
* [fsubal](mailto:fsubal@users.noreply.github.com)
|
||||
* [fusshi-](mailto:dikky1218@users.noreply.github.com)
|
||||
* [gentaro](mailto:gentaroooo@gmail.com)
|
||||
* [gol-cha](mailto:info@mevo.xyz)
|
||||
* [hakoai](mailto:hk--76@qa2.so-net.ne.jp)
|
||||
* [haosbvnker](mailto:github@chaosbunker.com)
|
||||
* [isati](mailto:phil@juchnowi.cz)
|
||||
@ -545,16 +561,18 @@ and provided thanks to the work of the following contributors:
|
||||
* [karlyeurl](mailto:karl.yeurl@gmail.com)
|
||||
* [kedama](mailto:32974885+kedamadq@users.noreply.github.com)
|
||||
* [kodai](mailto:shirafuta.kodai@gmail.com)
|
||||
* [koyu](mailto:me@koyu.space)
|
||||
* [kuro5hin](mailto:rusty@kuro5hin.org)
|
||||
* [luzpaz](mailto:luzpaz@users.noreply.github.com)
|
||||
* [maxypy](mailto:maxime@mpigou.fr)
|
||||
* [mhe](mailto:mail@marcus-herrmann.com)
|
||||
* [mike castleman](mailto:m@mlcastle.net)
|
||||
* [mimikun](mailto:dzdzble_effort_311@outlook.jp)
|
||||
* [mohemohe](mailto:mohemohe@users.noreply.github.com)
|
||||
* [mshrtkch](mailto:mshrtkch@users.noreply.github.com)
|
||||
* [muan](mailto:muan@github.com)
|
||||
* [namelessGonbai](mailto:43787036+namelessgonbai@users.noreply.github.com)
|
||||
* [neetshin](mailto:neetshin@neetsh.in)
|
||||
* [nightpool](mailto:nightpool@users.noreply.github.com)
|
||||
* [rch850](mailto:rich850@gmail.com)
|
||||
* [roikale](mailto:roikale@users.noreply.github.com)
|
||||
* [rysiekpl](mailto:rysiek@hackerspace.pl)
|
||||
@ -589,243 +607,338 @@ This document is provided for informational purposes only. Since it is only upda
|
||||
|
||||
Following people have contributed to translation of Mastodon:
|
||||
|
||||
- **Albanian**
|
||||
- Besnik Bleta
|
||||
- Aditoo
|
||||
- **Arabic**
|
||||
- ButterflyOfFire
|
||||
- Aditoo
|
||||
- Amrz0
|
||||
- **Asturian**
|
||||
- ButterflyOfFire
|
||||
- Enol P.
|
||||
- Aditoo
|
||||
- **Basque**
|
||||
- Osoitz
|
||||
- Aditoo
|
||||
- Aitzol
|
||||
- ButterflyOfFire
|
||||
- Gorka Azkarate
|
||||
- Osoitz
|
||||
- Peru Iparragirre
|
||||
- Gorka Azkarate
|
||||
- **Bengali**
|
||||
- dxwc
|
||||
- **Bulgarian**
|
||||
- ButterflyOfFire
|
||||
- Aditoo
|
||||
- **Catalan**
|
||||
- spla
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Joan Montané
|
||||
- Jose Luis
|
||||
- spla
|
||||
- **Chinese (Hong Kong)**
|
||||
- ButterflyOfFire
|
||||
- Luzi Leung
|
||||
- Aditoo
|
||||
- **Chinese (Simplified)**
|
||||
- Allen Zhong
|
||||
- ButterflyOfFire
|
||||
- SerCom_KC
|
||||
- martialarts
|
||||
- Kaitian Xie
|
||||
- Aditoo
|
||||
- pan93412
|
||||
- **Chinese (Traditional)**
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- James58899
|
||||
- Jeff Huang
|
||||
- pan93412
|
||||
- S1ttidoe477
|
||||
- SHA265
|
||||
- Jeff Huang
|
||||
- **Corsican**
|
||||
- Alix D. R.
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- **Croatian**
|
||||
- ButterflyOfFire
|
||||
- Aditoo
|
||||
- **Czech**
|
||||
- ButterflyOfFire
|
||||
- Lorem Ipsum
|
||||
- Aditoo
|
||||
- Marek Ľach
|
||||
- ButterflyOfFire
|
||||
- **Danish**
|
||||
- ButterflyOfFire
|
||||
- Einhjeriar
|
||||
- Rasmus Sæderup
|
||||
- **Dutch**
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- **Dutch**
|
||||
- Albakham
|
||||
- ButterflyOfFire
|
||||
- Jelv
|
||||
- jeroenpraat
|
||||
- rscmbbng
|
||||
- Aditoo
|
||||
- Jelv
|
||||
- **English**
|
||||
- ButterflyOfFire
|
||||
- Renato "Lond" Cerqueira
|
||||
- **English (United Kingdom)**
|
||||
- Albakham
|
||||
- **Esperanto**
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Becci Cat
|
||||
- Jeong Arm
|
||||
- Martin Bodin
|
||||
- Mélanie Chauvel
|
||||
- Vanege
|
||||
- Martin Bodin
|
||||
- tuxayo/Victor Grousset
|
||||
- **Finnish**
|
||||
- ButterflyOfFire
|
||||
- Jonne Arjoranta
|
||||
- S Heija
|
||||
- Mikko Poussu
|
||||
- Taru Luojola
|
||||
- S Heija
|
||||
- Aditoo
|
||||
- Jonne Arjoranta
|
||||
- **French**
|
||||
- Alda Marteau-Hardi
|
||||
- Albakham
|
||||
- Alix D. R.
|
||||
- Baptiste Jonglez
|
||||
- ButterflyOfFire
|
||||
- Franck Paul
|
||||
- Jean-Baptiste Holcroft
|
||||
- codl
|
||||
- Leia
|
||||
- Alda Marteau-Hardi
|
||||
- Mélanie Chauvel
|
||||
- Paul Marques Mota
|
||||
- azenet
|
||||
- Olivier Humbert
|
||||
- Aditoo
|
||||
- Jonathan Chan
|
||||
- Letiteuf55
|
||||
- Martin Bodin
|
||||
- Mélanie Chauvel
|
||||
- Olivier Humbert
|
||||
- Paul Marques Mota
|
||||
- Sylvhem
|
||||
- Baptiste Jonglez
|
||||
- goofy-mdn
|
||||
- Jean-Baptiste Holcroft
|
||||
- Technowix
|
||||
- Thibaut Girka
|
||||
- Martin Bodin
|
||||
- Théodore
|
||||
- azenet
|
||||
- codl
|
||||
- Thibaut Girka
|
||||
- Franck Paul
|
||||
- Sylvhem
|
||||
- **Galician**
|
||||
- ButterflyOfFire
|
||||
- Xose M.
|
||||
- Aditoo
|
||||
- manequim
|
||||
- **Georgian**
|
||||
- ButterflyOfFire
|
||||
- Aditoo
|
||||
- **German**
|
||||
- Benedikt Geißler
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Daniel
|
||||
- Eugen Rochko
|
||||
- Koyu Berteon
|
||||
- Patrick Figel
|
||||
- Weblate Admin
|
||||
- averageunicorn
|
||||
- ePirat
|
||||
- koyu
|
||||
- Koyu Berteon
|
||||
- larsreineke
|
||||
- koyu
|
||||
- Austin Jones
|
||||
- lilo
|
||||
- Benedikt Geißler
|
||||
- ePirat
|
||||
- Eugen Rochko
|
||||
- Weblate Admin
|
||||
- Patrick Figel
|
||||
- **Greek**
|
||||
- Antonis
|
||||
- ButterflyOfFire
|
||||
- Dimitris Maroulidis
|
||||
- Antonis
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Konstantinos Grevenitis
|
||||
- **Hebrew**
|
||||
- ButterflyOfFire
|
||||
- Aditoo
|
||||
- Ira
|
||||
- Yaron Shahrabani
|
||||
- **Hungarian**
|
||||
- Adam Paszternak
|
||||
- ButterflyOfFire
|
||||
- Adam Paszternak
|
||||
- Aditoo
|
||||
- Tibike Miklós
|
||||
- **Ido**
|
||||
- ButterflyOfFire
|
||||
- Aditoo
|
||||
- **Indonesian**
|
||||
- Alfiana Sibuea
|
||||
- afachri
|
||||
- ButterflyOfFire
|
||||
- Dito Kurnia Pratama
|
||||
- Eirworks
|
||||
- afachri
|
||||
- Aditoo
|
||||
- Alfiana Sibuea
|
||||
- se7entime
|
||||
- **Irish**
|
||||
- Albakham
|
||||
- Kevin Houlihan
|
||||
- **Italian**
|
||||
- Alessandro Levati
|
||||
- Albakham
|
||||
- ButterflyOfFire
|
||||
- Marcin Mikołajczak
|
||||
- Aditoo
|
||||
- Giuseppe Pignataro
|
||||
- Stefano
|
||||
- **Japanese**
|
||||
- ButterflyOfFire
|
||||
- Kumasun Morino
|
||||
- Yamagishi Kazutoshi
|
||||
- Hinaloe
|
||||
- 小鳥遊まりあ
|
||||
- mayaeh
|
||||
- osapon
|
||||
- unarist
|
||||
- 小鳥遊まりあ
|
||||
- 森の子リスのミーコの大冒険
|
||||
- **Korean**
|
||||
- Kumasun Morino
|
||||
- Yamagishi Kazutoshi
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Jeong Arm
|
||||
- unarist
|
||||
- **Kazakh**
|
||||
- arshat
|
||||
- Aditoo
|
||||
- **Korean**
|
||||
- Aditoo
|
||||
- Jeong Arm
|
||||
- ButterflyOfFire
|
||||
- Minori Hiraoka
|
||||
- Yamagishi Kazutoshi
|
||||
- **Lithuanian**
|
||||
- Sarunas Medeikis
|
||||
- **Malay**
|
||||
- ButterflyOfFire
|
||||
- Muhammad Nur Hidayat (MNH48)
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- **Norwegian (old code)**
|
||||
- ButterflyOfFire
|
||||
- Espen Rønnevik
|
||||
- Aditoo
|
||||
- Tale
|
||||
- **Occitan**
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Maxenç
|
||||
- Quenti2
|
||||
- Quentí
|
||||
- Maxenç
|
||||
- **Persian**
|
||||
- ButterflyOfFire
|
||||
- Masoud Abkenar
|
||||
- **Polish**
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- **Polish**
|
||||
- Aditoo
|
||||
- Albakham
|
||||
- ButterflyOfFire
|
||||
- Jakub Mendyk
|
||||
- Marcin Mikołajczak
|
||||
- Marek Ľach
|
||||
- Stasiek Michalski
|
||||
- Marcin Mikołajczak
|
||||
- Jakub Mendyk
|
||||
- Marek Ľach
|
||||
- krkk
|
||||
- **Portuguese**
|
||||
- Albakham
|
||||
- João Pinheiro
|
||||
- manequim
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Hugo Gameiro
|
||||
- manequim
|
||||
- **Portuguese (Brazil)**
|
||||
- André Andrade
|
||||
- Aditoo
|
||||
- Albakham
|
||||
- Anna e só
|
||||
- ButterflyOfFire
|
||||
- Renato "Lond" Cerqueira
|
||||
- **Romanian**
|
||||
- André Andrade
|
||||
- ButterflyOfFire
|
||||
- **Romanian**
|
||||
- adrianbblk
|
||||
- ButterflyOfFire
|
||||
- Aditoo
|
||||
- **Russian**
|
||||
- Andrew Zyabin
|
||||
- Albakham
|
||||
- ButterflyOfFire
|
||||
- Evgeny Petrov
|
||||
- Aditoo
|
||||
- Павел Гастелло
|
||||
- Andrew Zyabin
|
||||
- Yaron Shahrabani
|
||||
- **Serbian**
|
||||
- Branko Kokanovic
|
||||
- Burekz Finezt
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- **Serbian (latin)**
|
||||
- ButterflyOfFire
|
||||
- Aditoo
|
||||
- **Slovak**
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Ivan Pleva
|
||||
- Lorem Ipsum
|
||||
- Marek Ľach
|
||||
- Peter
|
||||
- **Slovenian**
|
||||
- ButterflyOfFire
|
||||
- Kristijan Tkalec
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- **Spanish**
|
||||
- Angeles Broullón
|
||||
- Antón López
|
||||
- Albakham
|
||||
- ButterflyOfFire
|
||||
- Carlos Mondragon
|
||||
- Antón López
|
||||
- Max Winkler
|
||||
- Pablo de la Concepción Sanz
|
||||
- Sergio Soriano
|
||||
- Angeles Broullón
|
||||
- Lothar Wolf
|
||||
- Aditoo
|
||||
- David Charte
|
||||
- Emmanuel
|
||||
- Lothar Wolf
|
||||
- Pablo de la Concepción Sanz
|
||||
- **Swedish**
|
||||
- ButterflyOfFire
|
||||
- Elias Mårtenson
|
||||
- Isak Holmström
|
||||
- Shellkr
|
||||
- Aditoo
|
||||
- Elias Mårtenson
|
||||
- Stefan Midjich
|
||||
- Tim Stahel
|
||||
- Jonas Hultén
|
||||
- **Telugu**
|
||||
- avndp
|
||||
- Ranjith Tellakula
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Joseph Nuthalapati
|
||||
- Ranjith Tellakula
|
||||
- avndp
|
||||
- **Thai**
|
||||
- ButterflyOfFire
|
||||
- parnikkapore
|
||||
- Thai Localization
|
||||
- Aditoo
|
||||
- **Turkish**
|
||||
- Ali Demirtas
|
||||
- ButterflyOfFire
|
||||
- Aditoo
|
||||
- **Ukrainian**
|
||||
- ButterflyOfFire
|
||||
- Ivan Verchenko
|
||||
- alexcleac
|
||||
- **Welsh**
|
||||
- ButterflyOfFire
|
||||
- Jaz-Michael King
|
||||
- Kevin Beynon
|
||||
- Owain Rhys Lewis
|
||||
- Renato "Lond" Cerqueira
|
||||
- Rhoslyn Prys
|
||||
- Aditoo
|
||||
- Ivan Verchenko
|
||||
- **Welsh**
|
||||
- carl morris
|
||||
- Jaz-Michael King
|
||||
- Owain Rhys Lewis
|
||||
- Rhoslyn Prys
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Renato "Lond" Cerqueira
|
||||
- Albakham
|
||||
- Kevin Beynon
|
||||
- **Armenian**
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- **Latvian**
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Maigonis
|
||||
- **Tamil**
|
||||
- Aditoo
|
||||
- ButterflyOfFire
|
||||
- Prasanna Venkadesh
|
||||
|
346
CHANGELOG.md
346
CHANGELOG.md
@ -3,6 +3,352 @@ Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [2.9.2] - 2019-06-22
|
||||
### Added
|
||||
|
||||
- Add `short_description` and `approval_required` to `GET /api/v1/instance` ([Gargron](https://github.com/tootsuite/mastodon/pull/11146))
|
||||
|
||||
### Changed
|
||||
|
||||
- Change camera icon to paperclip icon in upload form ([koyuawsmbrtn](https://github.com/tootsuite/mastodon/pull/11149))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix audio-only OGG and WebM files not being processed as such ([Gargron](https://github.com/tootsuite/mastodon/pull/11151))
|
||||
- Fix audio not being downloaded from remote servers ([Gargron](https://github.com/tootsuite/mastodon/pull/11145))
|
||||
|
||||
## [2.9.1] - 2019-06-22
|
||||
### Added
|
||||
|
||||
- Add moderation API ([Gargron](https://github.com/tootsuite/mastodon/pull/9387))
|
||||
- Add audio uploads ([Gargron](https://github.com/tootsuite/mastodon/pull/11123), [Gargron](https://github.com/tootsuite/mastodon/pull/11141))
|
||||
|
||||
### Changed
|
||||
|
||||
- Change domain blocks to automatically support subdomains ([Gargron](https://github.com/tootsuite/mastodon/pull/11138))
|
||||
- Change Nanobox configuration to bring it up to date ([danhunsaker](https://github.com/tootsuite/mastodon/pull/11083))
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove expensive counters from federation page in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11139))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix converted media being saved with original extension and mime type ([Gargron](https://github.com/tootsuite/mastodon/pull/11130))
|
||||
- Fix layout of identity proofs settings ([acid-chicken](https://github.com/tootsuite/mastodon/pull/11126))
|
||||
- Fix active scope only returning suspended users ([ThibG](https://github.com/tootsuite/mastodon/pull/11111))
|
||||
- Fix sanitizer making block level elements unreadable ([Gargron](https://github.com/tootsuite/mastodon/pull/10836))
|
||||
- Fix label for site theme not being translated in admin UI ([palindromordnilap](https://github.com/tootsuite/mastodon/pull/11121))
|
||||
- Fix statuses not being filtered irreversibly in web UI under some circumstances ([ThibG](https://github.com/tootsuite/mastodon/pull/11113))
|
||||
- Fix scrolling behaviour in compose form ([ThibG](https://github.com/tootsuite/mastodon/pull/11093))
|
||||
|
||||
## [2.9.0] - 2019-06-13
|
||||
### Added
|
||||
|
||||
- **Add single-column mode in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/10807), [Gargron](https://github.com/tootsuite/mastodon/pull/10848), [Gargron](https://github.com/tootsuite/mastodon/pull/11003), [Gargron](https://github.com/tootsuite/mastodon/pull/10961), [Hanage999](https://github.com/tootsuite/mastodon/pull/10915), [noellabo](https://github.com/tootsuite/mastodon/pull/10917), [abcang](https://github.com/tootsuite/mastodon/pull/10859), [Gargron](https://github.com/tootsuite/mastodon/pull/10820), [Gargron](https://github.com/tootsuite/mastodon/pull/10835), [Gargron](https://github.com/tootsuite/mastodon/pull/10809), [Gargron](https://github.com/tootsuite/mastodon/pull/10963), [noellabo](https://github.com/tootsuite/mastodon/pull/10883), [Hanage999](https://github.com/tootsuite/mastodon/pull/10839))
|
||||
- Add waiting time to the list of pending accounts in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10985))
|
||||
- Add a keyboard shortcut to hide/show media in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10647), [Gargron](https://github.com/tootsuite/mastodon/pull/10838), [ThibG](https://github.com/tootsuite/mastodon/pull/10872))
|
||||
- Add `account_id` param to `GET /api/v1/notifications` ([pwoolcoc](https://github.com/tootsuite/mastodon/pull/10796))
|
||||
- Add confirmation modal for unboosting toots in web UI ([aurelien-reeves](https://github.com/tootsuite/mastodon/pull/10287))
|
||||
- Add emoji suggestions to content warning and poll option fields in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10555))
|
||||
- Add `source` attribute to response of `DELETE /api/v1/statuses/:id` ([ThibG](https://github.com/tootsuite/mastodon/pull/10669))
|
||||
- Add some caching for HTML versions of public status pages ([ThibG](https://github.com/tootsuite/mastodon/pull/10701))
|
||||
- Add button to conveniently copy OAuth code ([ThibG](https://github.com/tootsuite/mastodon/pull/11065))
|
||||
|
||||
### Changed
|
||||
|
||||
- **Change default layout to single column in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/10847))
|
||||
- **Change light theme** ([Gargron](https://github.com/tootsuite/mastodon/pull/10992), [Gargron](https://github.com/tootsuite/mastodon/pull/10996), [yuzulabo](https://github.com/tootsuite/mastodon/pull/10754), [Gargron](https://github.com/tootsuite/mastodon/pull/10845))
|
||||
- **Change preferences page into appearance, notifications, and other** ([Gargron](https://github.com/tootsuite/mastodon/pull/10977), [Gargron](https://github.com/tootsuite/mastodon/pull/10988))
|
||||
- Change priority of delete activity forwards for replies and reblogs ([Gargron](https://github.com/tootsuite/mastodon/pull/11002))
|
||||
- Change Mastodon logo to use primary text color of the given theme ([Gargron](https://github.com/tootsuite/mastodon/pull/10994))
|
||||
- Change reblogs counter to be updated when boosted privately ([Gargron](https://github.com/tootsuite/mastodon/pull/10964))
|
||||
- Change bio limit from 160 to 500 characters ([trwnh](https://github.com/tootsuite/mastodon/pull/10790))
|
||||
- Change API rate limiting to reduce allowed unauthenticated requests ([ThibG](https://github.com/tootsuite/mastodon/pull/10860), [hinaloe](https://github.com/tootsuite/mastodon/pull/10868), [mayaeh](https://github.com/tootsuite/mastodon/pull/10867))
|
||||
- Change help text of `tootctl emoji import` command to specify a gzipped TAR archive is required ([dariusk](https://github.com/tootsuite/mastodon/pull/11000))
|
||||
- Change web UI to hide poll options behind content warnings ([ThibG](https://github.com/tootsuite/mastodon/pull/10983))
|
||||
- Change silencing to ensure local effects and remote effects are the same for silenced local users ([ThibG](https://github.com/tootsuite/mastodon/pull/10575))
|
||||
- Change `tootctl domains purge` to remove custom emoji as well ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10721))
|
||||
- Change Docker image to keep `apt` working ([SuperSandro2000](https://github.com/tootsuite/mastodon/pull/10830))
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove `dist-upgrade` from Docker image ([SuperSandro2000](https://github.com/tootsuite/mastodon/pull/10822))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix RTL layout not being RTL within the columns area in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10990))
|
||||
- Fix display of alternative text when a media attachment is not available in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10981))
|
||||
- Fix not being able to directly switch between list timelines in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10973))
|
||||
- Fix media sensitivity not being maintained in delete & redraft in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10980))
|
||||
- Fix emoji picker being always displayed in web UI ([noellabo](https://github.com/tootsuite/mastodon/pull/10979), [yuzulabo](https://github.com/tootsuite/mastodon/pull/10801), [wcpaez](https://github.com/tootsuite/mastodon/pull/10978))
|
||||
- Fix potential private status leak through caching ([ThibG](https://github.com/tootsuite/mastodon/pull/10969))
|
||||
- Fix refreshing featured toots when the new collection is empty in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10971))
|
||||
- Fix undoing domain block also undoing individual moderation on users from before the domain block ([ThibG](https://github.com/tootsuite/mastodon/pull/10660))
|
||||
- Fix time not being local in the audit log ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10751))
|
||||
- Fix statuses removed by moderation re-appearing on subsequent fetches ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10732))
|
||||
- Fix misattribution of inlined announces if `attributedTo` isn't present in ActivityPub ([ThibG](https://github.com/tootsuite/mastodon/pull/10967))
|
||||
- Fix `GET /api/v1/polls/:id` not requiring authentication for non-public polls ([Gargron](https://github.com/tootsuite/mastodon/pull/10960))
|
||||
- Fix handling of blank poll options in ActivityPub ([ThibG](https://github.com/tootsuite/mastodon/pull/10946))
|
||||
- Fix avatar preview aspect ratio on edit profile page ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10931))
|
||||
- Fix web push notifications not being sent for polls ([ThibG](https://github.com/tootsuite/mastodon/pull/10864))
|
||||
- Fix cut off letters in last paragraph of statuses in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/10821))
|
||||
- Fix list not being automatically unpinned when it returns 404 in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11045))
|
||||
- Fix login sometimes redirecting to paths that are not pages ([Gargron](https://github.com/tootsuite/mastodon/pull/11019))
|
||||
|
||||
## [2.8.4] - 2019-05-24
|
||||
### Fixed
|
||||
|
||||
- Fix delivery not retrying on some inbox errors that should be retriable ([ThibG](https://github.com/tootsuite/mastodon/pull/10812))
|
||||
- Fix unnecessary 5 minute cooldowns on signature verifications in some cases ([ThibG](https://github.com/tootsuite/mastodon/pull/10813))
|
||||
- Fix possible race condition when processing statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10815))
|
||||
|
||||
### Security
|
||||
|
||||
- Require specific OAuth scopes for specific endpoints of the streaming API, instead of merely requiring a token for all endpoints, and allow using WebSockets protocol negotiation to specify the access token instead of using a query string ([ThibG](https://github.com/tootsuite/mastodon/pull/10818))
|
||||
|
||||
## [2.8.3] - 2019-05-19
|
||||
### Added
|
||||
|
||||
- Add `og:image:alt` OpenGraph tag ([BenLubar](https://github.com/tootsuite/mastodon/pull/10779))
|
||||
- Add clickable area below avatar in statuses in web UI ([Dar13](https://github.com/tootsuite/mastodon/pull/10766))
|
||||
- Add crossed-out eye icon on account gallery in web UI ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10715))
|
||||
- Add media description tooltip to thumbnails in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10713))
|
||||
|
||||
### Changed
|
||||
|
||||
- Change "mark as sensitive" button into a checkbox for clarity ([ThibG](https://github.com/tootsuite/mastodon/pull/10748))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix bug allowing users to publicly boost their private statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10775), [ThibG](https://github.com/tootsuite/mastodon/pull/10783))
|
||||
- Fix performance in formatter by a little ([ThibG](https://github.com/tootsuite/mastodon/pull/10765))
|
||||
- Fix some colors in the light theme ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10754))
|
||||
- Fix some colors of the high contrast theme ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10711))
|
||||
- Fix ambivalent active state of poll refresh button in web UI ([MaciekBaron](https://github.com/tootsuite/mastodon/pull/10720))
|
||||
- Fix duplicate posting being possible from web UI ([hinaloe](https://github.com/tootsuite/mastodon/pull/10785))
|
||||
- Fix "invited by" not showing up in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10791))
|
||||
|
||||
## [2.8.2] - 2019-05-05
|
||||
### Added
|
||||
|
||||
- Add `SOURCE_TAG` environment variable ([ushitora-anqou](https://github.com/tootsuite/mastodon/pull/10698))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix cropped hero image on frontpage ([BaptisteGelez](https://github.com/tootsuite/mastodon/pull/10702))
|
||||
- Fix blurhash gem not compiling on some operating systems ([Gargron](https://github.com/tootsuite/mastodon/pull/10700))
|
||||
- Fix unexpected CSS animations in some browsers ([ThibG](https://github.com/tootsuite/mastodon/pull/10699))
|
||||
- Fix closing video modal scrolling timelines to top ([ThibG](https://github.com/tootsuite/mastodon/pull/10695))
|
||||
|
||||
## [2.8.1] - 2019-05-04
|
||||
### Added
|
||||
|
||||
- Add link to existing domain block when trying to block an already-blocked domain ([ThibG](https://github.com/tootsuite/mastodon/pull/10663))
|
||||
- Add button to view context to media modal when opened from account gallery in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10676))
|
||||
- Add ability to create multiple-choice polls in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10603))
|
||||
- Add `GITHUB_REPOSITORY` and `SOURCE_BASE_URL` environment variables ([rosylilly](https://github.com/tootsuite/mastodon/pull/10600))
|
||||
- Add `/interact/` paths to `robots.txt` ([ThibG](https://github.com/tootsuite/mastodon/pull/10666))
|
||||
- Add `blurhash` to the Attachment entity in the REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/10630))
|
||||
|
||||
### Changed
|
||||
|
||||
- Change hidden media to be shown as a blurhash-based colorful gradient instead of a black box in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10630))
|
||||
- Change rejected media to be shown as a blurhash-based gradient instead of a list of filenames in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10630))
|
||||
- Change e-mail whitelist/blacklist to not be checked when invited ([Gargron](https://github.com/tootsuite/mastodon/pull/10683))
|
||||
- Change cache header of REST API results to no-cache ([ThibG](https://github.com/tootsuite/mastodon/pull/10655))
|
||||
- Change the "mark media as sensitive" button to be more obvious in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10673), [Gargron](https://github.com/tootsuite/mastodon/pull/10682))
|
||||
- Change account gallery in web UI to display 3 columns, open media modal ([Gargron](https://github.com/tootsuite/mastodon/pull/10667), [Gargron](https://github.com/tootsuite/mastodon/pull/10674))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix LDAP/PAM/SAML/CAS users not being pre-approved ([Gargron](https://github.com/tootsuite/mastodon/pull/10621))
|
||||
- Fix accounts created through tootctl not being always pre-approved ([Gargron](https://github.com/tootsuite/mastodon/pull/10684))
|
||||
- Fix Sidekiq retrying ActivityPub processing jobs that fail validation ([ThibG](https://github.com/tootsuite/mastodon/pull/10614))
|
||||
- Fix toots not being scrolled into view sometimes through keyboard selection ([ThibG](https://github.com/tootsuite/mastodon/pull/10593))
|
||||
- Fix expired invite links being usable to bypass approval mode ([ThibG](https://github.com/tootsuite/mastodon/pull/10657))
|
||||
- Fix not being able to save e-mail preference for new pending accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/10622))
|
||||
- Fix upload progressbar when image resizing is involved ([ThibG](https://github.com/tootsuite/mastodon/pull/10632))
|
||||
- Fix block action not automatically cancelling pending follow request ([ThibG](https://github.com/tootsuite/mastodon/pull/10633))
|
||||
- Fix stoplight logging to stderr separate from Rails logger ([Gargron](https://github.com/tootsuite/mastodon/pull/10624))
|
||||
- Fix sign up button not saying sign up when invite is used ([Gargron](https://github.com/tootsuite/mastodon/pull/10623))
|
||||
- Fix health checks in Docker Compose configuration ([fabianonline](https://github.com/tootsuite/mastodon/pull/10553))
|
||||
- Fix modal items not being scrollable on touch devices ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/10605))
|
||||
- Fix Keybase configuration using wrong domain when a web domain is used ([BenLubar](https://github.com/tootsuite/mastodon/pull/10565))
|
||||
- Fix avatar GIFs not being animated on-hover on public profiles ([hyenagirl64](https://github.com/tootsuite/mastodon/pull/10549))
|
||||
- Fix OpenGraph parser not understanding some valid property meta tags ([da2x](https://github.com/tootsuite/mastodon/pull/10604))
|
||||
- Fix wrong fonts being displayed when Roboto is installed on user's machine ([ThibG](https://github.com/tootsuite/mastodon/pull/10594))
|
||||
- Fix confirmation modals being too narrow for a secondary action button ([ThibG](https://github.com/tootsuite/mastodon/pull/10586))
|
||||
|
||||
## [2.8.0] - 2019-04-10
|
||||
### Added
|
||||
|
||||
- Add polls ([Gargron](https://github.com/tootsuite/mastodon/pull/10111), [ThibG](https://github.com/tootsuite/mastodon/pull/10155), [Gargron](https://github.com/tootsuite/mastodon/pull/10184), [ThibG](https://github.com/tootsuite/mastodon/pull/10196), [Gargron](https://github.com/tootsuite/mastodon/pull/10248), [ThibG](https://github.com/tootsuite/mastodon/pull/10255), [ThibG](https://github.com/tootsuite/mastodon/pull/10322), [Gargron](https://github.com/tootsuite/mastodon/pull/10138), [Gargron](https://github.com/tootsuite/mastodon/pull/10139), [Gargron](https://github.com/tootsuite/mastodon/pull/10144), [Gargron](https://github.com/tootsuite/mastodon/pull/10145),[Gargron](https://github.com/tootsuite/mastodon/pull/10146), [Gargron](https://github.com/tootsuite/mastodon/pull/10148), [Gargron](https://github.com/tootsuite/mastodon/pull/10151), [ThibG](https://github.com/tootsuite/mastodon/pull/10150), [Gargron](https://github.com/tootsuite/mastodon/pull/10168), [Gargron](https://github.com/tootsuite/mastodon/pull/10165), [Gargron](https://github.com/tootsuite/mastodon/pull/10172), [Gargron](https://github.com/tootsuite/mastodon/pull/10170), [Gargron](https://github.com/tootsuite/mastodon/pull/10171), [Gargron](https://github.com/tootsuite/mastodon/pull/10186), [Gargron](https://github.com/tootsuite/mastodon/pull/10189), [ThibG](https://github.com/tootsuite/mastodon/pull/10200), [rinsuki](https://github.com/tootsuite/mastodon/pull/10203), [Gargron](https://github.com/tootsuite/mastodon/pull/10213), [Gargron](https://github.com/tootsuite/mastodon/pull/10246), [Gargron](https://github.com/tootsuite/mastodon/pull/10265), [Gargron](https://github.com/tootsuite/mastodon/pull/10261), [ThibG](https://github.com/tootsuite/mastodon/pull/10333), [Gargron](https://github.com/tootsuite/mastodon/pull/10352), [ThibG](https://github.com/tootsuite/mastodon/pull/10140), [ThibG](https://github.com/tootsuite/mastodon/pull/10142), [ThibG](https://github.com/tootsuite/mastodon/pull/10141), [ThibG](https://github.com/tootsuite/mastodon/pull/10162), [ThibG](https://github.com/tootsuite/mastodon/pull/10161), [ThibG](https://github.com/tootsuite/mastodon/pull/10158), [ThibG](https://github.com/tootsuite/mastodon/pull/10156), [ThibG](https://github.com/tootsuite/mastodon/pull/10160), [Gargron](https://github.com/tootsuite/mastodon/pull/10185), [Gargron](https://github.com/tootsuite/mastodon/pull/10188), [ThibG](https://github.com/tootsuite/mastodon/pull/10195), [ThibG](https://github.com/tootsuite/mastodon/pull/10208), [Gargron](https://github.com/tootsuite/mastodon/pull/10187), [ThibG](https://github.com/tootsuite/mastodon/pull/10214), [ThibG](https://github.com/tootsuite/mastodon/pull/10209))
|
||||
- Add follows & followers managing UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10268), [Gargron](https://github.com/tootsuite/mastodon/pull/10308), [Gargron](https://github.com/tootsuite/mastodon/pull/10404), [Gargron](https://github.com/tootsuite/mastodon/pull/10293))
|
||||
- Add identity proof integration with Keybase ([Gargron](https://github.com/tootsuite/mastodon/pull/10297), [xgess](https://github.com/tootsuite/mastodon/pull/10375), [Gargron](https://github.com/tootsuite/mastodon/pull/10338), [Gargron](https://github.com/tootsuite/mastodon/pull/10350), [Gargron](https://github.com/tootsuite/mastodon/pull/10414))
|
||||
- Add option to overwrite imported data instead of merging ([Gargron](https://github.com/tootsuite/mastodon/pull/9962))
|
||||
- Add featured hashtags to profiles ([Gargron](https://github.com/tootsuite/mastodon/pull/9755), [Gargron](https://github.com/tootsuite/mastodon/pull/10167), [Gargron](https://github.com/tootsuite/mastodon/pull/10249), [ThibG](https://github.com/tootsuite/mastodon/pull/10034))
|
||||
- Add admission-based registrations mode ([Gargron](https://github.com/tootsuite/mastodon/pull/10250), [ThibG](https://github.com/tootsuite/mastodon/pull/10269), [Gargron](https://github.com/tootsuite/mastodon/pull/10264), [ThibG](https://github.com/tootsuite/mastodon/pull/10321), [Gargron](https://github.com/tootsuite/mastodon/pull/10349), [Gargron](https://github.com/tootsuite/mastodon/pull/10469))
|
||||
- Add support for WebP uploads ([acid-chicken](https://github.com/tootsuite/mastodon/pull/9879))
|
||||
- Add "copy link" item to status action bars in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/9983))
|
||||
- Add list title editing in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/9748))
|
||||
- Add a "Block & Report" button to the block confirmation dialog in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10360))
|
||||
- Add disappointed elephant when the page crashes in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10275))
|
||||
- Add ability to upload multiple files at once in web UI ([tmm576](https://github.com/tootsuite/mastodon/pull/9856))
|
||||
- Add indication when you are not allowed to follow an account in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10420), [Gargron](https://github.com/tootsuite/mastodon/pull/10491))
|
||||
- Add validations to admin settings to catch common mistakes ([Gargron](https://github.com/tootsuite/mastodon/pull/10348), [ThibG](https://github.com/tootsuite/mastodon/pull/10354))
|
||||
- Add `type`, `limit`, `offset`, `min_id`, `max_id`, `account_id` to search API ([Gargron](https://github.com/tootsuite/mastodon/pull/10091))
|
||||
- Add a preferences API so apps can share basic behaviours ([Gargron](https://github.com/tootsuite/mastodon/pull/10109))
|
||||
- Add `visibility` param to reblog REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/9851), [ThibG](https://github.com/tootsuite/mastodon/pull/10302))
|
||||
- Add `allowfullscreen` attribute to OEmbed iframe ([rinsuki](https://github.com/tootsuite/mastodon/pull/10370))
|
||||
- Add `blocked_by` relationship to the REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/10373))
|
||||
- Add `tootctl statuses remove` to sweep unreferenced statuses ([Gargron](https://github.com/tootsuite/mastodon/pull/10063))
|
||||
- Add `tootctl search deploy` to avoid ugly rake task syntax ([Gargron](https://github.com/tootsuite/mastodon/pull/10403))
|
||||
- Add `tootctl self-destruct` to shut down server gracefully ([Gargron](https://github.com/tootsuite/mastodon/pull/10367))
|
||||
- Add option to hide application used to toot ([ThibG](https://github.com/tootsuite/mastodon/pull/9897), [rinsuki](https://github.com/tootsuite/mastodon/pull/9994), [hinaloe](https://github.com/tootsuite/mastodon/pull/10086))
|
||||
- Add `DB_SSLMODE` configuration variable ([sascha-sl](https://github.com/tootsuite/mastodon/pull/10210))
|
||||
- Add click-to-copy UI to invites page ([Gargron](https://github.com/tootsuite/mastodon/pull/10259))
|
||||
- Add self-replies fetching ([ThibG](https://github.com/tootsuite/mastodon/pull/10106), [ThibG](https://github.com/tootsuite/mastodon/pull/10128), [ThibG](https://github.com/tootsuite/mastodon/pull/10175), [ThibG](https://github.com/tootsuite/mastodon/pull/10201))
|
||||
- Add rate limit for media proxy requests ([Gargron](https://github.com/tootsuite/mastodon/pull/10490))
|
||||
- Add `tootctl emoji purge` ([Gargron](https://github.com/tootsuite/mastodon/pull/10481))
|
||||
- Add `tootctl accounts approve` ([Gargron](https://github.com/tootsuite/mastodon/pull/10480))
|
||||
- Add `tootctl accounts reset-relationships` ([noellabo](https://github.com/tootsuite/mastodon/pull/10483))
|
||||
|
||||
### Changed
|
||||
|
||||
- Change design of landing page ([Gargron](https://github.com/tootsuite/mastodon/pull/10232), [Gargron](https://github.com/tootsuite/mastodon/pull/10260), [ThibG](https://github.com/tootsuite/mastodon/pull/10284), [ThibG](https://github.com/tootsuite/mastodon/pull/10291), [koyuawsmbrtn](https://github.com/tootsuite/mastodon/pull/10356), [Gargron](https://github.com/tootsuite/mastodon/pull/10245))
|
||||
- Change design of profile column in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10337), [Aditoo17](https://github.com/tootsuite/mastodon/pull/10387), [ThibG](https://github.com/tootsuite/mastodon/pull/10390), [mayaeh](https://github.com/tootsuite/mastodon/pull/10379), [ThibG](https://github.com/tootsuite/mastodon/pull/10411))
|
||||
- Change language detector threshold from 140 characters to 4 words ([Gargron](https://github.com/tootsuite/mastodon/pull/10376))
|
||||
- Change language detector to always kick in for non-latin alphabets ([Gargron](https://github.com/tootsuite/mastodon/pull/10276))
|
||||
- Change icons of features on admin dashboard ([Gargron](https://github.com/tootsuite/mastodon/pull/10366))
|
||||
- Change DNS timeouts from 1s to 5s ([ThibG](https://github.com/tootsuite/mastodon/pull/10238))
|
||||
- Change Docker image to use Ubuntu with jemalloc ([Sir-Boops](https://github.com/tootsuite/mastodon/pull/10100), [BenLubar](https://github.com/tootsuite/mastodon/pull/10212))
|
||||
- Change public pages to be cacheable by proxies ([BenLubar](https://github.com/tootsuite/mastodon/pull/9059))
|
||||
- Change the 410 gone response for suspended accounts to be cacheable by proxies ([ThibG](https://github.com/tootsuite/mastodon/pull/10339))
|
||||
- Change web UI to not not empty timeline of blocked users on block ([ThibG](https://github.com/tootsuite/mastodon/pull/10359))
|
||||
- Change JSON serializer to remove unused `@context` values ([Gargron](https://github.com/tootsuite/mastodon/pull/10378))
|
||||
- Change GIFV file size limit to be the same as for other videos ([rinsuki](https://github.com/tootsuite/mastodon/pull/9924))
|
||||
- Change Webpack to not use @babel/preset-env to compile node_modules ([ykzts](https://github.com/tootsuite/mastodon/pull/10289))
|
||||
- Change web UI to use new Web Share Target API ([gol-cha](https://github.com/tootsuite/mastodon/pull/9963))
|
||||
- Change ActivityPub reports to have persistent URIs ([ThibG](https://github.com/tootsuite/mastodon/pull/10303))
|
||||
- Change `tootctl accounts cull --dry-run` to list accounts that would be deleted ([BenLubar](https://github.com/tootsuite/mastodon/pull/10460))
|
||||
- Change format of CSV exports of follows and mutes to include extra settings ([ThibG](https://github.com/tootsuite/mastodon/pull/10495), [ThibG](https://github.com/tootsuite/mastodon/pull/10335))
|
||||
- Change ActivityPub collections to be cacheable by proxies ([ThibG](https://github.com/tootsuite/mastodon/pull/10467))
|
||||
- Change REST API and public profiles to not return follows/followers for users that have blocked you ([Gargron](https://github.com/tootsuite/mastodon/pull/10491))
|
||||
- Change the groupings of menu items in settings navigation ([Gargron](https://github.com/tootsuite/mastodon/pull/10533))
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove zopfli compression to speed up Webpack from 6min to 1min ([nolanlawson](https://github.com/tootsuite/mastodon/pull/10288))
|
||||
- Remove stats.json generation to speed up Webpack ([nolanlawson](https://github.com/tootsuite/mastodon/pull/10290))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix public timelines being broken by new toots when they are not mounted in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10131))
|
||||
- Fix quick filter settings not being saved when selecting a different filter in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10296))
|
||||
- Fix remote interaction dialogs being indexed by search engines ([Gargron](https://github.com/tootsuite/mastodon/pull/10240))
|
||||
- Fix maxed-out invites not showing up as expired in UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10274))
|
||||
- Fix scrollbar styles on compose textarea ([Gargron](https://github.com/tootsuite/mastodon/pull/10292))
|
||||
- Fix timeline merge workers being queued for remote users ([Gargron](https://github.com/tootsuite/mastodon/pull/10355))
|
||||
- Fix alternative relay support regression ([Gargron](https://github.com/tootsuite/mastodon/pull/10398))
|
||||
- Fix trying to fetch keys of unknown accounts on a self-delete from them ([ThibG](https://github.com/tootsuite/mastodon/pull/10326))
|
||||
- Fix CAS `:service_validate_url` option ([enewhuis](https://github.com/tootsuite/mastodon/pull/10328))
|
||||
- Fix race conditions when creating backups ([ThibG](https://github.com/tootsuite/mastodon/pull/10234))
|
||||
- Fix whitespace not being stripped out of username before validation ([aurelien-reeves](https://github.com/tootsuite/mastodon/pull/10239))
|
||||
- Fix n+1 query when deleting status ([Gargron](https://github.com/tootsuite/mastodon/pull/10247))
|
||||
- Fix exiting follows not being rejected when suspending a remote account ([ThibG](https://github.com/tootsuite/mastodon/pull/10230))
|
||||
- Fix the underlying button element in a disabled icon button not being disabled ([ThibG](https://github.com/tootsuite/mastodon/pull/10194))
|
||||
- Fix race condition when streaming out deleted statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10280))
|
||||
- Fix performance of admin federation UI by caching account counts ([Gargron](https://github.com/tootsuite/mastodon/pull/10374))
|
||||
- Fix JS error on pages that don't define a CSRF token ([hinaloe](https://github.com/tootsuite/mastodon/pull/10383))
|
||||
- Fix `tootctl accounts cull` sometimes removing accounts that are temporarily unreachable ([BenLubar](https://github.com/tootsuite/mastodon/pull/10460))
|
||||
|
||||
## [2.7.4] - 2019-03-05
|
||||
### Fixed
|
||||
|
||||
- Fix web UI not cleaning up notifications after block ([Gargron](https://github.com/tootsuite/mastodon/pull/10108))
|
||||
- Fix redundant HTTP requests when resolving private statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10115))
|
||||
- Fix performance of account media query ([abcang](https://github.com/tootsuite/mastodon/pull/10121))
|
||||
- Fix mention processing for unknown accounts ([ThibG](https://github.com/tootsuite/mastodon/pull/10125))
|
||||
- Fix getting started column not scrolling on short screens ([trwnh](https://github.com/tootsuite/mastodon/pull/10075))
|
||||
- Fix direct messages pagination in the web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10126))
|
||||
- Fix serialization of Announce activities ([ThibG](https://github.com/tootsuite/mastodon/pull/10129))
|
||||
- Fix home timeline perpetually reloading when empty in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/10130))
|
||||
- Fix lists export ([ThibG](https://github.com/tootsuite/mastodon/pull/10136))
|
||||
- Fix edit profile page crash for suspended-then-unsuspended users ([ThibG](https://github.com/tootsuite/mastodon/pull/10178))
|
||||
|
||||
## [2.7.3] - 2019-02-23
|
||||
### Added
|
||||
|
||||
- Add domain filter to the admin federation page ([ThibG](https://github.com/tootsuite/mastodon/pull/10071))
|
||||
- Add quick link from admin account view to block/unblock instance ([ThibG](https://github.com/tootsuite/mastodon/pull/10073))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix video player width not being updated to fit container width ([ThibG](https://github.com/tootsuite/mastodon/pull/10069))
|
||||
- Fix domain filter being shown in admin page when local filter is active ([ThibG](https://github.com/tootsuite/mastodon/pull/10074))
|
||||
- Fix crash when conversations have no valid participants ([ThibG](https://github.com/tootsuite/mastodon/pull/10078))
|
||||
- Fix error when performing admin actions on no statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10094))
|
||||
|
||||
### Changed
|
||||
|
||||
- Change custom emojis to randomize stored file name ([hinaloe](https://github.com/tootsuite/mastodon/pull/10090))
|
||||
|
||||
## [2.7.2] - 2019-02-17
|
||||
### Added
|
||||
|
||||
- Add support for IPv6 in e-mail validation ([zoc](https://github.com/tootsuite/mastodon/pull/10009))
|
||||
- Add record of IP address used for signing up ([ThibG](https://github.com/tootsuite/mastodon/pull/10026))
|
||||
- Add tight rate-limit for API deletions (30 per 30 minutes) ([Gargron](https://github.com/tootsuite/mastodon/pull/10042))
|
||||
- Add support for embedded `Announce` objects attributed to the same actor ([ThibG](https://github.com/tootsuite/mastodon/pull/9998), [Gargron](https://github.com/tootsuite/mastodon/pull/10065))
|
||||
- Add spam filter for `Create` and `Announce` activities ([Gargron](https://github.com/tootsuite/mastodon/pull/10005), [Gargron](https://github.com/tootsuite/mastodon/pull/10041), [Gargron](https://github.com/tootsuite/mastodon/pull/10062))
|
||||
- Add `registrations` attribute to `GET /api/v1/instance` ([Gargron](https://github.com/tootsuite/mastodon/pull/10060))
|
||||
- Add `vapid_key` to `POST /api/v1/apps` and `GET /api/v1/apps/verify_credentials` ([Gargron](https://github.com/tootsuite/mastodon/pull/10058))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix link color and add link underlines in high-contrast theme ([Gargron](https://github.com/tootsuite/mastodon/pull/9949), [Gargron](https://github.com/tootsuite/mastodon/pull/10028))
|
||||
- Fix unicode characters in URLs not being linkified ([JMendyk](https://github.com/tootsuite/mastodon/pull/8447), [hinaloe](https://github.com/tootsuite/mastodon/pull/9991))
|
||||
- Fix URLs linkifier grabbing ending quotation as part of the link ([Gargron](https://github.com/tootsuite/mastodon/pull/9997))
|
||||
- Fix authorized applications page design ([rinsuki](https://github.com/tootsuite/mastodon/pull/9969))
|
||||
- Fix custom emojis not showing up in share page emoji picker ([rinsuki](https://github.com/tootsuite/mastodon/pull/9970))
|
||||
- Fix too liberal application of whitespace in toots ([trwnh](https://github.com/tootsuite/mastodon/pull/9968))
|
||||
- Fix misleading e-mail hint being displayed in admin view ([ThibG](https://github.com/tootsuite/mastodon/pull/9973))
|
||||
- Fix tombstones not being cleared out ([abcang](https://github.com/tootsuite/mastodon/pull/9978))
|
||||
- Fix some timeline jumps ([ThibG](https://github.com/tootsuite/mastodon/pull/9982), [ThibG](https://github.com/tootsuite/mastodon/pull/10001), [rinsuki](https://github.com/tootsuite/mastodon/pull/10046))
|
||||
- Fix content warning input taking keyboard focus even when hidden ([hinaloe](https://github.com/tootsuite/mastodon/pull/10017))
|
||||
- Fix hashtags select styling in default and high-contrast themes ([Gargron](https://github.com/tootsuite/mastodon/pull/10029))
|
||||
- Fix style regressions on landing page ([Gargron](https://github.com/tootsuite/mastodon/pull/10030))
|
||||
- Fix hashtag column not subscribing to stream on mount ([Gargron](https://github.com/tootsuite/mastodon/pull/10040))
|
||||
- Fix relay enabling/disabling not resetting inbox availability status ([Gargron](https://github.com/tootsuite/mastodon/pull/10048))
|
||||
- Fix mutes, blocks, domain blocks and follow requests not paginating ([Gargron](https://github.com/tootsuite/mastodon/pull/10057))
|
||||
- Fix crash on public hashtag pages when streaming fails ([ThibG](https://github.com/tootsuite/mastodon/pull/10061))
|
||||
|
||||
### Changed
|
||||
|
||||
- Change icon for unlisted visibility level ([clarcharr](https://github.com/tootsuite/mastodon/pull/9952))
|
||||
- Change queue of actor deletes from push to pull for non-follower recipients ([ThibG](https://github.com/tootsuite/mastodon/pull/10016))
|
||||
- Change robots.txt to exclude media proxy URLs ([nightpool](https://github.com/tootsuite/mastodon/pull/10038))
|
||||
- Change upload description input to allow line breaks ([BenLubar](https://github.com/tootsuite/mastodon/pull/10036))
|
||||
- Change `dist/mastodon-streaming.service` to recommend running node without intermediary npm command ([nolanlawson](https://github.com/tootsuite/mastodon/pull/10032))
|
||||
- Change conversations to always show names of other participants ([Gargron](https://github.com/tootsuite/mastodon/pull/10047))
|
||||
- Change buttons on timeline preview to open the interaction dialog ([Gargron](https://github.com/tootsuite/mastodon/pull/10054))
|
||||
- Change error graphic to hover-to-play ([Gargron](https://github.com/tootsuite/mastodon/pull/10055))
|
||||
|
||||
## [2.7.1] - 2019-01-28
|
||||
### Fixed
|
||||
|
||||
- Fix SSO authentication not working due to missing agreement boolean ([Gargron](https://github.com/tootsuite/mastodon/pull/9915))
|
||||
- Fix slow fallback of CopyAccountStats migration setting stats to 0 ([Gargron](https://github.com/tootsuite/mastodon/pull/9930))
|
||||
- Fix wrong command in migration error message ([angristan](https://github.com/tootsuite/mastodon/pull/9877))
|
||||
- Fix initial value of volume slider in video player and handle volume changes ([ThibG](https://github.com/tootsuite/mastodon/pull/9929))
|
||||
- Fix missing hotkeys for notifications ([ThibG](https://github.com/tootsuite/mastodon/pull/9927))
|
||||
- Fix being able to attach unattached media created by other users ([ThibG](https://github.com/tootsuite/mastodon/pull/9921))
|
||||
- Fix unrescued SSL error during link verification ([renatolond](https://github.com/tootsuite/mastodon/pull/9914))
|
||||
- Fix Firefox scrollbar color regression ([trwnh](https://github.com/tootsuite/mastodon/pull/9908))
|
||||
- Fix scheduled status with media immediately creating a status ([ThibG](https://github.com/tootsuite/mastodon/pull/9894))
|
||||
- Fix missing strong style for landing page description ([Kjwon15](https://github.com/tootsuite/mastodon/pull/9892))
|
||||
|
||||
## [2.7.0] - 2019-01-20
|
||||
### Added
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
Contributing
|
||||
============
|
||||
|
||||
Thank you for considering contributing to Mastodon 🐘
|
||||
Thank you for considering contributing to Mastodon 🐘
|
||||
|
||||
You can contribute in the following ways:
|
||||
|
||||
@ -10,15 +10,17 @@ You can contribute in the following ways:
|
||||
- Contributing code to Mastodon by fixing bugs or implementing features
|
||||
- Improving the documentation
|
||||
|
||||
If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
|
||||
|
||||
## Bug reports
|
||||
|
||||
Bug reports and feature suggestions can be submitted to [GitHub Issues](https://github.com/tootsuite/mastodon/issues). Please make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected in the past using the search function. Please also use descriptive, concise titles.
|
||||
|
||||
## Translations
|
||||
|
||||
You can submit translations via [Weblate](https://weblate.joinmastodon.org/). They are periodically merged into the codebase.
|
||||
You can submit translations via [Crowdin](https://crowdin.com/project/mastodon). They are periodically merged into the codebase.
|
||||
|
||||
[](https://weblate.joinmastodon.org/)
|
||||
[][crowdin]
|
||||
|
||||
## Pull requests
|
||||
|
||||
|
191
Dockerfile
191
Dockerfile
@ -1,90 +1,127 @@
|
||||
FROM node:8.15-alpine as node
|
||||
FROM ruby:2.6-alpine3.8
|
||||
FROM ubuntu:18.04 as build-dep
|
||||
|
||||
LABEL maintainer="https://github.com/tootsuite/mastodon" \
|
||||
description="Your self-hosted, globally interconnected microblogging community"
|
||||
# Use bash for the shell
|
||||
SHELL ["bash", "-c"]
|
||||
|
||||
# Install Node
|
||||
ENV NODE_VER="8.15.0"
|
||||
RUN echo "Etc/UTC" > /etc/localtime && \
|
||||
apt update && \
|
||||
apt -y install wget make gcc g++ python && \
|
||||
cd ~ && \
|
||||
wget https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER.tar.gz && \
|
||||
tar xf node-v$NODE_VER.tar.gz && \
|
||||
cd node-v$NODE_VER && \
|
||||
./configure --prefix=/opt/node && \
|
||||
make -j$(nproc) > /dev/null && \
|
||||
make install
|
||||
|
||||
# Install jemalloc
|
||||
ENV JE_VER="5.1.0"
|
||||
RUN apt update && \
|
||||
apt -y install autoconf && \
|
||||
cd ~ && \
|
||||
wget https://github.com/jemalloc/jemalloc/archive/$JE_VER.tar.gz && \
|
||||
tar xf $JE_VER.tar.gz && \
|
||||
cd jemalloc-$JE_VER && \
|
||||
./autogen.sh && \
|
||||
./configure --prefix=/opt/jemalloc && \
|
||||
make -j$(nproc) > /dev/null && \
|
||||
make install_bin install_include install_lib
|
||||
|
||||
# Install ruby
|
||||
ENV RUBY_VER="2.6.1"
|
||||
ENV CPPFLAGS="-I/opt/jemalloc/include"
|
||||
ENV LDFLAGS="-L/opt/jemalloc/lib/"
|
||||
RUN apt update && \
|
||||
apt -y install build-essential \
|
||||
bison libyaml-dev libgdbm-dev libreadline-dev \
|
||||
libncurses5-dev libffi-dev zlib1g-dev libssl-dev && \
|
||||
cd ~ && \
|
||||
wget https://cache.ruby-lang.org/pub/ruby/${RUBY_VER%.*}/ruby-$RUBY_VER.tar.gz && \
|
||||
tar xf ruby-$RUBY_VER.tar.gz && \
|
||||
cd ruby-$RUBY_VER && \
|
||||
./configure --prefix=/opt/ruby \
|
||||
--with-jemalloc \
|
||||
--with-shared \
|
||||
--disable-install-doc && \
|
||||
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
|
||||
make -j$(nproc) > /dev/null && \
|
||||
make install
|
||||
|
||||
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin"
|
||||
|
||||
RUN npm install -g yarn && \
|
||||
gem install bundler && \
|
||||
apt update && \
|
||||
apt -y install git libicu-dev libidn11-dev \
|
||||
libpq-dev libprotobuf-dev protobuf-compiler
|
||||
|
||||
COPY Gemfile* package.json yarn.lock /opt/mastodon/
|
||||
|
||||
RUN cd /opt/mastodon && \
|
||||
bundle install -j$(nproc) --deployment --without development test && \
|
||||
yarn install --pure-lockfile
|
||||
|
||||
FROM ubuntu:18.04
|
||||
|
||||
# Copy over all the langs needed for runtime
|
||||
COPY --from=build-dep /opt/node /opt/node
|
||||
COPY --from=build-dep /opt/ruby /opt/ruby
|
||||
COPY --from=build-dep /opt/jemalloc /opt/jemalloc
|
||||
|
||||
# Add more PATHs to the PATH
|
||||
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
|
||||
|
||||
# Create the mastodon user
|
||||
ARG UID=991
|
||||
ARG GID=991
|
||||
RUN apt update && \
|
||||
echo "Etc/UTC" > /etc/localtime && \
|
||||
ln -s /opt/jemalloc/lib/* /usr/lib/ && \
|
||||
apt install -y whois wget && \
|
||||
addgroup --gid $GID mastodon && \
|
||||
useradd -m -u $UID -g $GID -d /opt/mastodon mastodon && \
|
||||
echo "mastodon:`head /dev/urandom | tr -dc A-Za-z0-9 | head -c 24 | mkpasswd -s -m sha-256`" | chpasswd
|
||||
|
||||
ENV PATH=/mastodon/bin:$PATH \
|
||||
RAILS_SERVE_STATIC_FILES=true \
|
||||
RAILS_ENV=production \
|
||||
NODE_ENV=production
|
||||
# Install mastodon runtime deps
|
||||
RUN apt -y --no-install-recommends install \
|
||||
libssl1.1 libpq5 imagemagick ffmpeg \
|
||||
libicu60 libprotobuf10 libidn11 libyaml-0-2 \
|
||||
file ca-certificates tzdata libreadline7 && \
|
||||
apt -y install gcc && \
|
||||
ln -s /opt/mastodon /mastodon && \
|
||||
gem install bundler && \
|
||||
rm -rf /var/cache && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG LIBICONV_VERSION=1.15
|
||||
ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178
|
||||
# Add tini
|
||||
ENV TINI_VERSION="0.18.0"
|
||||
ENV TINI_SUM="12d20136605531b09a2c2dac02ccee85e1b874eb322ef6baf7561cd93f93c855"
|
||||
ADD https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini /tini
|
||||
RUN echo "$TINI_SUM tini" | sha256sum -c -
|
||||
RUN chmod +x /tini
|
||||
|
||||
EXPOSE 3000 4000
|
||||
# Copy over mastodon source, and dependencies from building, and set permissions
|
||||
COPY --chown=mastodon:mastodon . /opt/mastodon
|
||||
COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon
|
||||
|
||||
WORKDIR /mastodon
|
||||
# Run mastodon services in prod mode
|
||||
ENV RAILS_ENV="production"
|
||||
ENV NODE_ENV="production"
|
||||
|
||||
COPY --from=node /usr/local/bin/node /usr/local/bin/node
|
||||
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
|
||||
COPY --from=node /usr/local/bin/npm /usr/local/bin/npm
|
||||
COPY --from=node /opt/yarn-* /opt/yarn
|
||||
|
||||
RUN apk -U upgrade \
|
||||
&& apk add -t build-dependencies \
|
||||
build-base \
|
||||
icu-dev \
|
||||
libidn-dev \
|
||||
libressl \
|
||||
libtool \
|
||||
libxml2-dev \
|
||||
libxslt-dev \
|
||||
postgresql-dev \
|
||||
protobuf-dev \
|
||||
python \
|
||||
&& apk add \
|
||||
ca-certificates \
|
||||
ffmpeg \
|
||||
file \
|
||||
git \
|
||||
icu-libs \
|
||||
imagemagick \
|
||||
libidn \
|
||||
libpq \
|
||||
libxml2 \
|
||||
libxslt \
|
||||
protobuf \
|
||||
tini \
|
||||
tzdata \
|
||||
&& update-ca-certificates \
|
||||
&& ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
|
||||
&& ln -s /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg \
|
||||
&& mkdir -p /tmp/src /opt \
|
||||
&& wget -O libiconv.tar.gz "https://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
|
||||
&& echo "$LIBICONV_DOWNLOAD_SHA256 *libiconv.tar.gz" | sha256sum -c - \
|
||||
&& tar -xzf libiconv.tar.gz -C /tmp/src \
|
||||
&& rm libiconv.tar.gz \
|
||||
&& cd /tmp/src/libiconv-$LIBICONV_VERSION \
|
||||
&& ./configure --prefix=/usr/local \
|
||||
&& make -j$(getconf _NPROCESSORS_ONLN)\
|
||||
&& make install \
|
||||
&& libtool --finish /usr/local/lib \
|
||||
&& cd /mastodon \
|
||||
&& rm -rf /tmp/* /var/cache/apk/*
|
||||
|
||||
COPY Gemfile Gemfile.lock package.json yarn.lock .yarnclean /mastodon/
|
||||
|
||||
RUN bundle config build.nokogiri --use-system-libraries --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \
|
||||
&& bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \
|
||||
&& yarn install --pure-lockfile --ignore-engines \
|
||||
&& yarn cache clean
|
||||
|
||||
RUN addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon \
|
||||
&& mkdir -p /mastodon/public/system /mastodon/public/assets /mastodon/public/packs \
|
||||
&& chown -R mastodon:mastodon /mastodon/public
|
||||
|
||||
COPY . /mastodon
|
||||
|
||||
RUN chown -R mastodon:mastodon /mastodon
|
||||
|
||||
VOLUME /mastodon/public/system
|
||||
# Tell rails to serve static files
|
||||
ENV RAILS_SERVE_STATIC_FILES="true"
|
||||
ENV BIND="0.0.0.0"
|
||||
|
||||
# Set the run user
|
||||
USER mastodon
|
||||
|
||||
RUN OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder bundle exec rails assets:precompile
|
||||
# Precompile assets
|
||||
RUN cd ~ && \
|
||||
OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile && \
|
||||
yarn cache clean
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
# Set the work dir and the container entry point
|
||||
WORKDIR /opt/mastodon
|
||||
ENTRYPOINT ["/tini", "--"]
|
||||
|
68
Gemfile
68
Gemfile
@ -5,32 +5,33 @@ ruby '>= 2.4.0', '< 2.7.0'
|
||||
|
||||
gem 'pkg-config', '~> 1.3'
|
||||
|
||||
gem 'puma', '~> 3.12'
|
||||
gem 'rails', '~> 5.2.2'
|
||||
gem 'puma', '~> 4.0'
|
||||
gem 'rails', '~> 5.2.3'
|
||||
gem 'thor', '~> 0.20'
|
||||
|
||||
gem 'hamlit-rails', '~> 0.2'
|
||||
gem 'pg', '~> 1.1'
|
||||
gem 'makara', '~> 0.4'
|
||||
gem 'pghero', '~> 2.2'
|
||||
gem 'dotenv-rails', '~> 2.6'
|
||||
gem 'dotenv-rails', '~> 2.7'
|
||||
|
||||
gem 'aws-sdk-s3', '~> 1.30', require: false
|
||||
gem 'aws-sdk-s3', '~> 1.46', require: false
|
||||
gem 'fog-core', '<= 2.1.0'
|
||||
gem 'fog-openstack', '~> 0.3', require: false
|
||||
gem 'paperclip', '~> 6.0'
|
||||
gem 'paperclip-av-transcoder', '~> 0.6'
|
||||
gem 'streamio-ffmpeg', '~> 3.0'
|
||||
gem 'blurhash', '~> 0.1'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.10'
|
||||
gem 'addressable', '~> 2.6'
|
||||
gem 'bootsnap', '~> 1.3', require: false
|
||||
gem 'bootsnap', '~> 1.4', require: false
|
||||
gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.6'
|
||||
gem 'iso-639'
|
||||
gem 'chewy', '~> 5.0'
|
||||
gem 'cld3', '~> 3.2.3'
|
||||
gem 'devise', '~> 4.5'
|
||||
gem 'cld3', '~> 3.2.4'
|
||||
gem 'devise', '~> 4.6'
|
||||
gem 'devise-two-factor', '~> 3.0'
|
||||
|
||||
group :pam_authentication, optional: true do
|
||||
@ -42,7 +43,7 @@ gem 'omniauth-cas', '~> 1.1'
|
||||
gem 'omniauth-saml', '~> 1.10'
|
||||
gem 'omniauth', '~> 1.9'
|
||||
|
||||
gem 'doorkeeper', '~> 5.0'
|
||||
gem 'doorkeeper', '~> 5.1'
|
||||
gem 'fast_blank', '~> 1.0'
|
||||
gem 'fastimage'
|
||||
gem 'goldfinger', '~> 2.1'
|
||||
@ -51,21 +52,23 @@ gem 'redis-namespace', '~> 1.5'
|
||||
gem 'htmlentities', '~> 4.3'
|
||||
gem 'http', '~> 3.3'
|
||||
gem 'http_accept_language', '~> 2.1'
|
||||
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2'
|
||||
gem 'httplog', '~> 1.2'
|
||||
gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true
|
||||
gem 'httplog', '~> 1.3'
|
||||
gem 'idn-ruby', require: 'idn'
|
||||
gem 'kaminari', '~> 1.1'
|
||||
gem 'link_header', '~> 0.0'
|
||||
gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
|
||||
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
|
||||
gem 'nokogiri', '~> 1.10'
|
||||
gem 'nsa', '~> 0.2'
|
||||
gem 'oj', '~> 3.7'
|
||||
gem 'oj', '~> 3.8'
|
||||
gem 'ostatus2', '~> 2.0'
|
||||
gem 'ox', '~> 2.10'
|
||||
gem 'ox', '~> 2.11'
|
||||
gem 'parslet'
|
||||
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c'
|
||||
gem 'pundit', '~> 2.0'
|
||||
gem 'premailer-rails'
|
||||
gem 'rack-attack', '~> 5.4'
|
||||
gem 'rack-attack', '~> 6.1'
|
||||
gem 'rack-cors', '~> 1.0', require: 'rack/cors'
|
||||
gem 'rails-i18n', '~> 5.1'
|
||||
gem 'rails-settings-cached', '~> 0.6'
|
||||
@ -81,23 +84,23 @@ gem 'simple-navigation', '~> 4.0'
|
||||
gem 'simple_form', '~> 4.1'
|
||||
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
||||
gem 'stoplight', '~> 2.1.3'
|
||||
gem 'strong_migrations', '~> 0.3'
|
||||
gem 'strong_migrations', '~> 0.4'
|
||||
gem 'tty-command', '~> 0.8', require: false
|
||||
gem 'tty-prompt', '~> 0.18', require: false
|
||||
gem 'tty-prompt', '~> 0.19', require: false
|
||||
gem 'twitter-text', '~> 1.14'
|
||||
gem 'tzinfo-data', '~> 1.2018'
|
||||
gem 'webpacker', '~> 3.5'
|
||||
gem 'tzinfo-data', '~> 1.2019'
|
||||
gem 'webpacker', '~> 4.0'
|
||||
gem 'webpush'
|
||||
|
||||
gem 'json-ld', '~> 3.0'
|
||||
gem 'json-ld', git: 'https://github.com/ruby-rdf/json-ld.git', ref: '345b7a5733308af827e8491d284dbafa9128d7a2'
|
||||
gem 'json-ld-preloaded', '~> 3.0'
|
||||
gem 'rdf-normalize', '~> 0.3'
|
||||
|
||||
group :development, :test do
|
||||
gem 'fabrication', '~> 2.20'
|
||||
gem 'fuubar', '~> 2.3'
|
||||
gem 'fuubar', '~> 2.4'
|
||||
gem 'i18n-tasks', '~> 0.9', require: false
|
||||
gem 'pry-byebug', '~> 3.6'
|
||||
gem 'pry-byebug', '~> 3.7'
|
||||
gem 'pry-rails', '~> 0.3'
|
||||
gem 'rspec-rails', '~> 3.8'
|
||||
end
|
||||
@ -107,30 +110,30 @@ group :production, :test do
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'capybara', '~> 3.12'
|
||||
gem 'capybara', '~> 3.28'
|
||||
gem 'climate_control', '~> 0.2'
|
||||
gem 'faker', '~> 1.9'
|
||||
gem 'microformats', '~> 4.0'
|
||||
gem 'faker', '~> 2.1'
|
||||
gem 'microformats', '~> 4.1'
|
||||
gem 'rails-controller-testing', '~> 1.0'
|
||||
gem 'rspec-sidekiq', '~> 3.0'
|
||||
gem 'simplecov', '~> 0.16', require: false
|
||||
gem 'webmock', '~> 3.5'
|
||||
gem 'parallel_tests', '~> 2.27'
|
||||
gem 'simplecov', '~> 0.17', require: false
|
||||
gem 'webmock', '~> 3.6'
|
||||
gem 'parallel_tests', '~> 2.29'
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'active_record_query_trace', '~> 1.5'
|
||||
gem 'active_record_query_trace', '~> 1.6'
|
||||
gem 'annotate', '~> 2.7'
|
||||
gem 'better_errors', '~> 2.5'
|
||||
gem 'binding_of_caller', '~> 0.7'
|
||||
gem 'bullet', '~> 5.9'
|
||||
gem 'bullet', '~> 6.0'
|
||||
gem 'letter_opener', '~> 1.7'
|
||||
gem 'letter_opener_web', '~> 1.3'
|
||||
gem 'memory_profiler'
|
||||
gem 'rubocop', '~> 0.63', require: false
|
||||
gem 'brakeman', '~> 4.4', require: false
|
||||
gem 'rubocop', '~> 0.74', require: false
|
||||
gem 'rubocop-rails', '~> 2.2', require: false
|
||||
gem 'brakeman', '~> 4.6', require: false
|
||||
gem 'bundler-audit', '~> 0.6', require: false
|
||||
gem 'scss_lint', '~> 0.57', require: false
|
||||
|
||||
gem 'capistrano', '~> 3.11'
|
||||
gem 'capistrano-rails', '~> 1.4'
|
||||
@ -142,8 +145,9 @@ group :development do
|
||||
end
|
||||
|
||||
group :production do
|
||||
gem 'lograge', '~> 0.10'
|
||||
gem 'lograge', '~> 0.11'
|
||||
gem 'redis-rails', '~> 5.0'
|
||||
end
|
||||
|
||||
gem 'concurrent-ruby', require: false
|
||||
gem 'connection_pool', require: false
|
||||
|
431
Gemfile.lock
431
Gemfile.lock
@ -5,59 +5,80 @@ GIT
|
||||
specs:
|
||||
posix-spawn (0.3.13)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/ruby-rdf/json-ld.git
|
||||
revision: 345b7a5733308af827e8491d284dbafa9128d7a2
|
||||
ref: 345b7a5733308af827e8491d284dbafa9128d7a2
|
||||
specs:
|
||||
json-ld (3.0.2)
|
||||
htmlentities (~> 4.3)
|
||||
json-canonicalization (~> 0.1)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
multi_json (~> 1.13)
|
||||
rack (>= 1.6, < 3.0)
|
||||
rdf (~> 3.0, >= 3.0.8)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/tmm1/http_parser.rb
|
||||
revision: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
|
||||
ref: 54b17ba8c7d8d20a16dfc65d1775241833219cf2
|
||||
submodules: true
|
||||
specs:
|
||||
http_parser.rb (0.6.1)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/witgo/nilsimsa
|
||||
revision: fd184883048b922b176939f851338d0a4971a532
|
||||
ref: fd184883048b922b176939f851338d0a4971a532
|
||||
specs:
|
||||
nilsimsa (1.1.2)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
actioncable (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailer (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
activejob (= 5.2.2)
|
||||
actionmailer (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activejob (= 5.2.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
actionpack (5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
rack (~> 2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
actionview (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
active_model_serializers (0.10.8)
|
||||
actionpack (>= 4.1, < 6)
|
||||
activemodel (>= 4.1, < 6)
|
||||
active_model_serializers (0.10.10)
|
||||
actionpack (>= 4.1, < 6.1)
|
||||
activemodel (>= 4.1, < 6.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
active_record_query_trace (1.5.4)
|
||||
activejob (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
active_record_query_trace (1.6.2)
|
||||
activejob (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
activerecord (5.2.2)
|
||||
activemodel (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
activemodel (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
activerecord (5.2.3)
|
||||
activemodel (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
arel (>= 9.0)
|
||||
activestorage (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
activerecord (= 5.2.2)
|
||||
activestorage (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
activerecord (= 5.2.3)
|
||||
marcel (~> 0.3.1)
|
||||
activesupport (5.2.2)
|
||||
activesupport (5.2.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
@ -66,8 +87,8 @@ GEM
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
airbrussh (1.3.0)
|
||||
sshkit (>= 1.6.1, != 1.7.0)
|
||||
annotate (2.7.4)
|
||||
activerecord (>= 3.2, < 6.0)
|
||||
annotate (2.7.5)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
rake (>= 10.4, < 13.0)
|
||||
arel (9.0.0)
|
||||
ast (2.4.0)
|
||||
@ -75,41 +96,44 @@ GEM
|
||||
encryptor (~> 3.0.0)
|
||||
av (0.9.0)
|
||||
cocaine (~> 0.5.3)
|
||||
aws-eventstream (1.0.1)
|
||||
aws-partitions (1.131.0)
|
||||
aws-sdk-core (3.45.0)
|
||||
aws-eventstream (~> 1.0)
|
||||
aws-eventstream (1.0.3)
|
||||
aws-partitions (1.193.0)
|
||||
aws-sdk-core (3.61.1)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
aws-partitions (~> 1.0)
|
||||
aws-sigv4 (~> 1.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.13.0)
|
||||
aws-sdk-core (~> 3, >= 3.39.0)
|
||||
aws-sigv4 (~> 1.0)
|
||||
aws-sdk-s3 (1.30.1)
|
||||
aws-sdk-core (~> 3, >= 3.39.0)
|
||||
aws-sdk-kms (1.24.0)
|
||||
aws-sdk-core (~> 3, >= 3.61.1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.46.0)
|
||||
aws-sdk-core (~> 3, >= 3.61.1)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.0)
|
||||
aws-sigv4 (1.0.3)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.1.0)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
bcrypt (3.1.12)
|
||||
benchmark-ips (2.7.2)
|
||||
better_errors (2.5.0)
|
||||
better_errors (2.5.1)
|
||||
coderay (>= 1.0.0)
|
||||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.8.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.3.2)
|
||||
blurhash (0.1.3)
|
||||
ffi (~> 1.10.0)
|
||||
bootsnap (1.4.4)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (4.4.0)
|
||||
browser (2.5.3)
|
||||
brakeman (4.6.1)
|
||||
browser (2.6.1)
|
||||
builder (3.2.3)
|
||||
bullet (5.9.0)
|
||||
bullet (6.0.1)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
bundler-audit (0.6.1)
|
||||
bundler (>= 1.2.0, < 3)
|
||||
thor (~> 0.18)
|
||||
byebug (10.0.2)
|
||||
byebug (11.0.0)
|
||||
capistrano (3.11.0)
|
||||
airbrussh (>= 1.0.0)
|
||||
i18n
|
||||
@ -126,13 +150,13 @@ GEM
|
||||
sshkit (~> 1.3)
|
||||
capistrano-yarn (2.0.2)
|
||||
capistrano (~> 3.0)
|
||||
capybara (3.12.0)
|
||||
capybara (3.28.0)
|
||||
addressable
|
||||
mini_mime (>= 0.1.3)
|
||||
nokogiri (~> 1.8)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (~> 1.2)
|
||||
regexp_parser (~> 1.5)
|
||||
xpath (~> 3.2)
|
||||
case_transform (0.2)
|
||||
activesupport
|
||||
@ -142,21 +166,21 @@ GEM
|
||||
elasticsearch (>= 2.0.0)
|
||||
elasticsearch-dsl
|
||||
chunky_png (1.3.10)
|
||||
cld3 (3.2.3)
|
||||
ffi (>= 1.1.0, < 1.10.0)
|
||||
cld3 (3.2.4)
|
||||
ffi (>= 1.1.0, < 1.11.0)
|
||||
climate_control (0.2.0)
|
||||
cocaine (0.5.8)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
coderay (1.1.2)
|
||||
concurrent-ruby (1.1.4)
|
||||
concurrent-ruby (1.1.5)
|
||||
connection_pool (2.2.2)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.4)
|
||||
css_parser (1.6.0)
|
||||
css_parser (1.7.0)
|
||||
addressable
|
||||
debug_inspector (0.0.3)
|
||||
derailed_benchmarks (1.3.5)
|
||||
derailed_benchmarks (1.3.6)
|
||||
benchmark-ips (~> 2)
|
||||
get_process_mem (~> 0)
|
||||
heapy (~> 0)
|
||||
@ -164,7 +188,7 @@ GEM
|
||||
rack (>= 1)
|
||||
rake (> 10, < 13)
|
||||
thor (~> 0.19)
|
||||
devise (4.5.0)
|
||||
devise (4.6.2)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0, < 6.0)
|
||||
@ -180,15 +204,15 @@ GEM
|
||||
devise (>= 4.0.0)
|
||||
rpam2 (~> 4.0)
|
||||
diff-lcs (1.3)
|
||||
docile (1.3.0)
|
||||
docile (1.3.2)
|
||||
domain_name (0.5.20180417)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
doorkeeper (5.0.2)
|
||||
railties (>= 4.2)
|
||||
dotenv (2.6.0)
|
||||
dotenv-rails (2.6.0)
|
||||
dotenv (= 2.6.0)
|
||||
railties (>= 3.2, < 6.0)
|
||||
doorkeeper (5.1.0)
|
||||
railties (>= 5)
|
||||
dotenv (2.7.5)
|
||||
dotenv-rails (2.7.5)
|
||||
dotenv (= 2.7.5)
|
||||
railties (>= 3.2, < 6.1)
|
||||
elasticsearch (6.0.2)
|
||||
elasticsearch-api (= 6.0.2)
|
||||
elasticsearch-transport (= 6.0.2)
|
||||
@ -204,14 +228,14 @@ GEM
|
||||
et-orbi (1.1.6)
|
||||
tzinfo
|
||||
excon (0.62.0)
|
||||
fabrication (2.20.1)
|
||||
faker (1.9.1)
|
||||
fabrication (2.20.2)
|
||||
faker (2.1.0)
|
||||
i18n (>= 0.7)
|
||||
faraday (0.15.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
fast_blank (1.0.0)
|
||||
fastimage (2.1.5)
|
||||
ffi (1.9.25)
|
||||
ffi (1.10.0)
|
||||
fog-core (2.1.0)
|
||||
builder
|
||||
excon (~> 0.58)
|
||||
@ -228,32 +252,32 @@ GEM
|
||||
fugit (1.1.6)
|
||||
et-orbi (~> 1.1, >= 1.1.6)
|
||||
raabro (~> 1.1)
|
||||
fuubar (2.3.2)
|
||||
fuubar (2.4.1)
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
get_process_mem (0.2.3)
|
||||
globalid (0.4.1)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
goldfinger (2.1.0)
|
||||
addressable (~> 2.5)
|
||||
http (~> 3.0)
|
||||
nokogiri (~> 1.8)
|
||||
oj (~> 3.0)
|
||||
hamlit (2.8.8)
|
||||
hamlit (2.9.3)
|
||||
temple (>= 0.8.0)
|
||||
thor
|
||||
tilt
|
||||
hamlit-rails (0.2.0)
|
||||
hamlit-rails (0.2.3)
|
||||
actionpack (>= 4.0.1)
|
||||
activesupport (>= 4.0.1)
|
||||
hamlit (>= 1.2.0)
|
||||
railties (>= 4.0.1)
|
||||
hamster (3.0.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
hashdiff (0.3.7)
|
||||
hashdiff (1.0.0)
|
||||
hashie (3.6.0)
|
||||
heapy (0.1.4)
|
||||
highline (2.0.0)
|
||||
highline (2.0.1)
|
||||
hiredis (0.6.3)
|
||||
hkdf (0.3.0)
|
||||
htmlentities (4.3.4)
|
||||
@ -266,12 +290,12 @@ GEM
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (2.1.1)
|
||||
http_accept_language (2.1.1)
|
||||
httplog (1.2.0)
|
||||
httplog (1.3.2)
|
||||
rack (>= 1.0)
|
||||
rainbow (>= 2.0.0)
|
||||
i18n (1.5.2)
|
||||
i18n (1.6.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-tasks (0.9.28)
|
||||
i18n-tasks (0.9.29)
|
||||
activesupport (>= 4.0.2)
|
||||
ast (>= 2.1.0)
|
||||
erubi
|
||||
@ -284,17 +308,15 @@ GEM
|
||||
idn-ruby (0.1.0)
|
||||
ipaddress (0.8.3)
|
||||
iso-639 (0.2.8)
|
||||
jaro_winkler (1.5.2)
|
||||
jaro_winkler (1.5.3)
|
||||
jmespath (1.4.0)
|
||||
json (2.1.0)
|
||||
json-ld (3.0.2)
|
||||
multi_json (~> 1.12)
|
||||
rdf (>= 2.2.8, < 4.0)
|
||||
json-ld-preloaded (3.0.2)
|
||||
json (2.2.0)
|
||||
json-canonicalization (0.1.0)
|
||||
json-ld-preloaded (3.0.3)
|
||||
json-ld (~> 3.0)
|
||||
multi_json (~> 1.12)
|
||||
rdf (~> 3.0)
|
||||
jsonapi-renderer (0.2.0)
|
||||
jsonapi-renderer (0.2.2)
|
||||
jwt (2.1.0)
|
||||
kaminari (1.1.1)
|
||||
activesupport (>= 4.1.0)
|
||||
@ -317,7 +339,7 @@ GEM
|
||||
letter_opener (~> 1.0)
|
||||
railties (>= 3.2)
|
||||
link_header (0.0.8)
|
||||
lograge (0.10.0)
|
||||
lograge (0.11.2)
|
||||
actionpack (>= 4)
|
||||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
@ -327,34 +349,34 @@ GEM
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
makara (0.4.0)
|
||||
makara (0.4.1)
|
||||
activerecord (>= 3.0.0)
|
||||
marcel (0.3.3)
|
||||
mimemagic (~> 0.3.2)
|
||||
mario-redis-lock (1.2.1)
|
||||
redis (>= 3.0.5)
|
||||
memory_profiler (0.9.12)
|
||||
memory_profiler (0.9.14)
|
||||
method_source (0.9.2)
|
||||
microformats (4.0.7)
|
||||
json
|
||||
nokogiri
|
||||
microformats (4.1.0)
|
||||
json (~> 2.1)
|
||||
nokogiri (~> 1.8, >= 1.8.3)
|
||||
mime-types (3.2.2)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2018.0812)
|
||||
mimemagic (0.3.2)
|
||||
mini_mime (1.0.1)
|
||||
mimemagic (0.3.3)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.11.3)
|
||||
msgpack (1.2.4)
|
||||
msgpack (1.2.10)
|
||||
multi_json (1.13.1)
|
||||
multipart-post (2.0.0)
|
||||
necromancer (0.4.0)
|
||||
necromancer (0.5.0)
|
||||
net-ldap (0.16.1)
|
||||
net-scp (1.2.1)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (5.0.2)
|
||||
nio4r (2.3.1)
|
||||
nokogiri (1.10.1)
|
||||
nokogiri (1.10.3)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
nokogumbo (2.0.0)
|
||||
nokogiri (~> 1.8, >= 1.8.4)
|
||||
@ -363,7 +385,7 @@ GEM
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sidekiq (>= 3.5)
|
||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||
oj (3.7.8)
|
||||
oj (3.8.1)
|
||||
omniauth (1.9.0)
|
||||
hashie (>= 3.4.6, < 3.7.0)
|
||||
rack (>= 1.6.2, < 3)
|
||||
@ -379,7 +401,7 @@ GEM
|
||||
addressable (~> 2.5)
|
||||
http (~> 3.0)
|
||||
nokogiri (~> 1.8)
|
||||
ox (2.10.0)
|
||||
ox (2.11.0)
|
||||
paperclip (6.0.0)
|
||||
activemodel (>= 4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
@ -389,62 +411,63 @@ GEM
|
||||
paperclip-av-transcoder (0.6.4)
|
||||
av (~> 0.9.0)
|
||||
paperclip (>= 2.5.2)
|
||||
parallel (1.13.0)
|
||||
parallel_tests (2.27.1)
|
||||
parallel (1.17.0)
|
||||
parallel_tests (2.29.1)
|
||||
parallel
|
||||
parser (2.6.0.0)
|
||||
parser (2.6.3.0)
|
||||
ast (~> 2.4.0)
|
||||
parslet (1.8.2)
|
||||
pastel (0.7.2)
|
||||
equatable (~> 0.5.0)
|
||||
tty-color (~> 0.4.0)
|
||||
pg (1.1.4)
|
||||
pghero (2.2.0)
|
||||
pghero (2.2.1)
|
||||
activerecord
|
||||
pkg-config (1.3.2)
|
||||
powerpack (0.1.2)
|
||||
pkg-config (1.3.7)
|
||||
premailer (1.11.1)
|
||||
addressable
|
||||
css_parser (>= 1.6.0)
|
||||
htmlentities (>= 4.0.0)
|
||||
premailer-rails (1.10.2)
|
||||
actionmailer (>= 3, < 6)
|
||||
premailer-rails (1.10.3)
|
||||
actionmailer (>= 3)
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
private_address_check (0.5.0)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
pry-byebug (3.6.0)
|
||||
byebug (~> 10.0)
|
||||
pry-byebug (3.7.0)
|
||||
byebug (~> 11.0)
|
||||
pry (~> 0.10)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (3.0.3)
|
||||
puma (3.12.0)
|
||||
public_suffix (3.1.1)
|
||||
puma (4.0.1)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.0.1)
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.1.6)
|
||||
rack (2.0.6)
|
||||
rack-attack (5.4.2)
|
||||
rack (2.0.7)
|
||||
rack-attack (6.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.0.2)
|
||||
rack-cors (1.0.3)
|
||||
rack-protection (2.0.5)
|
||||
rack
|
||||
rack-proxy (0.6.4)
|
||||
rack-proxy (0.6.5)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (5.2.2)
|
||||
actioncable (= 5.2.2)
|
||||
actionmailer (= 5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
activejob (= 5.2.2)
|
||||
activemodel (= 5.2.2)
|
||||
activerecord (= 5.2.2)
|
||||
activestorage (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
rails (5.2.3)
|
||||
actioncable (= 5.2.3)
|
||||
actionmailer (= 5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activejob (= 5.2.3)
|
||||
activemodel (= 5.2.3)
|
||||
activerecord (= 5.2.3)
|
||||
activestorage (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.2.2)
|
||||
railties (= 5.2.3)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.4)
|
||||
actionpack (>= 5.0.1.x)
|
||||
@ -453,30 +476,27 @@ GEM
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.0.4)
|
||||
rails-html-sanitizer (1.1.0)
|
||||
loofah (~> 2.2, >= 2.2.2)
|
||||
rails-i18n (5.1.2)
|
||||
rails-i18n (5.1.3)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 5.0, < 6)
|
||||
rails-settings-cached (0.6.6)
|
||||
rails (>= 4.2.0)
|
||||
railties (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
railties (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
rake (12.3.2)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.9.10)
|
||||
ffi (>= 0.5.0, < 2)
|
||||
rdf (3.0.9)
|
||||
rake (12.3.3)
|
||||
rdf (3.0.12)
|
||||
hamster (~> 3.0)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
rdf-normalize (0.3.3)
|
||||
rdf (>= 2.2, < 4.0)
|
||||
redis (4.1.0)
|
||||
redis (4.1.2)
|
||||
redis-actionpack (5.0.2)
|
||||
actionpack (>= 4.0, < 6)
|
||||
redis-rack (>= 1, < 3)
|
||||
@ -495,12 +515,12 @@ GEM
|
||||
redis-store (>= 1.2, < 2)
|
||||
redis-store (1.5.0)
|
||||
redis (>= 2.2, < 5)
|
||||
regexp_parser (1.3.0)
|
||||
regexp_parser (1.6.0)
|
||||
request_store (1.4.1)
|
||||
rack (>= 1.4)
|
||||
responders (2.4.0)
|
||||
actionpack (>= 4.2.0, < 5.3)
|
||||
railties (>= 4.2.0, < 5.3)
|
||||
responders (2.4.1)
|
||||
actionpack (>= 4.2.0, < 6.0)
|
||||
railties (>= 4.2.0, < 6.0)
|
||||
rotp (2.1.2)
|
||||
rpam2 (4.0.2)
|
||||
rqrcode (0.10.1)
|
||||
@ -525,33 +545,27 @@ GEM
|
||||
rspec-core (~> 3.0, >= 3.0.0)
|
||||
sidekiq (>= 2.4.0)
|
||||
rspec-support (3.8.0)
|
||||
rubocop (0.63.1)
|
||||
rubocop (0.74.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.5, != 2.5.1.1)
|
||||
powerpack (~> 0.1)
|
||||
parser (>= 2.6)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (~> 1.4.0)
|
||||
ruby-progressbar (1.10.0)
|
||||
unicode-display_width (>= 1.4.0, < 1.7)
|
||||
rubocop-rails (2.2.1)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby-saml (1.9.0)
|
||||
nokogiri (>= 1.5.10)
|
||||
rufus-scheduler (3.5.2)
|
||||
fugit (~> 1.1, >= 1.1.5)
|
||||
safe_yaml (1.0.4)
|
||||
safe_yaml (1.0.5)
|
||||
sanitize (5.0.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.8.0)
|
||||
nokogumbo (~> 2.0)
|
||||
sass (3.6.0)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
scss_lint (0.57.1)
|
||||
rake (>= 0.9, < 13)
|
||||
sass (~> 3.5, >= 3.5.5)
|
||||
sidekiq (5.2.5)
|
||||
sidekiq (5.2.7)
|
||||
connection_pool (~> 2.2, >= 2.2.2)
|
||||
rack (>= 1.5.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
@ -563,16 +577,16 @@ GEM
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 3)
|
||||
tilt (>= 1.4.0)
|
||||
sidekiq-unique-jobs (6.0.8)
|
||||
sidekiq-unique-jobs (6.0.13)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
sidekiq (>= 4.0, < 6.0)
|
||||
sidekiq (>= 4.0, < 7.0)
|
||||
thor (~> 0)
|
||||
simple-navigation (4.0.5)
|
||||
activesupport (>= 2.3.2)
|
||||
simple_form (4.1.0)
|
||||
actionpack (>= 5.0)
|
||||
activemodel (>= 5.0)
|
||||
simplecov (0.16.1)
|
||||
simplecov (0.17.0)
|
||||
docile (~> 1.1)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
@ -592,54 +606,51 @@ GEM
|
||||
stoplight (2.1.3)
|
||||
streamio-ffmpeg (3.0.2)
|
||||
multi_json (~> 1.8)
|
||||
strong_migrations (0.3.1)
|
||||
activerecord (>= 3.2.0)
|
||||
temple (0.8.0)
|
||||
strong_migrations (0.4.1)
|
||||
activerecord (>= 5)
|
||||
temple (0.8.1)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
terrapin (0.6.0)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
thor (0.20.3)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.8)
|
||||
timers (4.2.0)
|
||||
tilt (2.0.9)
|
||||
tty-color (0.4.3)
|
||||
tty-command (0.8.2)
|
||||
pastel (~> 0.7.0)
|
||||
tty-cursor (0.6.0)
|
||||
tty-prompt (0.18.1)
|
||||
necromancer (~> 0.4.0)
|
||||
tty-cursor (0.7.0)
|
||||
tty-prompt (0.19.0)
|
||||
necromancer (~> 0.5.0)
|
||||
pastel (~> 0.7.0)
|
||||
timers (~> 4.0)
|
||||
tty-cursor (~> 0.6.0)
|
||||
tty-reader (~> 0.5.0)
|
||||
tty-reader (0.5.0)
|
||||
tty-cursor (~> 0.6.0)
|
||||
tty-screen (~> 0.6.4)
|
||||
tty-reader (~> 0.6.0)
|
||||
tty-reader (0.6.0)
|
||||
tty-cursor (~> 0.7)
|
||||
tty-screen (~> 0.7)
|
||||
wisper (~> 2.0.0)
|
||||
tty-screen (0.6.5)
|
||||
tty-screen (0.7.0)
|
||||
twitter-text (1.14.7)
|
||||
unf (~> 0.1.0)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2018.9)
|
||||
tzinfo-data (1.2019.2)
|
||||
tzinfo (>= 1.0.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.5)
|
||||
unicode-display_width (1.4.1)
|
||||
unicode-display_width (1.6.0)
|
||||
uniform_notifier (1.12.1)
|
||||
warden (1.2.7)
|
||||
rack (>= 1.0)
|
||||
webmock (3.5.1)
|
||||
warden (1.2.8)
|
||||
rack (>= 2.0.6)
|
||||
webmock (3.6.2)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff
|
||||
webpacker (3.5.5)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webpacker (4.0.7)
|
||||
activesupport (>= 4.2)
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 4.2)
|
||||
webpush (0.3.6)
|
||||
webpush (0.3.8)
|
||||
hkdf (~> 0.2)
|
||||
jwt (~> 2.0)
|
||||
websocket-driver (0.7.0)
|
||||
@ -654,40 +665,42 @@ PLATFORMS
|
||||
|
||||
DEPENDENCIES
|
||||
active_model_serializers (~> 0.10)
|
||||
active_record_query_trace (~> 1.5)
|
||||
active_record_query_trace (~> 1.6)
|
||||
addressable (~> 2.6)
|
||||
annotate (~> 2.7)
|
||||
aws-sdk-s3 (~> 1.30)
|
||||
aws-sdk-s3 (~> 1.46)
|
||||
better_errors (~> 2.5)
|
||||
binding_of_caller (~> 0.7)
|
||||
bootsnap (~> 1.3)
|
||||
brakeman (~> 4.4)
|
||||
blurhash (~> 0.1)
|
||||
bootsnap (~> 1.4)
|
||||
brakeman (~> 4.6)
|
||||
browser
|
||||
bullet (~> 5.9)
|
||||
bullet (~> 6.0)
|
||||
bundler-audit (~> 0.6)
|
||||
capistrano (~> 3.11)
|
||||
capistrano-rails (~> 1.4)
|
||||
capistrano-rbenv (~> 2.1)
|
||||
capistrano-yarn (~> 2.0)
|
||||
capybara (~> 3.12)
|
||||
capybara (~> 3.28)
|
||||
charlock_holmes (~> 0.7.6)
|
||||
chewy (~> 5.0)
|
||||
cld3 (~> 3.2.3)
|
||||
cld3 (~> 3.2.4)
|
||||
climate_control (~> 0.2)
|
||||
concurrent-ruby
|
||||
connection_pool
|
||||
derailed_benchmarks
|
||||
devise (~> 4.5)
|
||||
devise (~> 4.6)
|
||||
devise-two-factor (~> 3.0)
|
||||
devise_pam_authenticatable2 (~> 9.2)
|
||||
doorkeeper (~> 5.0)
|
||||
dotenv-rails (~> 2.6)
|
||||
doorkeeper (~> 5.1)
|
||||
dotenv-rails (~> 2.7)
|
||||
fabrication (~> 2.20)
|
||||
faker (~> 1.9)
|
||||
faker (~> 2.1)
|
||||
fast_blank (~> 1.0)
|
||||
fastimage
|
||||
fog-core (<= 2.1.0)
|
||||
fog-openstack (~> 0.3)
|
||||
fuubar (~> 2.3)
|
||||
fuubar (~> 2.4)
|
||||
goldfinger (~> 2.1)
|
||||
hamlit-rails (~> 0.2)
|
||||
hiredis (~> 0.6)
|
||||
@ -695,47 +708,49 @@ DEPENDENCIES
|
||||
http (~> 3.3)
|
||||
http_accept_language (~> 2.1)
|
||||
http_parser.rb (~> 0.6)!
|
||||
httplog (~> 1.2)
|
||||
httplog (~> 1.3)
|
||||
i18n-tasks (~> 0.9)
|
||||
idn-ruby
|
||||
iso-639
|
||||
json-ld (~> 3.0)
|
||||
json-ld!
|
||||
json-ld-preloaded (~> 3.0)
|
||||
kaminari (~> 1.1)
|
||||
letter_opener (~> 1.7)
|
||||
letter_opener_web (~> 1.3)
|
||||
link_header (~> 0.0)
|
||||
lograge (~> 0.10)
|
||||
lograge (~> 0.11)
|
||||
makara (~> 0.4)
|
||||
mario-redis-lock (~> 1.2)
|
||||
memory_profiler
|
||||
microformats (~> 4.0)
|
||||
microformats (~> 4.1)
|
||||
mime-types (~> 3.2)
|
||||
net-ldap (~> 0.10)
|
||||
nilsimsa!
|
||||
nokogiri (~> 1.10)
|
||||
nsa (~> 0.2)
|
||||
oj (~> 3.7)
|
||||
oj (~> 3.8)
|
||||
omniauth (~> 1.9)
|
||||
omniauth-cas (~> 1.1)
|
||||
omniauth-saml (~> 1.10)
|
||||
ostatus2 (~> 2.0)
|
||||
ox (~> 2.10)
|
||||
ox (~> 2.11)
|
||||
paperclip (~> 6.0)
|
||||
paperclip-av-transcoder (~> 0.6)
|
||||
parallel_tests (~> 2.27)
|
||||
parallel_tests (~> 2.29)
|
||||
parslet
|
||||
pg (~> 1.1)
|
||||
pghero (~> 2.2)
|
||||
pkg-config (~> 1.3)
|
||||
posix-spawn!
|
||||
premailer-rails
|
||||
private_address_check (~> 0.5)
|
||||
pry-byebug (~> 3.6)
|
||||
pry-byebug (~> 3.7)
|
||||
pry-rails (~> 0.3)
|
||||
puma (~> 3.12)
|
||||
puma (~> 4.0)
|
||||
pundit (~> 2.0)
|
||||
rack-attack (~> 5.4)
|
||||
rack-attack (~> 6.1)
|
||||
rack-cors (~> 1.0)
|
||||
rails (~> 5.2.2)
|
||||
rails (~> 5.2.3)
|
||||
rails-controller-testing (~> 1.0)
|
||||
rails-i18n (~> 5.1)
|
||||
rails-settings-cached (~> 0.6)
|
||||
@ -746,32 +761,32 @@ DEPENDENCIES
|
||||
rqrcode (~> 0.10)
|
||||
rspec-rails (~> 3.8)
|
||||
rspec-sidekiq (~> 3.0)
|
||||
rubocop (~> 0.63)
|
||||
rubocop (~> 0.74)
|
||||
rubocop-rails (~> 2.2)
|
||||
sanitize (~> 5.0)
|
||||
scss_lint (~> 0.57)
|
||||
sidekiq (~> 5.2)
|
||||
sidekiq-bulk (~> 0.2.0)
|
||||
sidekiq-scheduler (~> 3.0)
|
||||
sidekiq-unique-jobs (~> 6.0)
|
||||
simple-navigation (~> 4.0)
|
||||
simple_form (~> 4.1)
|
||||
simplecov (~> 0.16)
|
||||
simplecov (~> 0.17)
|
||||
sprockets-rails (~> 3.2)
|
||||
stackprof
|
||||
stoplight (~> 2.1.3)
|
||||
streamio-ffmpeg (~> 3.0)
|
||||
strong_migrations (~> 0.3)
|
||||
strong_migrations (~> 0.4)
|
||||
thor (~> 0.20)
|
||||
tty-command (~> 0.8)
|
||||
tty-prompt (~> 0.18)
|
||||
tty-prompt (~> 0.19)
|
||||
twitter-text (~> 1.14)
|
||||
tzinfo-data (~> 1.2018)
|
||||
webmock (~> 3.5)
|
||||
webpacker (~> 3.5)
|
||||
tzinfo-data (~> 1.2019)
|
||||
webmock (~> 3.6)
|
||||
webpacker (~> 4.0)
|
||||
webpush
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.6.0p0
|
||||
ruby 2.6.1p33
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.3
|
||||
|
14
Procfile
14
Procfile
@ -1,2 +1,14 @@
|
||||
web: bundle exec puma -C config/puma.rb
|
||||
web: if [ "$RUN_STREAMING" != "true" ]; then BIND=0.0.0.0 bundle exec puma -C config/puma.rb; else BIND=0.0.0.0 node ./streaming; fi
|
||||
worker: bundle exec sidekiq
|
||||
|
||||
# For the streaming API, you need a separate app that shares Postgres and Redis:
|
||||
#
|
||||
# heroku create
|
||||
# heroku buildpacks:add heroku/nodejs
|
||||
# heroku config:set RUN_STREAMING=true
|
||||
# heroku addons:attach <main-app>::DATABASE
|
||||
# heroku addons:attach <main-app>::REDIS
|
||||
#
|
||||
# and let the main app use the separate app:
|
||||
#
|
||||
# heroku config:set STREAMING_API_BASE_URL=wss://<streaming-app>.herokuapp.com -a <main-app>
|
||||
|
10
README.md
10
README.md
@ -4,13 +4,13 @@
|
||||
[][releases]
|
||||
[][circleci]
|
||||
[][code_climate]
|
||||
[][weblate]
|
||||
[][crowdin]
|
||||
[][docker]
|
||||
|
||||
[releases]: https://github.com/tootsuite/mastodon/releases
|
||||
[circleci]: https://circleci.com/gh/tootsuite/mastodon
|
||||
[code_climate]: https://codeclimate.com/github/tootsuite/mastodon
|
||||
[weblate]: https://weblate.joinmastodon.org/engage/mastodon/
|
||||
[crowdin]: https://crowdin.com/project/mastodon
|
||||
[docker]: https://hub.docker.com/r/tootsuite/mastodon/
|
||||
|
||||
Mastodon is a **free, open-source social network server** based on ActivityPub. Follow friends and discover new ones. Publish anything you want: links, pictures, text, video. All servers of Mastodon are interoperable as a federated network, i.e. users on one server can seamlessly communicate with users from another one. This includes non-Mastodon software that also implements ActivityPub!
|
||||
@ -21,7 +21,7 @@ Click below to **learn more** in a video:
|
||||
|
||||
[youtube_demo]: https://www.youtube.com/watch?v=IPSbNdBmWKE
|
||||
|
||||
## Navigation
|
||||
## Navigation
|
||||
|
||||
- [Project homepage 🐘](https://joinmastodon.org)
|
||||
- [Support the development via Patreon][patreon]
|
||||
@ -80,13 +80,13 @@ A **Vagrant** configuration is included for development purposes.
|
||||
|
||||
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 Weblate. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
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 Weblate. 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
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2016-2018 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md))
|
||||
Copyright (C) 2016-2019 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.
|
||||
|
||||
|
13
Vagrantfile
vendored
13
Vagrantfile
vendored
@ -44,7 +44,18 @@ sudo apt-get install \
|
||||
|
||||
# Install rvm
|
||||
read RUBY_VERSION < .ruby-version
|
||||
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
|
||||
|
||||
gpg_command="gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB"
|
||||
$($gpg_command)
|
||||
if [ $? -ne 0 ];then
|
||||
echo "GPG command failed, This prevented RVM from installing."
|
||||
echo "Retrying once..." && $($gpg_command)
|
||||
if [ $? -ne 0 ];then
|
||||
echo "GPG failed for the second time, please ensure network connectivity."
|
||||
echo "Exiting..." && exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
|
||||
source /home/vagrant/.rvm/scripts/rvm
|
||||
|
||||
|
@ -31,7 +31,7 @@ class StatusesIndex < Chewy::Index
|
||||
},
|
||||
}
|
||||
|
||||
define_type ::Status.unscoped.without_reblogs do
|
||||
define_type ::Status.unscoped.without_reblogs.includes(:media_attachments) do
|
||||
crutch :mentions do |collection|
|
||||
data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
|
||||
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
|
||||
@ -48,14 +48,14 @@ class StatusesIndex < Chewy::Index
|
||||
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)].join("\n\n") } do
|
||||
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) }
|
||||
field :created_at, type: 'date'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,26 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AboutController < ApplicationController
|
||||
before_action :set_body_classes
|
||||
before_action :set_instance_presenter, only: [:show, :more, :terms]
|
||||
layout 'public'
|
||||
|
||||
def show
|
||||
serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
|
||||
@initial_state_json = serializable_resource.to_json
|
||||
end
|
||||
before_action :require_open_federation!, only: [:show, :more]
|
||||
before_action :set_body_classes, only: :show
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_expires_in
|
||||
|
||||
skip_before_action :require_functional!, only: [:more, :terms]
|
||||
|
||||
def show; end
|
||||
|
||||
def more
|
||||
render layout: 'public'
|
||||
flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor]
|
||||
end
|
||||
|
||||
def terms
|
||||
render layout: 'public'
|
||||
end
|
||||
def terms; end
|
||||
|
||||
private
|
||||
|
||||
def require_open_federation!
|
||||
not_found if whitelist_mode?
|
||||
end
|
||||
|
||||
def new_user
|
||||
User.new.tap(&:build_account)
|
||||
User.new.tap do |user|
|
||||
user.build_account
|
||||
user.build_invite_request
|
||||
end
|
||||
end
|
||||
|
||||
helper_method :new_user
|
||||
@ -30,13 +38,10 @@ class AboutController < ApplicationController
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
@hide_navbar = true
|
||||
end
|
||||
|
||||
def initial_state_params
|
||||
{
|
||||
settings: { known_fediverse: Setting.show_known_fediverse_at_about_page },
|
||||
token: current_session&.token,
|
||||
}
|
||||
def set_expires_in
|
||||
expires_in 0, public: true
|
||||
end
|
||||
end
|
||||
|
@ -4,13 +4,16 @@ class AccountsController < ApplicationController
|
||||
PAGE_SIZE = 20
|
||||
|
||||
include AccountControllerConcern
|
||||
include SignatureAuthentication
|
||||
|
||||
before_action :set_cache_headers
|
||||
before_action :set_body_classes
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@body_classes = 'with-modals'
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
@pinned_statuses = []
|
||||
@endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
|
||||
|
||||
@ -29,34 +32,33 @@ class AccountsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
format.atom do
|
||||
@entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id])
|
||||
render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? }))
|
||||
end
|
||||
|
||||
format.rss do
|
||||
expires_in 0, public: true
|
||||
|
||||
@statuses = cache_collection(default_statuses.without_reblogs.without_replies.limit(PAGE_SIZE), Status)
|
||||
render xml: RSS::AccountSerializer.render(@account, @statuses)
|
||||
end
|
||||
|
||||
format.json do
|
||||
skip_session!
|
||||
|
||||
render_cached_json(['activitypub', 'actor', @account], content_type: 'application/activity+json') do
|
||||
ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
|
||||
render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def show_pinned_statuses?
|
||||
[replies_requested?, media_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
|
||||
|
||||
def filtered_statuses
|
||||
default_statuses.tap do |statuses|
|
||||
statuses.merge!(hashtag_scope) if tag_requested?
|
||||
statuses.merge!(only_media_scope) if media_requested?
|
||||
statuses.merge!(no_replies_scope) unless replies_requested?
|
||||
end
|
||||
@ -78,12 +80,21 @@ class AccountsController < ApplicationController
|
||||
Status.without_replies
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:username])
|
||||
def hashtag_scope
|
||||
tag = Tag.find_normalized(params[:tag])
|
||||
|
||||
if tag
|
||||
Status.tagged_with(tag.id)
|
||||
else
|
||||
Status.none
|
||||
end
|
||||
end
|
||||
|
||||
def username_param
|
||||
params[:username]
|
||||
end
|
||||
|
||||
def older_url
|
||||
::Rails.logger.info("older: max_id #{@statuses.last.id}, url #{pagination_url(max_id: @statuses.last.id)}")
|
||||
pagination_url(max_id: @statuses.last.id)
|
||||
end
|
||||
|
||||
@ -92,7 +103,9 @@ class AccountsController < ApplicationController
|
||||
end
|
||||
|
||||
def pagination_url(max_id: nil, min_id: nil)
|
||||
if media_requested?
|
||||
if tag_requested?
|
||||
short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id)
|
||||
elsif media_requested?
|
||||
short_account_media_url(@account, max_id: max_id, min_id: min_id)
|
||||
elsif replies_requested?
|
||||
short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
|
||||
@ -109,6 +122,10 @@ class AccountsController < ApplicationController
|
||||
request.path.ends_with?('/with_replies')
|
||||
end
|
||||
|
||||
def tag_requested?
|
||||
request.path.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
|
||||
end
|
||||
|
||||
def filtered_status_page(params)
|
||||
if params[:min_id].present?
|
||||
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
|
||||
@ -116,4 +133,12 @@ class AccountsController < ApplicationController
|
||||
filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
|
||||
end
|
||||
end
|
||||
|
||||
def restrict_fields_to
|
||||
if signed_request_account.present? || public_fetch_mode?
|
||||
# Return all fields
|
||||
else
|
||||
%i(id type preferred_username inbox public_key endpoints)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
11
app/controllers/activitypub/base_controller.rb
Normal file
11
app/controllers/activitypub/base_controller.rb
Normal file
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::BaseController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!
|
||||
|
||||
private
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
|
||||
end
|
||||
end
|
@ -1,26 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::CollectionsController < Api::BaseController
|
||||
class ActivityPub::CollectionsController < ActivityPub::BaseController
|
||||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :set_account
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_size
|
||||
before_action :set_statuses
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
render json: collection_presenter,
|
||||
serializer: ActivityPub::CollectionSerializer,
|
||||
adapter: ActivityPub::Adapter,
|
||||
content_type: 'application/activity+json',
|
||||
skip_activities: true
|
||||
expires_in 3.minutes, public: public_fetch_mode?
|
||||
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def set_statuses
|
||||
@statuses = scope_for_collection
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
|
@ -1,28 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::InboxesController < Api::BaseController
|
||||
class ActivityPub::InboxesController < ActivityPub::BaseController
|
||||
include SignatureVerification
|
||||
include JsonLdHelper
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :set_account
|
||||
before_action :skip_unknown_actor_delete
|
||||
before_action :require_signature!
|
||||
|
||||
def create
|
||||
if signed_request_account
|
||||
upgrade_account
|
||||
process_payload
|
||||
head 202
|
||||
else
|
||||
render plain: signature_verification_failure_reason, status: 401
|
||||
end
|
||||
upgrade_account
|
||||
process_payload
|
||||
head 202
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username]) if params[:account_username]
|
||||
def skip_unknown_actor_delete
|
||||
head 202 if unknown_deleted_account?
|
||||
end
|
||||
|
||||
def unknown_deleted_account?
|
||||
json = Oj.load(body, mode: :strict)
|
||||
json.is_a?(Hash) && json['type'] == 'Delete' && json['actor'].present? && json['actor'] == value_or_id(json['object']) && !Account.where(uri: json['actor']).exists?
|
||||
rescue Oj::ParseError
|
||||
false
|
||||
end
|
||||
|
||||
def account_required?
|
||||
params[:account_username].present?
|
||||
end
|
||||
|
||||
def body
|
||||
@body ||= request.body.read
|
||||
return @body if defined?(@body)
|
||||
|
||||
@body = request.body.read
|
||||
@body.force_encoding('UTF-8') if @body.present?
|
||||
|
||||
request.body.rewind if request.body.respond_to?(:rewind)
|
||||
|
||||
@body
|
||||
end
|
||||
|
||||
def upgrade_account
|
||||
@ -31,11 +48,10 @@ class ActivityPub::InboxesController < Api::BaseController
|
||||
ResolveAccountWorker.perform_async(signed_request_account.acct)
|
||||
end
|
||||
|
||||
Pubsubhubbub::UnsubscribeWorker.perform_async(signed_request_account.id) if signed_request_account.subscribed?
|
||||
DeliveryFailureTracker.track_inverse_success!(signed_request_account)
|
||||
end
|
||||
|
||||
def process_payload
|
||||
ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body.force_encoding('UTF-8'), @account&.id)
|
||||
ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id)
|
||||
end
|
||||
end
|
||||
|
@ -1,23 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::OutboxesController < Api::BaseController
|
||||
class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||
LIMIT = 20
|
||||
|
||||
include SignatureVerification
|
||||
include AccountOwnedConcern
|
||||
|
||||
before_action :set_account
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_statuses
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def outbox_presenter
|
||||
if page_requested?
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
|
70
app/controllers/activitypub/replies_controller.rb
Normal file
70
app/controllers/activitypub/replies_controller.rb
Normal file
@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::RepliesController < ActivityPub::BaseController
|
||||
include SignatureAuthentication
|
||||
include Authorization
|
||||
include AccountOwnedConcern
|
||||
|
||||
DESCENDANTS_LIMIT = 60
|
||||
|
||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||
before_action :set_status
|
||||
before_action :set_cache_headers
|
||||
before_action :set_replies
|
||||
|
||||
def index
|
||||
expires_in 0, public: public_fetch_mode?
|
||||
render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def set_replies
|
||||
@replies = page_params[:other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses
|
||||
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
|
||||
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
|
||||
end
|
||||
|
||||
def replies_collection_presenter
|
||||
page = ActivityPub::CollectionPresenter.new(
|
||||
id: account_status_replies_url(@account, @status, page_params),
|
||||
type: :unordered,
|
||||
part_of: account_status_replies_url(@account, @status),
|
||||
next: next_page,
|
||||
items: @replies.map { |status| status.local ? status : status.id }
|
||||
)
|
||||
|
||||
return page if page_requested?
|
||||
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_status_replies_url(@account, @status),
|
||||
type: :unordered,
|
||||
first: page
|
||||
)
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
params[:page] == 'true'
|
||||
end
|
||||
|
||||
def next_page
|
||||
account_status_replies_url(
|
||||
@account,
|
||||
@status,
|
||||
page: true,
|
||||
min_id: @replies&.last&.id,
|
||||
other_accounts: !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)
|
||||
)
|
||||
end
|
||||
|
||||
def page_params
|
||||
params_slice(:other_accounts, :min_id).merge(page: true)
|
||||
end
|
||||
end
|
@ -2,9 +2,9 @@
|
||||
|
||||
module Admin
|
||||
class AccountsController < BaseController
|
||||
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize]
|
||||
before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
|
||||
before_action :require_local_account!, only: [:enable, :memorialize]
|
||||
before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
|
||||
before_action :require_remote_account!, only: [:redownload]
|
||||
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
|
||||
|
||||
def index
|
||||
authorize :account, :index?
|
||||
@ -19,18 +19,6 @@ module Admin
|
||||
@warnings = @account.targeted_account_warnings.latest.custom
|
||||
end
|
||||
|
||||
def subscribe
|
||||
authorize @account, :subscribe?
|
||||
Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def unsubscribe
|
||||
authorize @account, :unsubscribe?
|
||||
Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id)
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def memorialize
|
||||
authorize @account, :memorialize?
|
||||
@account.memorialize!
|
||||
@ -45,6 +33,18 @@ module Admin
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def approve
|
||||
authorize @account.user, :approve?
|
||||
@account.user.approve!
|
||||
redirect_to admin_pending_accounts_path
|
||||
end
|
||||
|
||||
def reject
|
||||
authorize @account.user, :reject?
|
||||
SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true)
|
||||
redirect_to admin_pending_accounts_path
|
||||
end
|
||||
|
||||
def unsilence
|
||||
authorize @account, :unsilence?
|
||||
@account.unsilence!
|
||||
@ -114,6 +114,8 @@ module Admin
|
||||
:remote,
|
||||
:by_domain,
|
||||
:active,
|
||||
:pending,
|
||||
:disabled,
|
||||
:silenced,
|
||||
:suspended,
|
||||
:username,
|
||||
|
@ -5,6 +5,9 @@ module Admin
|
||||
before_action :set_custom_emoji, except: [:index, :new, :create]
|
||||
before_action :set_filter_params
|
||||
|
||||
include ObfuscateFilename
|
||||
obfuscate_filename [:custom_emoji, :image]
|
||||
|
||||
def index
|
||||
authorize :custom_emoji, :index?
|
||||
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
|
||||
|
@ -5,12 +5,13 @@ module Admin
|
||||
class DashboardController < BaseController
|
||||
def index
|
||||
@users_count = User.count
|
||||
@pending_users_count = User.pending.count
|
||||
@registrations_week = Redis.current.get("activity:accounts:local:#{current_week}") || 0
|
||||
@logins_week = Redis.current.pfcount("activity:logins:#{current_week}")
|
||||
@interactions_week = Redis.current.get("activity:interactions:#{current_week}") || 0
|
||||
@relay_enabled = Relay.enabled.exists?
|
||||
@single_user_mode = Rails.configuration.x.single_user_mode
|
||||
@registrations_enabled = Setting.open_registrations
|
||||
@registrations_enabled = Setting.registrations_mode != 'none'
|
||||
@deletions_enabled = Setting.open_deletion
|
||||
@invites_enabled = Setting.min_invite_role == 'user'
|
||||
@search_enabled = Chewy.enabled?
|
||||
@ -19,7 +20,7 @@ module Admin
|
||||
@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(4)
|
||||
@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'
|
||||
@ -27,8 +28,14 @@ module Admin
|
||||
@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(7)
|
||||
@trending_hashtags = TrendingTags.get(10, filtered: false)
|
||||
@pending_tags_count = Tag.pending_review.count
|
||||
@authorized_fetch = authorized_fetch_mode?
|
||||
@whitelist_enabled = whitelist_mode?
|
||||
@profile_directory = Setting.profile_directory
|
||||
@timeline_preview = Setting.timeline_preview
|
||||
@spam_check_enabled = Setting.spam_check_enabled
|
||||
@trends_enabled = Setting.trends
|
||||
end
|
||||
|
||||
private
|
||||
@ -38,7 +45,13 @@ module Admin
|
||||
end
|
||||
|
||||
def redis_info
|
||||
@redis_info ||= Redis.current.info
|
||||
@redis_info ||= begin
|
||||
if Redis.current.is_a?(Redis::Namespace)
|
||||
Redis.current.redis.info
|
||||
else
|
||||
Redis.current.info
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
40
app/controllers/admin/domain_allows_controller.rb
Normal file
40
app/controllers/admin/domain_allows_controller.rb
Normal file
@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Admin::DomainAllowsController < Admin::BaseController
|
||||
before_action :set_domain_allow, only: [:destroy]
|
||||
|
||||
def new
|
||||
authorize :domain_allow, :create?
|
||||
|
||||
@domain_allow = DomainAllow.new(domain: params[:_domain])
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :domain_allow, :create?
|
||||
|
||||
@domain_allow = DomainAllow.new(resource_params)
|
||||
|
||||
if @domain_allow.save
|
||||
log_action :create, @domain_allow
|
||||
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.created_msg')
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @domain_allow, :destroy?
|
||||
UnallowDomainService.new.call(@domain_allow)
|
||||
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_domain_allow
|
||||
@domain_allow = DomainAllow.find(params[:id])
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:domain_allow).permit(:domain)
|
||||
end
|
||||
end
|
@ -2,24 +2,56 @@
|
||||
|
||||
module Admin
|
||||
class DomainBlocksController < BaseController
|
||||
before_action :set_domain_block, only: [:show, :destroy]
|
||||
before_action :set_domain_block, only: [:show, :destroy, :edit, :update]
|
||||
|
||||
def new
|
||||
authorize :domain_block, :create?
|
||||
@domain_block = DomainBlock.new(domain: params[:_domain])
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize :domain_block, :create?
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :domain_block, :create?
|
||||
|
||||
@domain_block = DomainBlock.new(resource_params)
|
||||
existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil
|
||||
|
||||
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
|
||||
@domain_block.save
|
||||
flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe # rubocop:disable Rails/OutputSafety
|
||||
@domain_block.errors[:domain].clear
|
||||
render :new
|
||||
else
|
||||
if existing_domain_block.present?
|
||||
@domain_block = existing_domain_block
|
||||
@domain_block.update(resource_params)
|
||||
end
|
||||
if @domain_block.save
|
||||
DomainBlockWorker.perform_async(@domain_block.id)
|
||||
log_action :create, @domain_block
|
||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize :domain_block, :create?
|
||||
|
||||
@domain_block.update(update_params)
|
||||
|
||||
severity_changed = @domain_block.severity_changed?
|
||||
|
||||
if @domain_block.save
|
||||
DomainBlockWorker.perform_async(@domain_block.id)
|
||||
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
|
||||
log_action :create, @domain_block
|
||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
||||
else
|
||||
render :new
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
@ -29,7 +61,7 @@ module Admin
|
||||
|
||||
def destroy
|
||||
authorize @domain_block, :destroy?
|
||||
UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
|
||||
UnblockDomainService.new.call(@domain_block)
|
||||
log_action :destroy, @domain_block
|
||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.destroyed_msg')
|
||||
end
|
||||
@ -40,12 +72,12 @@ module Admin
|
||||
@domain_block = DomainBlock.find(params[:id])
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :retroactive)
|
||||
def update_params
|
||||
params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment)
|
||||
end
|
||||
|
||||
def retroactive_unblock?
|
||||
ActiveRecord::Type.lookup(:boolean).cast(resource_params[:retroactive])
|
||||
def resource_params
|
||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
module Admin
|
||||
class InstancesController < BaseController
|
||||
before_action :set_domain_block, only: :show
|
||||
before_action :set_domain_allow, only: :show
|
||||
before_action :set_instance, only: :show
|
||||
|
||||
def index
|
||||
authorize :instance, :index?
|
||||
|
||||
@ -11,20 +15,40 @@ module Admin
|
||||
def show
|
||||
authorize :instance, :show?
|
||||
|
||||
@instance = Instance.new(Account.by_domain_accounts.find_by(domain: params[:id]) || DomainBlock.find_by!(domain: params[:id]))
|
||||
@following_count = Follow.where(account: Account.where(domain: params[:id])).count
|
||||
@followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
|
||||
@reports_count = Report.where(target_account: Account.where(domain: params[:id])).count
|
||||
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
|
||||
@available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url)
|
||||
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
|
||||
@domain_block = DomainBlock.find_by(domain: params[:id])
|
||||
@private_comment = @domain_block&.private_comment
|
||||
@public_comment = @domain_block&.public_comment
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_domain_block
|
||||
@domain_block = DomainBlock.rule_for(params[:id])
|
||||
end
|
||||
|
||||
def set_domain_allow
|
||||
@domain_allow = DomainAllow.rule_for(params[:id])
|
||||
end
|
||||
|
||||
def set_instance
|
||||
resource = Account.by_domain_accounts.find_by(domain: params[:id])
|
||||
resource ||= @domain_block
|
||||
resource ||= @domain_allow
|
||||
|
||||
if resource
|
||||
@instance = Instance.new(resource)
|
||||
else
|
||||
not_found
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_instances
|
||||
InstanceFilter.new(filter_params).results
|
||||
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
|
||||
end
|
||||
|
||||
def paginated_instances
|
||||
@ -38,7 +62,7 @@ module Admin
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.permit(:limited)
|
||||
params.permit(:limited, :by_domain)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
52
app/controllers/admin/pending_accounts_controller.rb
Normal file
52
app/controllers/admin/pending_accounts_controller.rb
Normal file
@ -0,0 +1,52 @@
|
||||
# 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
|
@ -10,6 +10,10 @@ module Admin
|
||||
@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
|
||||
|
||||
|
@ -2,85 +2,29 @@
|
||||
|
||||
module Admin
|
||||
class SettingsController < BaseController
|
||||
ADMIN_SETTINGS = %w(
|
||||
site_contact_username
|
||||
site_contact_email
|
||||
site_title
|
||||
site_short_description
|
||||
site_description
|
||||
site_extended_description
|
||||
site_terms
|
||||
open_registrations
|
||||
closed_registrations_message
|
||||
open_deletion
|
||||
timeline_preview
|
||||
show_staff_badge
|
||||
bootstrap_timeline_accounts
|
||||
theme
|
||||
thumbnail
|
||||
hero
|
||||
mascot
|
||||
min_invite_role
|
||||
activity_api_enabled
|
||||
peers_api_enabled
|
||||
show_known_fediverse_at_about_page
|
||||
preview_sensitive_media
|
||||
custom_css
|
||||
profile_directory
|
||||
).freeze
|
||||
|
||||
BOOLEAN_SETTINGS = %w(
|
||||
open_registrations
|
||||
open_deletion
|
||||
timeline_preview
|
||||
show_staff_badge
|
||||
activity_api_enabled
|
||||
peers_api_enabled
|
||||
show_known_fediverse_at_about_page
|
||||
preview_sensitive_media
|
||||
profile_directory
|
||||
).freeze
|
||||
|
||||
UPLOAD_SETTINGS = %w(
|
||||
thumbnail
|
||||
hero
|
||||
mascot
|
||||
).freeze
|
||||
|
||||
def edit
|
||||
authorize :settings, :show?
|
||||
|
||||
@admin_settings = Form::AdminSettings.new
|
||||
end
|
||||
|
||||
def update
|
||||
authorize :settings, :update?
|
||||
|
||||
settings_params.each do |key, value|
|
||||
if UPLOAD_SETTINGS.include?(key)
|
||||
upload = SiteUpload.where(var: key).first_or_initialize(var: key)
|
||||
upload.update(file: value)
|
||||
else
|
||||
setting = Setting.where(var: key).first_or_initialize(var: key)
|
||||
setting.update(value: value_for_update(key, value))
|
||||
end
|
||||
end
|
||||
@admin_settings = Form::AdminSettings.new(settings_params)
|
||||
|
||||
flash[:notice] = I18n.t('generic.changes_saved_msg')
|
||||
redirect_to edit_admin_settings_path
|
||||
if @admin_settings.save
|
||||
flash[:notice] = I18n.t('generic.changes_saved_msg')
|
||||
redirect_to edit_admin_settings_path
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def settings_params
|
||||
params.require(:form_admin_settings).permit(ADMIN_SETTINGS)
|
||||
end
|
||||
|
||||
def value_for_update(key, value)
|
||||
if BOOLEAN_SETTINGS.include?(key)
|
||||
value == '1'
|
||||
else
|
||||
value
|
||||
end
|
||||
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -4,41 +4,75 @@ module Admin
|
||||
class TagsController < BaseController
|
||||
before_action :set_tags, only: :index
|
||||
before_action :set_tag, except: :index
|
||||
before_action :set_filter_params
|
||||
before_action :set_usage_by_domain, except: :index
|
||||
before_action :set_counters, except: :index
|
||||
|
||||
def index
|
||||
authorize :tag, :index?
|
||||
end
|
||||
|
||||
def hide
|
||||
authorize @tag, :hide?
|
||||
@tag.account_tag_stat.update!(hidden: true)
|
||||
redirect_to admin_tags_path(@filter_params)
|
||||
def show
|
||||
authorize @tag, :show?
|
||||
end
|
||||
|
||||
def unhide
|
||||
authorize @tag, :unhide?
|
||||
@tag.account_tag_stat.update!(hidden: false)
|
||||
redirect_to admin_tags_path(@filter_params)
|
||||
def update
|
||||
authorize @tag, :update?
|
||||
|
||||
if @tag.update(tag_params.merge(reviewed_at: Time.now.utc))
|
||||
redirect_to admin_tag_path(@tag.id), notice: I18n.t('admin.tags.updated_msg')
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_tags
|
||||
@tags = Tag.discoverable
|
||||
@tags.merge!(Tag.hidden) if filter_params[:hidden]
|
||||
@tags = filtered_tags.page(params[:page])
|
||||
end
|
||||
|
||||
def set_tag
|
||||
@tag = Tag.find(params[:id])
|
||||
end
|
||||
|
||||
def set_filter_params
|
||||
@filter_params = filter_params.to_hash.symbolize_keys
|
||||
def set_usage_by_domain
|
||||
@usage_by_domain = @tag.statuses
|
||||
.where(visibility: :public)
|
||||
.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
|
||||
scope = Tag
|
||||
scope = scope.discoverable if filter_params[:context] == 'directory'
|
||||
scope = scope.unreviewed if filter_params[:review] == 'unreviewed'
|
||||
scope = scope.reviewed.order(reviewed_at: :desc) if filter_params[:review] == 'reviewed'
|
||||
scope = scope.pending_review.order(requested_review_at: :desc) if filter_params[:review] == 'pending_review'
|
||||
scope.order(score: :desc)
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.permit(:hidden)
|
||||
params.slice(:context, :review).permit(:context, :review)
|
||||
end
|
||||
|
||||
def tag_params
|
||||
params.require(:tag).permit(:name, :trendable, :usable, :listable)
|
||||
end
|
||||
|
||||
def current_week_days
|
||||
now = Time.now.utc.beginning_of_day.to_date
|
||||
|
||||
(Date.commercial(now.cwyear, now.cweek)..now).map do |date|
|
||||
date.to_time(:utc).beginning_of_day.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -7,7 +7,10 @@ class Api::BaseController < ApplicationController
|
||||
include RateLimitHeaders
|
||||
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :check_user_permissions
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
|
||||
before_action :set_cache_headers
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
@ -67,13 +70,19 @@ class Api::BaseController < ApplicationController
|
||||
nil
|
||||
end
|
||||
|
||||
def require_authenticated_user!
|
||||
render json: { error: 'This API requires an authenticated user' }, status: 401 unless current_user
|
||||
end
|
||||
|
||||
def require_user!
|
||||
if !current_user
|
||||
render json: { error: 'This method requires an authenticated user' }, status: 422
|
||||
elsif current_user.disabled?
|
||||
render json: { error: 'Your login is currently disabled' }, status: 403
|
||||
elsif !current_user.confirmed?
|
||||
render json: { error: 'Email confirmation is not completed' }, status: 403
|
||||
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
|
||||
elsif !current_user.approved?
|
||||
render json: { error: 'Your login is currently pending approval' }, status: 403
|
||||
else
|
||||
set_user_activity
|
||||
end
|
||||
@ -86,4 +95,12 @@ class Api::BaseController < ApplicationController
|
||||
def authorize_if_got_token!(*scopes)
|
||||
doorkeeper_authorize!(*scopes) if doorkeeper_token
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
end
|
||||
|
||||
def disallow_unauthenticated_api_access?
|
||||
authorized_fetch_mode?
|
||||
end
|
||||
end
|
||||
|
21
app/controllers/api/proofs_controller.rb
Normal file
21
app/controllers/api/proofs_controller.rb
Normal file
@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::ProofsController < Api::BaseController
|
||||
include AccountOwnedConcern
|
||||
|
||||
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
|
@ -1,73 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::PushController < Api::BaseController
|
||||
include SignatureVerification
|
||||
|
||||
def update
|
||||
response, status = process_push_request
|
||||
render plain: response, status: status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_push_request
|
||||
case hub_mode
|
||||
when 'subscribe'
|
||||
Pubsubhubbub::SubscribeService.new.call(account_from_topic, hub_callback, hub_secret, hub_lease_seconds, verified_domain)
|
||||
when 'unsubscribe'
|
||||
Pubsubhubbub::UnsubscribeService.new.call(account_from_topic, hub_callback)
|
||||
else
|
||||
["Unknown mode: #{hub_mode}", 422]
|
||||
end
|
||||
end
|
||||
|
||||
def hub_mode
|
||||
params['hub.mode']
|
||||
end
|
||||
|
||||
def hub_topic
|
||||
params['hub.topic']
|
||||
end
|
||||
|
||||
def hub_callback
|
||||
params['hub.callback']
|
||||
end
|
||||
|
||||
def hub_lease_seconds
|
||||
params['hub.lease_seconds']
|
||||
end
|
||||
|
||||
def hub_secret
|
||||
params['hub.secret']
|
||||
end
|
||||
|
||||
def account_from_topic
|
||||
if hub_topic.present? && local_domain? && account_feed_path?
|
||||
Account.find_local(hub_topic_params[:username])
|
||||
end
|
||||
end
|
||||
|
||||
def hub_topic_params
|
||||
@_hub_topic_params ||= Rails.application.routes.recognize_path(hub_topic_uri.path)
|
||||
end
|
||||
|
||||
def hub_topic_uri
|
||||
@_hub_topic_uri ||= Addressable::URI.parse(hub_topic).normalize
|
||||
end
|
||||
|
||||
def local_domain?
|
||||
TagManager.instance.web_domain?(hub_topic_domain)
|
||||
end
|
||||
|
||||
def verified_domain
|
||||
return signed_request_account.domain if signed_request_account
|
||||
end
|
||||
|
||||
def hub_topic_domain
|
||||
hub_topic_uri.host + (hub_topic_uri.port ? ":#{hub_topic_uri.port}" : '')
|
||||
end
|
||||
|
||||
def account_feed_path?
|
||||
hub_topic_params[:controller] == 'accounts' && hub_topic_params[:action] == 'show' && hub_topic_params[:format] == 'atom'
|
||||
end
|
||||
end
|
@ -1,37 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::SalmonController < Api::BaseController
|
||||
include SignatureVerification
|
||||
|
||||
before_action :set_account
|
||||
respond_to :txt
|
||||
|
||||
def update
|
||||
if verify_payload?
|
||||
process_salmon
|
||||
head 202
|
||||
elsif payload.present?
|
||||
render plain: signature_verification_failure_reason, status: 401
|
||||
else
|
||||
head 400
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
|
||||
def payload
|
||||
@_payload ||= request.body.read
|
||||
end
|
||||
|
||||
def verify_payload?
|
||||
payload.present? && VerifySalmonService.new.call(payload)
|
||||
end
|
||||
|
||||
def process_salmon
|
||||
SalmonWorker.perform_async(@account.id, payload.force_encoding('UTF-8'))
|
||||
end
|
||||
end
|
@ -1,51 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::SubscriptionsController < Api::BaseController
|
||||
before_action :set_account
|
||||
respond_to :txt
|
||||
|
||||
def show
|
||||
if subscription.valid?(params['hub.topic'])
|
||||
@account.update(subscription_expires_at: future_expires)
|
||||
render plain: encoded_challenge, status: 200
|
||||
else
|
||||
head 404
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if subscription.verify(body, request.headers['HTTP_X_HUB_SIGNATURE'])
|
||||
ProcessingWorker.perform_async(@account.id, body.force_encoding('UTF-8'))
|
||||
end
|
||||
|
||||
head 200
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def subscription
|
||||
@_subscription ||= @account.subscription(
|
||||
api_subscription_url(@account.id)
|
||||
)
|
||||
end
|
||||
|
||||
def body
|
||||
@_body ||= request.body.read
|
||||
end
|
||||
|
||||
def encoded_challenge
|
||||
HTMLEntities.new.encode(params['hub.challenge'])
|
||||
end
|
||||
|
||||
def future_expires
|
||||
Time.now.utc + lease_seconds_or_default
|
||||
end
|
||||
|
||||
def lease_seconds_or_default
|
||||
(params['hub.lease_seconds'] || 1.day).to_i.seconds
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
end
|
@ -19,11 +19,15 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
||||
end
|
||||
|
||||
def load_accounts
|
||||
return [] if @account.user_hides_network? && current_account.id != @account.id
|
||||
return [] if hide_results?
|
||||
|
||||
default_accounts.merge(paginated_follows).to_a
|
||||
end
|
||||
|
||||
def hide_results?
|
||||
(@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account))
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
Account.includes(:active_relationships, :account_stat).references(:active_relationships)
|
||||
end
|
||||
|
@ -19,11 +19,15 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
||||
end
|
||||
|
||||
def load_accounts
|
||||
return [] if @account.user_hides_network? && current_account.id != @account.id
|
||||
return [] if hide_results?
|
||||
|
||||
default_accounts.merge(paginated_follows).to_a
|
||||
end
|
||||
|
||||
def hide_results?
|
||||
(@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account))
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
Account.includes(:passive_relationships, :account_stat).references(:passive_relationships)
|
||||
end
|
||||
|
@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::IdentityProofsController < Api::BaseController
|
||||
before_action :require_user!
|
||||
before_action :set_account
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@proofs = @account.identity_proofs.active
|
||||
render json: @proofs, each_serializer: REST::IdentityProofSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
end
|
@ -16,10 +16,11 @@ class Api::V1::Accounts::SearchController < Api::BaseController
|
||||
def account_search
|
||||
AccountSearchService.new.call(
|
||||
params[:q],
|
||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
current_account,
|
||||
limit: limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
resolve: truthy_param?(:resolve),
|
||||
following: truthy_param?(:following)
|
||||
following: truthy_param?(:following),
|
||||
offset: params[:offset]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -3,7 +3,8 @@
|
||||
class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
|
||||
before_action :set_account
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
|
||||
|
||||
respond_to :json
|
||||
|
||||
@ -33,6 +34,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||
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?
|
||||
|
||||
statuses
|
||||
end
|
||||
@ -50,9 +52,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||
# Also, Avoid getting slow by not narrowing down by `statuses.account_id`.
|
||||
# When narrowing down by `statuses.account_id`, `index_statuses_20180106` will be used
|
||||
# and the table will be joined by `Merge Semi Join`, so the query will be slow.
|
||||
Status.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account)
|
||||
.paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
||||
.reorder(id: :desc).distinct(:id).pluck(:id)
|
||||
@account.statuses.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account)
|
||||
.paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
||||
.reorder(id: :desc).distinct(:id).pluck(:id)
|
||||
end
|
||||
|
||||
def pinned_scope
|
||||
@ -67,6 +69,16 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||
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)
|
||||
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
||||
end
|
||||
|
@ -12,6 +12,8 @@ class Api::V1::AccountsController < Api::BaseController
|
||||
before_action :check_account_suspension, only: [:show]
|
||||
before_action :check_enabled_registrations, only: [:create]
|
||||
|
||||
skip_before_action :require_authenticated_user!, only: :create
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@ -80,6 +82,10 @@ class Api::V1::AccountsController < Api::BaseController
|
||||
end
|
||||
|
||||
def check_enabled_registrations
|
||||
forbidden if single_user_mode? || !Setting.open_registrations
|
||||
forbidden if single_user_mode? || !allowed_registrations?
|
||||
end
|
||||
|
||||
def allowed_registrations?
|
||||
Setting.registrations_mode != 'none'
|
||||
end
|
||||
end
|
||||
|
32
app/controllers/api/v1/admin/account_actions_controller.rb
Normal file
32
app/controllers/api/v1/admin/account_actions_controller.rb
Normal file
@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::AccountActionsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }
|
||||
before_action :require_staff!
|
||||
before_action :set_account
|
||||
|
||||
def create
|
||||
account_action = Admin::AccountAction.new(resource_params)
|
||||
account_action.target_account = @account
|
||||
account_action.current_account = current_account
|
||||
account_action.save!
|
||||
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(
|
||||
:type,
|
||||
:report_id,
|
||||
:warning_preset_id,
|
||||
:text,
|
||||
:send_email_notification
|
||||
)
|
||||
end
|
||||
end
|
128
app/controllers/api/v1/admin/accounts_controller.rb
Normal file
128
app/controllers/api/v1/admin/accounts_controller.rb
Normal file
@ -0,0 +1,128 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::AccountsController < Api::BaseController
|
||||
include Authorization
|
||||
include AccountableConcern
|
||||
|
||||
LIMIT = 100
|
||||
|
||||
before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:index, :show]
|
||||
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }, except: [:index, :show]
|
||||
before_action :require_staff!
|
||||
before_action :set_accounts, only: :index
|
||||
before_action :set_account, except: :index
|
||||
before_action :require_local_account!, only: [:enable, :approve, :reject]
|
||||
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
FILTER_PARAMS = %i(
|
||||
local
|
||||
remote
|
||||
by_domain
|
||||
active
|
||||
pending
|
||||
disabled
|
||||
silenced
|
||||
suspended
|
||||
username
|
||||
display_name
|
||||
email
|
||||
ip
|
||||
staff
|
||||
).freeze
|
||||
|
||||
PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze
|
||||
|
||||
def index
|
||||
authorize :account, :index?
|
||||
render json: @accounts, each_serializer: REST::Admin::AccountSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @account, :show?
|
||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||
end
|
||||
|
||||
def enable
|
||||
authorize @account.user, :enable?
|
||||
@account.user.enable!
|
||||
log_action :enable, @account.user
|
||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||
end
|
||||
|
||||
def approve
|
||||
authorize @account.user, :approve?
|
||||
@account.user.approve!
|
||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||
end
|
||||
|
||||
def reject
|
||||
authorize @account.user, :reject?
|
||||
SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true)
|
||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||
end
|
||||
|
||||
def unsilence
|
||||
authorize @account, :unsilence?
|
||||
@account.unsilence!
|
||||
log_action :unsilence, @account
|
||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||
end
|
||||
|
||||
def unsuspend
|
||||
authorize @account, :unsuspend?
|
||||
@account.unsuspend!
|
||||
log_action :unsuspend, @account
|
||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_accounts
|
||||
@accounts = filtered_accounts.order(id: :desc).includes(user: [:invite_request, :invite]).paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
|
||||
def filtered_accounts
|
||||
AccountFilter.new(filter_params).results
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.permit(*FILTER_PARAMS)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_admin_accounts_url(pagination_params(max_id: pagination_max_id)) if records_continue?
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_admin_accounts_url(pagination_params(min_id: pagination_since_id)) unless @accounts.empty?
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@accounts.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@accounts.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@accounts.size == limit_param(LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
|
||||
end
|
||||
|
||||
def require_local_account!
|
||||
forbidden unless @account.local? && @account.user.present?
|
||||
end
|
||||
end
|
108
app/controllers/api/v1/admin/reports_controller.rb
Normal file
108
app/controllers/api/v1/admin/reports_controller.rb
Normal file
@ -0,0 +1,108 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Admin::ReportsController < Api::BaseController
|
||||
include Authorization
|
||||
include AccountableConcern
|
||||
|
||||
LIMIT = 100
|
||||
|
||||
before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:reports' }, only: [:index, :show]
|
||||
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:reports' }, except: [:index, :show]
|
||||
before_action :require_staff!
|
||||
before_action :set_reports, only: :index
|
||||
before_action :set_report, except: :index
|
||||
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
FILTER_PARAMS = %i(
|
||||
resolved
|
||||
account_id
|
||||
target_account_id
|
||||
).freeze
|
||||
|
||||
PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze
|
||||
|
||||
def index
|
||||
authorize :report, :index?
|
||||
render json: @reports, each_serializer: REST::Admin::ReportSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @report, :show?
|
||||
render json: @report, serializer: REST::Admin::ReportSerializer
|
||||
end
|
||||
|
||||
def assign_to_self
|
||||
authorize @report, :update?
|
||||
@report.update!(assigned_account_id: current_account.id)
|
||||
log_action :assigned_to_self, @report
|
||||
render json: @report, serializer: REST::Admin::ReportSerializer
|
||||
end
|
||||
|
||||
def unassign
|
||||
authorize @report, :update?
|
||||
@report.update!(assigned_account_id: nil)
|
||||
log_action :unassigned, @report
|
||||
render json: @report, serializer: REST::Admin::ReportSerializer
|
||||
end
|
||||
|
||||
def reopen
|
||||
authorize @report, :update?
|
||||
@report.unresolve!
|
||||
log_action :reopen, @report
|
||||
render json: @report, serializer: REST::Admin::ReportSerializer
|
||||
end
|
||||
|
||||
def resolve
|
||||
authorize @report, :update?
|
||||
@report.resolve!(current_account)
|
||||
log_action :resolve, @report
|
||||
render json: @report, serializer: REST::Admin::ReportSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_reports
|
||||
@reports = filtered_reports.order(id: :desc).with_accounts.paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||
end
|
||||
|
||||
def set_report
|
||||
@report = Report.find(params[:id])
|
||||
end
|
||||
|
||||
def filtered_reports
|
||||
ReportFilter.new(filter_params).results
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.permit(*FILTER_PARAMS)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_admin_reports_url(pagination_params(max_id: pagination_max_id)) if records_continue?
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_admin_reports_url(pagination_params(min_id: pagination_since_id)) unless @reports.empty?
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@reports.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@reports.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@reports.size == limit_param(LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
|
||||
end
|
||||
end
|
@ -6,6 +6,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
render json: doorkeeper_token.application, serializer: REST::StatusSerializer::ApplicationSerializer
|
||||
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key)
|
||||
end
|
||||
end
|
||||
|
@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::AppsController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!
|
||||
|
||||
def create
|
||||
@app = Doorkeeper::Application.create!(application_options)
|
||||
render json: @app, serializer: REST::ApplicationSerializer
|
||||
|
@ -3,9 +3,10 @@
|
||||
class Api::V1::CustomEmojisController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
def index
|
||||
render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do
|
||||
ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false), each_serializer: REST::CustomEmojiSerializer)
|
||||
end
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.local.where(disabled: false).includes(:category) }
|
||||
end
|
||||
end
|
||||
|
@ -1,31 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::FollowsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :follow, :'write:follows' }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
raise ActiveRecord::RecordNotFound if follow_params[:uri].blank?
|
||||
|
||||
@account = FollowService.new.call(current_user.account, target_uri).try(:target_account)
|
||||
|
||||
if @account.nil?
|
||||
username, domain = target_uri.split('@')
|
||||
@account = Account.find_remote!(username, domain)
|
||||
end
|
||||
|
||||
render json: @account, serializer: REST::AccountSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def target_uri
|
||||
follow_params[:uri].strip.gsub(/\A@/, '')
|
||||
end
|
||||
|
||||
def follow_params
|
||||
params.permit(:uri)
|
||||
end
|
||||
end
|
@ -3,10 +3,13 @@
|
||||
class Api::V1::Instances::ActivityController < Api::BaseController
|
||||
before_action :require_enabled_api!
|
||||
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
render_cached_json('api:v1:instances:activity:show', expires_in: 1.day) { activity }
|
||||
expires_in 1.day, public: true
|
||||
render_with_cache json: :activity, expires_in: 1.day
|
||||
end
|
||||
|
||||
private
|
||||
@ -31,6 +34,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
|
||||
end
|
||||
|
||||
def require_enabled_api!
|
||||
head 404 unless Setting.activity_api_enabled
|
||||
head 404 unless Setting.activity_api_enabled && !whitelist_mode?
|
||||
end
|
||||
end
|
||||
|
@ -3,15 +3,18 @@
|
||||
class Api::V1::Instances::PeersController < Api::BaseController
|
||||
before_action :require_enabled_api!
|
||||
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
render_cached_json('api:v1:instances:peers:index', expires_in: 1.day) { Account.remote.domains }
|
||||
expires_in 1.day, public: true
|
||||
render_with_cache(expires_in: 1.day) { Account.remote.domains }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_enabled_api!
|
||||
head 404 unless Setting.peers_api_enabled
|
||||
head 404 unless Setting.peers_api_enabled && !whitelist_mode?
|
||||
end
|
||||
end
|
||||
|
@ -3,9 +3,10 @@
|
||||
class Api::V1::InstancesController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
render_cached_json('api:v1:instances', expires_in: 5.minutes) do
|
||||
ActiveModelSerializers::SerializableResource.new({}, serializer: REST::InstanceSerializer)
|
||||
end
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache json: {}, serializer: REST::InstanceSerializer, root: 'instance'
|
||||
end
|
||||
end
|
||||
|
@ -44,7 +44,7 @@ class Api::V1::NotificationsController < Api::BaseController
|
||||
end
|
||||
|
||||
def browserable_account_notifications
|
||||
current_account.notifications.browserable(exclude_types)
|
||||
current_account.notifications.browserable(exclude_types, from_account)
|
||||
end
|
||||
|
||||
def target_statuses_from_notifications
|
||||
@ -81,6 +81,10 @@ class Api::V1::NotificationsController < Api::BaseController
|
||||
val
|
||||
end
|
||||
|
||||
def from_account
|
||||
params[:account_id]
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit, :exclude_types).permit(:limit, exclude_types: []).merge(core_params)
|
||||
end
|
||||
|
29
app/controllers/api/v1/polls/votes_controller.rb
Normal file
29
app/controllers/api/v1/polls/votes_controller.rb
Normal file
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Polls::VotesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
|
||||
before_action :require_user!
|
||||
before_action :set_poll
|
||||
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
VoteService.new.call(current_account, @poll, vote_params[:choices])
|
||||
render json: @poll, serializer: REST::PollSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_poll
|
||||
@poll = Poll.attached.find(params[:poll_id])
|
||||
authorize @poll.status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def vote_params
|
||||
params.permit(choices: [])
|
||||
end
|
||||
end
|
28
app/controllers/api/v1/polls_controller.rb
Normal file
28
app/controllers/api/v1/polls_controller.rb
Normal file
@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::PollsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, only: :show
|
||||
before_action :set_poll
|
||||
before_action :refresh_poll
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
render json: @poll, serializer: REST::PollSerializer, include_results: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_poll
|
||||
@poll = Poll.attached.find(params[:id])
|
||||
authorize @poll.status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def refresh_poll
|
||||
ActivityPub::FetchRemotePollService.new.call(@poll, current_account) if user_signed_in? && @poll.possibly_stale?
|
||||
end
|
||||
end
|
12
app/controllers/api/v1/preferences_controller.rb
Normal file
12
app/controllers/api/v1/preferences_controller.rb
Normal file
@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::PreferencesController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
render json: current_account, serializer: REST::PreferencesSerializer
|
||||
end
|
||||
end
|
@ -51,6 +51,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
|
||||
|
||||
def data_params
|
||||
return {} if params[:data].blank?
|
||||
params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention])
|
||||
params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll])
|
||||
end
|
||||
end
|
||||
|
@ -3,7 +3,7 @@
|
||||
class Api::V1::SearchController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
RESULTS_LIMIT = 5
|
||||
RESULTS_LIMIT = 20
|
||||
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:search' }
|
||||
before_action :require_user!
|
||||
@ -11,30 +11,22 @@ class Api::V1::SearchController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@search = Search.new(search)
|
||||
@search = Search.new(search_results)
|
||||
render json: @search, serializer: REST::SearchSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search
|
||||
search_results.tap do |search|
|
||||
search[:statuses].keep_if do |status|
|
||||
begin
|
||||
authorize status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def search_results
|
||||
SearchService.new.call(
|
||||
params[:q],
|
||||
RESULTS_LIMIT,
|
||||
truthy_param?(:resolve),
|
||||
current_account
|
||||
current_account,
|
||||
limit_param(RESULTS_LIMIT),
|
||||
search_params.merge(resolve: truthy_param?(:resolve))
|
||||
)
|
||||
end
|
||||
|
||||
def search_params
|
||||
params.permit(:type, :offset, :min_id, :max_id, :account_id)
|
||||
end
|
||||
end
|
||||
|
@ -9,7 +9,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
@status = ReblogService.new.call(current_user.account, status_for_reblog)
|
||||
@status = ReblogService.new.call(current_user.account, status_for_reblog, reblog_params)
|
||||
render json: @status, serializer: REST::StatusSerializer
|
||||
end
|
||||
|
||||
@ -32,4 +32,8 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
||||
def status_for_destroy
|
||||
current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
|
||||
end
|
||||
|
||||
def reblog_params
|
||||
params.permit(:visibility)
|
||||
end
|
||||
end
|
||||
|
@ -5,8 +5,8 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
|
||||
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :destroy]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy]
|
||||
before_action :require_user!, except: [:show, :context, :card]
|
||||
before_action :set_status, only: [:show, :context, :card]
|
||||
before_action :require_user!, except: [:show, :context]
|
||||
before_action :set_status, only: [:show, :context]
|
||||
|
||||
respond_to :json
|
||||
|
||||
@ -33,16 +33,6 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
def card
|
||||
@card = @status.preview_cards.first
|
||||
|
||||
if @card.nil?
|
||||
render_empty
|
||||
else
|
||||
render json: @card, serializer: REST::PreviewCardSerializer
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@status = PostStatusService.new.call(current_user.account,
|
||||
text: status_params[:status],
|
||||
@ -53,6 +43,7 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
visibility: status_params[:visibility],
|
||||
scheduled_at: status_params[:scheduled_at],
|
||||
application: doorkeeper_token.application,
|
||||
poll: status_params[:poll],
|
||||
idempotency: request.headers['Idempotency-Key'])
|
||||
|
||||
render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
|
||||
@ -64,7 +55,7 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
|
||||
RemovalWorker.perform_async(@status.id)
|
||||
|
||||
render_empty
|
||||
render json: @status, serializer: REST::StatusSerializer, source_requested: true
|
||||
end
|
||||
|
||||
private
|
||||
@ -73,12 +64,25 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
@status = Status.find(params[:id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404 instead of a 403 error code
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def status_params
|
||||
params.permit(:status, :in_reply_to_id, :sensitive, :spoiler_text, :visibility, :scheduled_at, media_ids: [])
|
||||
params.permit(
|
||||
:status,
|
||||
:in_reply_to_id,
|
||||
:sensitive,
|
||||
:spoiler_text,
|
||||
:visibility,
|
||||
:scheduled_at,
|
||||
media_ids: [],
|
||||
poll: [
|
||||
:multiple,
|
||||
:hide_totals,
|
||||
:expires_in,
|
||||
options: [],
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
|
@ -1,63 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::DirectController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:show]
|
||||
before_action :require_user!, only: [:show]
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@statuses = load_statuses
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_statuses
|
||||
cached_direct_statuses
|
||||
end
|
||||
|
||||
def cached_direct_statuses
|
||||
cache_collection direct_statuses, Status
|
||||
end
|
||||
|
||||
def direct_statuses
|
||||
direct_timeline_statuses
|
||||
end
|
||||
|
||||
def direct_timeline_statuses
|
||||
# this query requires built in pagination.
|
||||
Status.as_direct_timeline(
|
||||
current_account,
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id],
|
||||
true # returns array of cache_ids object
|
||||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:local, :limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_direct_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_direct_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
end
|
@ -14,7 +14,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
|
||||
private
|
||||
|
||||
def load_tag
|
||||
@tag = Tag.find_by(name: params[:id].downcase)
|
||||
@tag = Tag.find_normalized(params[:id])
|
||||
end
|
||||
|
||||
def load_statuses
|
||||
|
17
app/controllers/api/v1/trends_controller.rb
Normal file
17
app/controllers/api/v1/trends_controller.rb
Normal file
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::TrendsController < Api::BaseController
|
||||
before_action :set_tags
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
render json: @tags, each_serializer: REST::TagSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_tags
|
||||
@tags = TrendingTags.get(limit_param(10))
|
||||
end
|
||||
end
|
@ -2,7 +2,7 @@
|
||||
|
||||
class Api::V2::SearchController < Api::V1::SearchController
|
||||
def index
|
||||
@search = Search.new(search)
|
||||
@search = Search.new(search_results)
|
||||
render json: @search, serializer: REST::V2::SearchSerializer
|
||||
end
|
||||
end
|
||||
|
@ -22,6 +22,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
||||
favourite: alerts_enabled,
|
||||
reblog: alerts_enabled,
|
||||
mention: alerts_enabled,
|
||||
poll: alerts_enabled,
|
||||
},
|
||||
}
|
||||
|
||||
@ -57,6 +58,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
||||
end
|
||||
|
||||
def data_params
|
||||
@data_params ||= params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention])
|
||||
@data_params ||= params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention, :poll])
|
||||
end
|
||||
end
|
||||
|
@ -10,12 +10,15 @@ class ApplicationController < ActionController::Base
|
||||
include Localized
|
||||
include UserTrackingConcern
|
||||
include SessionTrackingConcern
|
||||
include CacheConcern
|
||||
include DomainControlHelper
|
||||
|
||||
helper_method :current_account
|
||||
helper_method :current_session
|
||||
helper_method :current_theme
|
||||
helper_method :single_user_mode?
|
||||
helper_method :use_seamless_external_login?
|
||||
helper_method :whitelist_mode?
|
||||
|
||||
rescue_from ActionController::RoutingError, with: :not_found
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
||||
@ -24,7 +27,7 @@ class ApplicationController < ActionController::Base
|
||||
rescue_from Mastodon::NotPermittedError, with: :forbidden
|
||||
|
||||
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
|
||||
before_action :check_user_permissions, if: :user_signed_in?
|
||||
before_action :require_functional!, if: :user_signed_in?
|
||||
|
||||
def raise_not_found
|
||||
raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}"
|
||||
@ -36,6 +39,14 @@ class ApplicationController < ActionController::Base
|
||||
Rails.env.production?
|
||||
end
|
||||
|
||||
def authorized_fetch_mode?
|
||||
ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode
|
||||
end
|
||||
|
||||
def public_fetch_mode?
|
||||
!authorized_fetch_mode?
|
||||
end
|
||||
|
||||
def store_current_location
|
||||
store_location_for(:user, request.url) unless request.format == :json
|
||||
end
|
||||
@ -48,8 +59,8 @@ class ApplicationController < ActionController::Base
|
||||
forbidden unless current_user&.staff?
|
||||
end
|
||||
|
||||
def check_user_permissions
|
||||
forbidden if current_user.disabled? || current_user.account.suspended?
|
||||
def require_functional!
|
||||
redirect_to edit_user_registration_path unless current_user.functional?
|
||||
end
|
||||
|
||||
def after_sign_out_path_for(_resource_or_scope)
|
||||
@ -83,7 +94,7 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def single_user_mode?
|
||||
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
|
||||
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.where('id > 0').exists?
|
||||
end
|
||||
|
||||
def use_seamless_external_login?
|
||||
@ -91,11 +102,15 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def current_account
|
||||
@current_account ||= current_user.try(:account)
|
||||
return @current_account if defined?(@current_account)
|
||||
|
||||
@current_account = current_user&.account
|
||||
end
|
||||
|
||||
def current_session
|
||||
@current_session ||= SessionActivation.find_by(session_id: cookies.signed['_session_id'])
|
||||
return @current_session if defined?(@current_session)
|
||||
|
||||
@current_session = SessionActivation.find_by(session_id: cookies.signed['_session_id']) if cookies.signed['_session_id'].present?
|
||||
end
|
||||
|
||||
def current_theme
|
||||
@ -103,55 +118,10 @@ class ApplicationController < ActionController::Base
|
||||
current_user.setting_theme
|
||||
end
|
||||
|
||||
def cache_collection(raw, klass)
|
||||
return raw unless klass.respond_to?(:with_includes)
|
||||
|
||||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
||||
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
|
||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
||||
|
||||
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
||||
|
||||
unless uncached_ids.empty?
|
||||
uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
|
||||
|
||||
uncached.each_value do |item|
|
||||
Rails.cache.write(item, item)
|
||||
end
|
||||
end
|
||||
|
||||
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
|
||||
end
|
||||
|
||||
def respond_with_error(code)
|
||||
respond_to do |format|
|
||||
format.any { head code }
|
||||
|
||||
format.html do
|
||||
set_locale
|
||||
render "errors/#{code}", layout: 'error', status: code
|
||||
end
|
||||
format.html { render "errors/#{code}", layout: 'error', status: code }
|
||||
end
|
||||
end
|
||||
|
||||
def render_cached_json(cache_key, **options)
|
||||
options[:expires_in] ||= 3.minutes
|
||||
cache_public = options.key?(:public) ? options.delete(:public) : true
|
||||
content_type = options.delete(:content_type) || 'application/json'
|
||||
|
||||
data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do
|
||||
yield.to_json
|
||||
end
|
||||
|
||||
expires_in options[:expires_in], public: cache_public
|
||||
render json: data, content_type: content_type
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = 'Accept'
|
||||
end
|
||||
|
||||
def skip_session!
|
||||
request.session_options[:skip] = true
|
||||
end
|
||||
end
|
||||
|
@ -4,34 +4,15 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
||||
layout 'auth'
|
||||
|
||||
before_action :set_body_classes
|
||||
before_action :set_user, only: [:finish_signup]
|
||||
|
||||
def finish_signup
|
||||
return unless request.patch? && params[:user]
|
||||
|
||||
if @user.update(user_params)
|
||||
@user.skip_reconfirmation!
|
||||
bypass_sign_in(@user)
|
||||
redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions')
|
||||
else
|
||||
@show_errors = true
|
||||
end
|
||||
end
|
||||
skip_before_action :require_functional!
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'lighter'
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email)
|
||||
end
|
||||
|
||||
def after_confirmation_path_for(_resource_name, user)
|
||||
if user.created_by_application && truthy_param?(:redirect_to_app)
|
||||
user.created_by_application.redirect_uri
|
||||
|
@ -27,7 +27,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
if resource.email_verified?
|
||||
root_path
|
||||
else
|
||||
finish_signup_path
|
||||
auth_setup_path(missing_email: '1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -9,6 +9,13 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
before_action :set_sessions, only: [:edit, :update]
|
||||
before_action :set_instance_presenter, only: [:new, :create, :update]
|
||||
before_action :set_body_classes, only: [:new, :create, :edit, :update]
|
||||
before_action :require_not_suspended!, only: [:update]
|
||||
|
||||
skip_before_action :require_functional!, only: [:edit, :update]
|
||||
|
||||
def new
|
||||
super(&:build_invite_request)
|
||||
end
|
||||
|
||||
def destroy
|
||||
not_found
|
||||
@ -24,21 +31,22 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
def build_resource(hash = nil)
|
||||
super(hash)
|
||||
|
||||
resource.locale = I18n.locale
|
||||
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
|
||||
resource.agreement = true
|
||||
resource.locale = I18n.locale
|
||||
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
|
||||
resource.agreement = true
|
||||
resource.current_sign_in_ip = request.remote_ip
|
||||
|
||||
resource.build_account if resource.account.nil?
|
||||
end
|
||||
|
||||
def configure_sign_up_params
|
||||
devise_parameter_sanitizer.permit(:sign_up) do |u|
|
||||
u.permit({ account_attributes: [:username] }, :email, :password, :password_confirmation, :invite_code)
|
||||
u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code)
|
||||
end
|
||||
end
|
||||
|
||||
def after_sign_up_path_for(_resource)
|
||||
new_user_session_path
|
||||
auth_setup_path
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(_resource)
|
||||
@ -64,7 +72,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
end
|
||||
|
||||
def allowed_registrations?
|
||||
Setting.open_registrations || @invite&.valid_for_use?
|
||||
Setting.registrations_mode != 'none' || @invite&.valid_for_use?
|
||||
end
|
||||
|
||||
def invite_code
|
||||
@ -86,7 +94,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
end
|
||||
|
||||
def set_invite
|
||||
@invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
|
||||
invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
|
||||
@invite = invite&.valid_for_use? ? invite : nil
|
||||
end
|
||||
|
||||
def determine_layout
|
||||
@ -96,4 +105,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
def set_sessions
|
||||
@sessions = current_user.session_activations
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
end
|
||||
|
@ -6,8 +6,10 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
layout 'auth'
|
||||
|
||||
skip_before_action :require_no_authentication, only: [:create]
|
||||
skip_before_action :check_user_permissions, only: [:destroy]
|
||||
skip_before_action :require_functional!
|
||||
|
||||
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
|
||||
|
||||
before_action :set_instance_presenter, only: [:new]
|
||||
before_action :set_body_classes
|
||||
|
||||
|
58
app/controllers/auth/setup_controller.rb
Normal file
58
app/controllers/auth/setup_controller.rb
Normal file
@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Auth::SetupController < ApplicationController
|
||||
layout 'auth'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :require_unconfirmed_or_pending!
|
||||
before_action :set_body_classes
|
||||
before_action :set_user
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
flash.now[:notice] = begin
|
||||
if @user.pending?
|
||||
I18n.t('devise.registrations.signed_up_but_pending')
|
||||
else
|
||||
I18n.t('devise.registrations.signed_up_but_unconfirmed')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
# This allows updating the e-mail without entering a password as is required
|
||||
# on the account settings page; however, we only allow this for accounts
|
||||
# that were not confirmed yet
|
||||
|
||||
if @user.update(user_params)
|
||||
redirect_to auth_setup_path, notice: I18n.t('devise.confirmations.send_instructions')
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
helper_method :missing_email?
|
||||
|
||||
private
|
||||
|
||||
def require_unconfirmed_or_pending!
|
||||
redirect_to root_path if current_user.confirmed? && current_user.approved?
|
||||
end
|
||||
|
||||
def set_user
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'lighter'
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email)
|
||||
end
|
||||
|
||||
def missing_email?
|
||||
truthy_param?(:missing_email)
|
||||
end
|
||||
end
|
@ -3,22 +3,19 @@
|
||||
module AccountControllerConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include AccountOwnedConcern
|
||||
|
||||
FOLLOW_PER_PAGE = 12
|
||||
|
||||
included do
|
||||
layout 'public'
|
||||
before_action :set_account
|
||||
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_link_headers
|
||||
before_action :check_account_suspension
|
||||
before_action :set_link_headers, if: -> { request.format.nil? || request.format == :html }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
@ -27,7 +24,6 @@ module AccountControllerConcern
|
||||
response.headers['Link'] = LinkHeader.new(
|
||||
[
|
||||
webfinger_account_link,
|
||||
atom_account_url_link,
|
||||
actor_url_link,
|
||||
]
|
||||
)
|
||||
@ -36,14 +32,7 @@ module AccountControllerConcern
|
||||
def webfinger_account_link
|
||||
[
|
||||
webfinger_account_url,
|
||||
[%w(rel lrdd), %w(type application/xrd+xml)],
|
||||
]
|
||||
end
|
||||
|
||||
def atom_account_url_link
|
||||
[
|
||||
account_url(@account, format: 'atom'),
|
||||
[%w(rel alternate), %w(type application/atom+xml)],
|
||||
[%w(rel lrdd), %w(type application/jrd+json)],
|
||||
]
|
||||
end
|
||||
|
||||
@ -57,8 +46,4 @@ module AccountControllerConcern
|
||||
def webfinger_account_url
|
||||
webfinger_url(resource: @account.to_webfinger_s)
|
||||
end
|
||||
|
||||
def check_account_suspension
|
||||
gone if @account.suspended?
|
||||
end
|
||||
end
|
||||
|
34
app/controllers/concerns/account_owned_concern.rb
Normal file
34
app/controllers/concerns/account_owned_concern.rb
Normal file
@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module AccountOwnedConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json }
|
||||
before_action :set_account, if: :account_required?
|
||||
before_action :check_account_approval, if: :account_required?
|
||||
before_action :check_account_suspension, if: :account_required?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def account_required?
|
||||
true
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(username_param)
|
||||
end
|
||||
|
||||
def username_param
|
||||
params[:account_username]
|
||||
end
|
||||
|
||||
def check_account_approval
|
||||
not_found if @account.local? && @account.user_pending?
|
||||
end
|
||||
|
||||
def check_account_suspension
|
||||
expires_in(3.minutes, public: true) && gone if @account.suspended?
|
||||
end
|
||||
end
|
50
app/controllers/concerns/cache_concern.rb
Normal file
50
app/controllers/concerns/cache_concern.rb
Normal file
@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module CacheConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def render_with_cache(**options)
|
||||
raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
|
||||
|
||||
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
|
||||
expires_in = options.delete(:expires_in) || 3.minutes
|
||||
body = Rails.cache.read(key, raw: true)
|
||||
|
||||
if body
|
||||
render(options.except(:json, :serializer, :each_serializer, :adapter, :fields).merge(json: body))
|
||||
else
|
||||
if block_given?
|
||||
options[:json] = yield
|
||||
elsif options[:json].is_a?(Symbol)
|
||||
options[:json] = send(options[:json])
|
||||
end
|
||||
|
||||
render(options)
|
||||
Rails.cache.write(key, response.body, expires_in: expires_in, raw: true)
|
||||
end
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
|
||||
end
|
||||
|
||||
def cache_collection(raw, klass)
|
||||
return raw unless klass.respond_to?(:with_includes)
|
||||
|
||||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
||||
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
|
||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
||||
|
||||
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
||||
|
||||
unless uncached_ids.empty?
|
||||
uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
|
||||
|
||||
uncached.each_value do |item|
|
||||
Rails.cache.write(item, item)
|
||||
end
|
||||
end
|
||||
|
||||
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
|
||||
end
|
||||
end
|
@ -4,16 +4,19 @@ module Localized
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_locale
|
||||
around_action :set_locale
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_locale
|
||||
I18n.locale = default_locale
|
||||
I18n.locale = current_user.locale if user_signed_in?
|
||||
rescue I18n::InvalidLocale
|
||||
I18n.locale = default_locale
|
||||
locale = current_user.locale if respond_to?(:user_signed_in?) && user_signed_in?
|
||||
locale ||= session[:locale] ||= default_locale
|
||||
locale = default_locale unless I18n.available_locales.include?(locale.to_sym)
|
||||
|
||||
I18n.with_locale(locale) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def default_locale
|
||||
|
@ -5,12 +5,22 @@
|
||||
module SignatureVerification
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include DomainControlHelper
|
||||
|
||||
def require_signature!
|
||||
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
|
||||
end
|
||||
|
||||
def signed_request?
|
||||
request.headers['Signature'].present?
|
||||
end
|
||||
|
||||
def signature_verification_failure_reason
|
||||
return @signature_verification_failure_reason if defined?(@signature_verification_failure_reason)
|
||||
@signature_verification_failure_reason
|
||||
end
|
||||
|
||||
def signature_verification_failure_code
|
||||
@signature_verification_failure_code || 401
|
||||
end
|
||||
|
||||
def signed_request_account
|
||||
@ -43,13 +53,7 @@ module SignatureVerification
|
||||
return
|
||||
end
|
||||
|
||||
account_stoplight = Stoplight("source:#{request.ip}") { account_from_key_id(signature_params['keyId']) }
|
||||
.with_fallback { nil }
|
||||
.with_threshold(1)
|
||||
.with_cool_off_time(5.minutes.seconds)
|
||||
.with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) }
|
||||
|
||||
account = account_stoplight.run
|
||||
account = account_from_key_id(signature_params['keyId'])
|
||||
|
||||
if account.nil?
|
||||
@signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}"
|
||||
@ -62,13 +66,7 @@ module SignatureVerification
|
||||
|
||||
return account unless verify_signature(account, signature, compare_signed_string).nil?
|
||||
|
||||
account_stoplight = Stoplight("source:#{request.ip}") { account.possibly_stale? ? account.refresh! : account_refresh_key(account) }
|
||||
.with_fallback { nil }
|
||||
.with_threshold(1)
|
||||
.with_cool_off_time(5.minutes.seconds)
|
||||
.with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) }
|
||||
|
||||
account = account_stoplight.run
|
||||
account = stoplight_wrap_request { account.possibly_stale? ? account.refresh! : account_refresh_key(account) }
|
||||
|
||||
if account.nil?
|
||||
@signature_verification_failure_reason = "Public key not found for key #{signature_params['keyId']}"
|
||||
@ -135,15 +133,31 @@ module SignatureVerification
|
||||
end
|
||||
|
||||
def account_from_key_id(key_id)
|
||||
domain = key_id.start_with?('acct:') ? key_id.split('@').last : key_id
|
||||
|
||||
if domain_not_allowed?(domain)
|
||||
@signature_verification_failure_code = 403
|
||||
return
|
||||
end
|
||||
|
||||
if key_id.start_with?('acct:')
|
||||
ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, ''))
|
||||
stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, '')) }
|
||||
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
|
||||
account = ActivityPub::TagManager.instance.uri_to_resource(key_id, Account)
|
||||
account ||= ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false)
|
||||
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false) }
|
||||
account
|
||||
end
|
||||
end
|
||||
|
||||
def stoplight_wrap_request(&block)
|
||||
Stoplight("source:#{request.remote_ip}", &block)
|
||||
.with_fallback { nil }
|
||||
.with_threshold(1)
|
||||
.with_cool_off_time(5.minutes.seconds)
|
||||
.with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) }
|
||||
.run
|
||||
end
|
||||
|
||||
def account_refresh_key(account)
|
||||
return if account.local? || !account.activitypub?
|
||||
ActivityPub::FetchRemoteAccountService.new.call(account.uri, only_key: true)
|
||||
|
87
app/controllers/concerns/status_controller_concern.rb
Normal file
87
app/controllers/concerns/status_controller_concern.rb
Normal file
@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module StatusControllerConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
ANCESTORS_LIMIT = 40
|
||||
DESCENDANTS_LIMIT = 60
|
||||
DESCENDANTS_DEPTH_LIMIT = 20
|
||||
|
||||
def create_descendant_thread(starting_depth, statuses)
|
||||
depth = starting_depth + statuses.size
|
||||
|
||||
if depth < DESCENDANTS_DEPTH_LIMIT
|
||||
{
|
||||
statuses: statuses,
|
||||
starting_depth: starting_depth,
|
||||
}
|
||||
else
|
||||
next_status = statuses.pop
|
||||
|
||||
{
|
||||
statuses: statuses,
|
||||
starting_depth: starting_depth,
|
||||
next_status: next_status,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def set_ancestors
|
||||
@ancestors = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : []
|
||||
@next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift
|
||||
end
|
||||
|
||||
def set_descendants
|
||||
@max_descendant_thread_id = params[:max_descendant_thread_id]&.to_i
|
||||
@since_descendant_thread_id = params[:since_descendant_thread_id]&.to_i
|
||||
|
||||
descendants = cache_collection(
|
||||
@status.descendants(
|
||||
DESCENDANTS_LIMIT,
|
||||
current_account,
|
||||
@max_descendant_thread_id,
|
||||
@since_descendant_thread_id,
|
||||
DESCENDANTS_DEPTH_LIMIT
|
||||
),
|
||||
Status
|
||||
)
|
||||
|
||||
@descendant_threads = []
|
||||
|
||||
if descendants.present?
|
||||
statuses = [descendants.first]
|
||||
starting_depth = 0
|
||||
|
||||
descendants.drop(1).each_with_index do |descendant, index|
|
||||
if descendants[index].id == descendant.in_reply_to_id
|
||||
statuses << descendant
|
||||
else
|
||||
@descendant_threads << create_descendant_thread(starting_depth, statuses)
|
||||
|
||||
# The thread is broken, assume it's a reply to the root status
|
||||
starting_depth = 0
|
||||
|
||||
# ... unless we can find its ancestor in one of the already-processed threads
|
||||
@descendant_threads.reverse_each do |descendant_thread|
|
||||
statuses = descendant_thread[:statuses]
|
||||
|
||||
index = statuses.find_index do |thread_status|
|
||||
thread_status.id == descendant.in_reply_to_id
|
||||
end
|
||||
|
||||
if index.present?
|
||||
starting_depth = descendant_thread[:starting_depth] + index + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
statuses = [descendant]
|
||||
end
|
||||
end
|
||||
|
||||
@descendant_threads << create_descendant_thread(starting_depth, statuses)
|
||||
end
|
||||
|
||||
@max_descendant_thread_id = @descendant_threads.pop[:statuses].first.id if descendants.size >= DESCENDANTS_LIMIT
|
||||
end
|
||||
end
|
@ -1,10 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CustomCssController < ApplicationController
|
||||
skip_before_action :store_current_location
|
||||
|
||||
before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
skip_session!
|
||||
expires_in 3.minutes, public: true
|
||||
render plain: Setting.custom_css || '', content_type: 'text/css'
|
||||
end
|
||||
end
|
||||
|
@ -3,7 +3,8 @@
|
||||
class DirectoriesController < ApplicationController
|
||||
layout 'public'
|
||||
|
||||
before_action :check_enabled
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :require_enabled!
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_tag, only: :show
|
||||
before_action :set_tags
|
||||
@ -19,12 +20,12 @@ class DirectoriesController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def check_enabled
|
||||
def require_enabled!
|
||||
return not_found unless Setting.profile_directory
|
||||
end
|
||||
|
||||
def set_tag
|
||||
@tag = Tag.discoverable.find_by!(name: params[:id].downcase)
|
||||
@tag = Tag.discoverable.find_normalized!(params[:id])
|
||||
end
|
||||
|
||||
def set_tags
|
||||
@ -32,7 +33,7 @@ class DirectoriesController < ApplicationController
|
||||
end
|
||||
|
||||
def set_accounts
|
||||
@accounts = Account.discoverable.page(params[:page]).per(40).tap do |query|
|
||||
@accounts = Account.discoverable.by_recent_status.page(params[:page]).per(40).tap do |query|
|
||||
query.merge!(Account.tagged_with(@tag.id)) if @tag
|
||||
end
|
||||
end
|
||||
|
@ -7,11 +7,8 @@ class EmojisController < ApplicationController
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
skip_session!
|
||||
|
||||
render_cached_json(['activitypub', 'emoji', @emoji], content_type: 'application/activity+json') do
|
||||
ActiveModelSerializers::SerializableResource.new(@emoji, serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter)
|
||||
end
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2,10 +2,16 @@
|
||||
|
||||
class FollowerAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
include SignatureVerification
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.user_hides_network?
|
||||
|
||||
follows
|
||||
@ -13,7 +19,9 @@ class FollowerAccountsController < ApplicationController
|
||||
end
|
||||
|
||||
format.json do
|
||||
raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
|
||||
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||
|
||||
render json: collection_presenter,
|
||||
serializer: ActivityPub::CollectionSerializer,
|
||||
@ -29,12 +37,16 @@ class FollowerAccountsController < ApplicationController
|
||||
@follows ||= Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
params[:page].present?
|
||||
end
|
||||
|
||||
def page_url(page)
|
||||
account_followers_url(@account, page: page) unless page.nil?
|
||||
end
|
||||
|
||||
def collection_presenter
|
||||
if params[:page].present?
|
||||
if page_requested?
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_followers_url(@account, page: params.fetch(:page, 1)),
|
||||
type: :ordered,
|
||||
|
@ -2,10 +2,16 @@
|
||||
|
||||
class FollowingAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
include SignatureVerification
|
||||
|
||||
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.user_hides_network?
|
||||
|
||||
follows
|
||||
@ -13,7 +19,9 @@ class FollowingAccountsController < ApplicationController
|
||||
end
|
||||
|
||||
format.json do
|
||||
raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
|
||||
raise Mastodon::NotPermittedError if page_requested? && @account.user_hides_network?
|
||||
|
||||
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
|
||||
|
||||
render json: collection_presenter,
|
||||
serializer: ActivityPub::CollectionSerializer,
|
||||
@ -29,12 +37,16 @@ class FollowingAccountsController < ApplicationController
|
||||
@follows ||= Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
params[:page].present?
|
||||
end
|
||||
|
||||
def page_url(page)
|
||||
account_following_index_url(@account, page: page) unless page.nil?
|
||||
end
|
||||
|
||||
def collection_presenter
|
||||
if params[:page].present?
|
||||
if page_requested?
|
||||
ActivityPub::CollectionPresenter.new(
|
||||
id: account_following_index_url(@account, page: params.fetch(:page, 1)),
|
||||
type: :ordered,
|
||||
|
@ -21,7 +21,7 @@ class HomeController < ApplicationController
|
||||
when 'statuses'
|
||||
status = Status.find_by(id: matches[2])
|
||||
|
||||
if status && (status.public_visibility? || status.unlisted_visibility?)
|
||||
if status&.distributable?
|
||||
redirect_to(ActivityPub::TagManager.instance.url_for(status))
|
||||
return
|
||||
end
|
||||
@ -50,15 +50,15 @@ class HomeController < ApplicationController
|
||||
push_subscription: current_account.user.web_push_subscription(current_session),
|
||||
current_account: current_account,
|
||||
token: current_session.token,
|
||||
admin: Account.find_local(Setting.site_contact_username),
|
||||
admin: Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')),
|
||||
}
|
||||
end
|
||||
|
||||
def default_redirect_path
|
||||
if request.path.start_with?('/web')
|
||||
if request.path.start_with?('/web') || whitelist_mode?
|
||||
new_user_session_path
|
||||
elsif single_user_mode?
|
||||
short_account_path(Account.local.where(suspended: false).first)
|
||||
short_account_path(Account.local.without_suspended.where('id > 0').first)
|
||||
else
|
||||
about_path
|
||||
end
|
||||
|
20
app/controllers/instance_actors_controller.rb
Normal file
20
app/controllers/instance_actors_controller.rb
Normal file
@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class InstanceActorsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
|
||||
def show
|
||||
expires_in 10.minutes, public: true
|
||||
render json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(-99)
|
||||
end
|
||||
|
||||
def restrict_fields_to
|
||||
%i(id type preferred_username inbox public_key endpoints url manually_approves_followers)
|
||||
end
|
||||
end
|
@ -2,6 +2,7 @@
|
||||
|
||||
class IntentsController < ApplicationController
|
||||
before_action :check_uri
|
||||
|
||||
rescue_from Addressable::URI::InvalidURIError, with: :handle_invalid_uri
|
||||
|
||||
def show
|
||||
|
@ -39,7 +39,7 @@ class InvitesController < ApplicationController
|
||||
private
|
||||
|
||||
def invites
|
||||
Invite.where(user: current_user).order(id: :desc)
|
||||
current_user.invites.order(id: :desc)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
|
@ -1,7 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ManifestsController < ApplicationController
|
||||
skip_before_action :store_current_location
|
||||
|
||||
def show
|
||||
render json: InstancePresenter.new, serializer: ManifestSerializer
|
||||
expires_in 3.minutes, public: true
|
||||
render json: InstancePresenter.new, serializer: ManifestSerializer, root: 'instance'
|
||||
end
|
||||
end
|
||||
|
@ -3,8 +3,13 @@
|
||||
class MediaController < ApplicationController
|
||||
include Authorization
|
||||
|
||||
skip_before_action :store_current_location
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :set_media_attachment
|
||||
before_action :verify_permitted_status!
|
||||
before_action :check_playable, only: :player
|
||||
before_action :allow_iframing, only: :player
|
||||
|
||||
content_security_policy only: :player do |p|
|
||||
p.frame_ancestors(false)
|
||||
@ -16,8 +21,6 @@ class MediaController < ApplicationController
|
||||
|
||||
def player
|
||||
@body_classes = 'player'
|
||||
response.headers['X-Frame-Options'] = 'ALLOWALL'
|
||||
raise ActiveRecord::RecordNotFound unless @media_attachment.video? || @media_attachment.gifv?
|
||||
end
|
||||
|
||||
private
|
||||
@ -29,7 +32,14 @@ class MediaController < ApplicationController
|
||||
def verify_permitted_status!
|
||||
authorize @media_attachment.status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404 instead of a 403 error code
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def check_playable
|
||||
not_found unless @media_attachment.larger_media_format?
|
||||
end
|
||||
|
||||
def allow_iframing
|
||||
response.headers['X-Frame-Options'] = 'ALLOWALL'
|
||||
end
|
||||
end
|
||||
|
@ -3,6 +3,10 @@
|
||||
class MediaProxyController < ApplicationController
|
||||
include RoutingHelper
|
||||
|
||||
skip_before_action :store_current_location
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
|
||||
def show
|
||||
RedisLock.acquire(lock_options) do |lock|
|
||||
if lock.acquired?
|
||||
@ -37,6 +41,6 @@ class MediaProxyController < ApplicationController
|
||||
end
|
||||
|
||||
def reject_media?
|
||||
DomainBlock.find_by(domain: @media_attachment.account.domain)&.reject_media?
|
||||
DomainBlock.reject_media?(@media_attachment.account.domain)
|
||||
end
|
||||
end
|
||||
|
@ -5,6 +5,9 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
||||
|
||||
before_action :store_current_location
|
||||
before_action :authenticate_resource_owner!
|
||||
before_action :set_body_classes
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
include Localized
|
||||
|
||||
@ -15,6 +18,10 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
||||
|
||||
private
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def store_current_location
|
||||
store_location_for(:user, request.url)
|
||||
end
|
||||
|
31
app/controllers/public_timelines_controller.rb
Normal file
31
app/controllers/public_timelines_controller.rb
Normal file
@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class PublicTimelinesController < ApplicationController
|
||||
layout 'public'
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :require_enabled!
|
||||
before_action :set_body_classes
|
||||
before_action :set_instance_presenter
|
||||
|
||||
def show
|
||||
@initial_state_json = ActiveModelSerializers::SerializableResource.new(
|
||||
InitialStatePresenter.new(settings: { known_fediverse: Setting.show_known_fediverse_at_about_page }, token: current_session&.token),
|
||||
serializer: InitialStateSerializer
|
||||
).to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_enabled!
|
||||
not_found unless Setting.timeline_preview
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
end
|
104
app/controllers/relationships_controller.rb
Normal file
104
app/controllers/relationships_controller.rb
Normal file
@ -0,0 +1,104 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RelationshipsController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_accounts, only: :show
|
||||
before_action :set_body_classes
|
||||
|
||||
helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
|
||||
|
||||
def show
|
||||
@form = Form::AccountBatch.new
|
||||
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 relationships_path(current_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_accounts
|
||||
@accounts = relationships_scope.page(params[:page]).per(40)
|
||||
end
|
||||
|
||||
def relationships_scope
|
||||
scope = begin
|
||||
if following_relationship?
|
||||
current_account.following.eager_load(:account_stat).reorder(nil)
|
||||
else
|
||||
current_account.followers.eager_load(:account_stat).reorder(nil)
|
||||
end
|
||||
end
|
||||
|
||||
scope.merge!(Follow.recent) if params[:order].blank? || params[:order] == 'recent'
|
||||
scope.merge!(Account.by_recent_status) if params[:order] == 'active'
|
||||
scope.merge!(mutual_relationship_scope) if mutual_relationship?
|
||||
scope.merge!(moved_account_scope) if params[:status] == 'moved'
|
||||
scope.merge!(primary_account_scope) if params[:status] == 'primary'
|
||||
scope.merge!(by_domain_scope) if params[:by_domain].present?
|
||||
scope.merge!(dormant_account_scope) if params[:activity] == 'dormant'
|
||||
|
||||
scope
|
||||
end
|
||||
|
||||
def mutual_relationship_scope
|
||||
Account.where(id: current_account.following)
|
||||
end
|
||||
|
||||
def moved_account_scope
|
||||
Account.where.not(moved_to_account_id: nil)
|
||||
end
|
||||
|
||||
def primary_account_scope
|
||||
Account.where(moved_to_account_id: nil)
|
||||
end
|
||||
|
||||
def dormant_account_scope
|
||||
AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago)))
|
||||
end
|
||||
|
||||
def by_domain_scope
|
||||
Account.where(domain: params[:by_domain])
|
||||
end
|
||||
|
||||
def form_account_batch_params
|
||||
params.require(:form_account_batch).permit(:action, account_ids: [])
|
||||
end
|
||||
|
||||
def following_relationship?
|
||||
params[:relationship].blank? || params[:relationship] == 'following'
|
||||
end
|
||||
|
||||
def mutual_relationship?
|
||||
params[:relationship] == 'mutual'
|
||||
end
|
||||
|
||||
def followed_by_relationship?
|
||||
params[:relationship] == 'followed_by'
|
||||
end
|
||||
|
||||
def current_params
|
||||
params.slice(:page, :status, :relationship, :by_domain, :activity, :order).permit(:page, :status, :relationship, :by_domain, :activity, :order)
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
if params[:unfollow]
|
||||
'unfollow'
|
||||
elsif params[:remove_from_followers]
|
||||
'remove_from_followers'
|
||||
elsif params[:block_domains]
|
||||
'block_domains'
|
||||
end
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
end
|
@ -1,10 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RemoteFollowController < ApplicationController
|
||||
include AccountOwnedConcern
|
||||
|
||||
layout 'modal'
|
||||
|
||||
before_action :set_account
|
||||
before_action :gone, if: :suspended_account?
|
||||
before_action :set_body_classes
|
||||
|
||||
def new
|
||||
@ -32,14 +32,6 @@ class RemoteFollowController < ApplicationController
|
||||
{ acct: session[:remote_follow] }
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find_local!(params[:account_username])
|
||||
end
|
||||
|
||||
def suspended_account?
|
||||
@account.suspended?
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'modal-layout'
|
||||
@hide_header = true
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user