Merge commit 'b85c387c5c0527b0ad31c27031a09d361826c5fc' into glitch-soc/merge-upstream

Conflicts:
- `config/initializers/content_security_policy.rb`:
  Kept our version, it was not affected by upstream's bug.
This commit is contained in:
Claire
2023-06-10 16:48:01 +02:00
178 changed files with 1616 additions and 1109 deletions

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Admin::Metrics::Measure::InstanceAccountsMeasure < Admin::Metrics::Measure::BaseMeasure
include Admin::Metrics::Measure::QueryHelper
def self.with_params?
true
end
@@ -25,33 +27,25 @@ class Admin::Metrics::Measure::InstanceAccountsMeasure < Admin::Metrics::Measure
nil
end
def perform_data_query
account_matching_sql = begin
if params[:include_subdomains]
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
else
'accounts.domain = $3::text'
end
end
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
end
sql = <<-SQL.squish
def sql_query_string
<<~SQL.squish
SELECT axis.*, (
WITH new_accounts AS (
SELECT accounts.id
FROM accounts
WHERE date_trunc('day', accounts.created_at)::date = axis.period
AND #{account_matching_sql}
AND #{account_domain_sql(params[:include_subdomains])}
)
SELECT count(*) FROM new_accounts
) AS value
FROM (
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
) AS axis
SQL
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
end
def time_period

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Admin::Metrics::Measure::InstanceFollowersMeasure < Admin::Metrics::Measure::BaseMeasure
include Admin::Metrics::Measure::QueryHelper
def self.with_params?
true
end
@@ -25,34 +27,26 @@ class Admin::Metrics::Measure::InstanceFollowersMeasure < Admin::Metrics::Measur
nil
end
def perform_data_query
account_matching_sql = begin
if params[:include_subdomains]
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
else
'accounts.domain = $3::text'
end
end
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
end
sql = <<-SQL.squish
def sql_query_string
<<~SQL.squish
SELECT axis.*, (
WITH new_followers AS (
SELECT follows.id
FROM follows
INNER JOIN accounts ON follows.account_id = accounts.id
WHERE date_trunc('day', follows.created_at)::date = axis.period
AND #{account_matching_sql}
AND #{account_domain_sql(params[:include_subdomains])}
)
SELECT count(*) FROM new_followers
) AS value
FROM (
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
) AS axis
SQL
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
end
def time_period

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Admin::Metrics::Measure::InstanceFollowsMeasure < Admin::Metrics::Measure::BaseMeasure
include Admin::Metrics::Measure::QueryHelper
def self.with_params?
true
end
@@ -25,34 +27,26 @@ class Admin::Metrics::Measure::InstanceFollowsMeasure < Admin::Metrics::Measure:
nil
end
def perform_data_query
account_matching_sql = begin
if params[:include_subdomains]
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
else
'accounts.domain = $3::text'
end
end
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
end
sql = <<-SQL.squish
def sql_query_string
<<~SQL.squish
SELECT axis.*, (
WITH new_follows AS (
SELECT follows.id
FROM follows
INNER JOIN accounts ON follows.target_account_id = accounts.id
WHERE date_trunc('day', follows.created_at)::date = axis.period
AND #{account_matching_sql}
AND #{account_domain_sql(params[:include_subdomains])}
)
SELECT count(*) FROM new_follows
) AS value
FROM (
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
) AS axis
SQL
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
end
def time_period

View File

@@ -1,6 +1,7 @@
# frozen_string_literal: true
class Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure < Admin::Metrics::Measure::BaseMeasure
include Admin::Metrics::Measure::QueryHelper
include ActionView::Helpers::NumberHelper
def self.with_params?
@@ -35,34 +36,26 @@ class Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure < Admin::Metrics:
nil
end
def perform_data_query
account_matching_sql = begin
if params[:include_subdomains]
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
else
'accounts.domain = $3::text'
end
end
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
end
sql = <<-SQL.squish
def sql_query_string
<<~SQL.squish
SELECT axis.*, (
WITH new_media_attachments AS (
SELECT COALESCE(media_attachments.file_file_size, 0) + COALESCE(media_attachments.thumbnail_file_size, 0) AS size
FROM media_attachments
INNER JOIN accounts ON accounts.id = media_attachments.account_id
WHERE date_trunc('day', media_attachments.created_at)::date = axis.period
AND #{account_matching_sql}
AND #{account_domain_sql(params[:include_subdomains])}
)
SELECT SUM(size) FROM new_media_attachments
) AS value
FROM (
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
) AS axis
SQL
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
end
def time_period

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Admin::Metrics::Measure::InstanceReportsMeasure < Admin::Metrics::Measure::BaseMeasure
include Admin::Metrics::Measure::QueryHelper
def self.with_params?
true
end
@@ -25,34 +27,26 @@ class Admin::Metrics::Measure::InstanceReportsMeasure < Admin::Metrics::Measure:
nil
end
def perform_data_query
account_matching_sql = begin
if params[:include_subdomains]
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
else
'accounts.domain = $3::text'
end
end
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
end
sql = <<-SQL.squish
def sql_query_string
<<~SQL.squish
SELECT axis.*, (
WITH new_reports AS (
SELECT reports.id
FROM reports
INNER JOIN accounts ON accounts.id = reports.target_account_id
WHERE date_trunc('day', reports.created_at)::date = axis.period
AND #{account_matching_sql}
AND #{account_domain_sql(params[:include_subdomains])}
)
SELECT count(*) FROM new_reports
) AS value
FROM (
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
) AS axis
SQL
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
end
def time_period

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure::BaseMeasure
include Admin::Metrics::Measure::QueryHelper
def self.with_params?
true
end
@@ -25,35 +27,35 @@ class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure
nil
end
def perform_data_query
account_matching_sql = begin
if params[:include_subdomains]
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $5::text))"
else
'accounts.domain = $5::text'
end
end
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain], earliest_status_id: earliest_status_id, latest_status_id: latest_status_id }]
end
sql = <<-SQL.squish
def sql_query_string
<<~SQL.squish
SELECT axis.*, (
WITH new_statuses AS (
SELECT statuses.id
FROM statuses
INNER JOIN accounts ON accounts.id = statuses.account_id
WHERE statuses.id BETWEEN $3 AND $4
AND #{account_matching_sql}
WHERE statuses.id BETWEEN :earliest_status_id AND :latest_status_id
AND #{account_domain_sql(params[:include_subdomains])}
AND date_trunc('day', statuses.created_at)::date = axis.period
)
SELECT count(*) FROM new_statuses
) AS value
FROM (
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
) AS axis
SQL
end
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, params[:domain]]])
def earliest_status_id
Mastodon::Snowflake.id_at(@start_at, with_random: false)
end
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
def latest_status_id
Mastodon::Snowflake.id_at(@end_at, with_random: false)
end
def time_period

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMeasure
include Admin::Metrics::Measure::QueryHelper
def key
'new_users'
end
@@ -15,8 +17,12 @@ class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMe
User.where(created_at: previous_time_period).count
end
def perform_data_query
sql = <<-SQL.squish
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at }]
end
def sql_query_string
<<~SQL.squish
SELECT axis.*, (
WITH new_users AS (
SELECT users.id
@@ -26,12 +32,8 @@ class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMe
SELECT count(*) FROM new_users
) AS value
FROM (
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
) AS axis
SQL
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at]])
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
end
end

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::BaseMeasure
include Admin::Metrics::Measure::QueryHelper
def key
'opened_reports'
end
@@ -15,8 +17,12 @@ class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::B
Report.where(created_at: previous_time_period).count
end
def perform_data_query
sql = <<-SQL.squish
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at }]
end
def sql_query_string
<<~SQL.squish
SELECT axis.*, (
WITH new_reports AS (
SELECT reports.id
@@ -26,12 +32,8 @@ class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::B
SELECT count(*) FROM new_reports
) AS value
FROM (
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
) AS axis
SQL
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at]])
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
end
end

View File

@@ -0,0 +1,25 @@
# frozen_string_literal: true
module Admin::Metrics::Measure::QueryHelper
protected
def perform_data_query
measurement_data_rows.map { |row| { date: row['period'], value: row['value'].to_s } }
end
def measurement_data_rows
ActiveRecord::Base.connection.select_all(sanitized_sql_string)
end
def sanitized_sql_string
ActiveRecord::Base.sanitize_sql_array(sql_array)
end
def account_domain_sql(include_subdomains)
if include_subdomains
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || :domain::text))"
else
'accounts.domain = :domain::text'
end
end
end

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure::BaseMeasure
include Admin::Metrics::Measure::QueryHelper
def key
'resolved_reports'
end
@@ -15,8 +17,12 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
Report.resolved.where(action_taken_at: previous_time_period).count
end
def perform_data_query
sql = <<-SQL.squish
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at }]
end
def sql_query_string
<<~SQL.squish
SELECT axis.*, (
WITH resolved_reports AS (
SELECT reports.id
@@ -26,12 +32,8 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
SELECT count(*) FROM resolved_reports
) AS value
FROM (
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
) AS axis
SQL
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at]])
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
end
end

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::BaseMeasure
include Admin::Metrics::Measure::QueryHelper
def self.with_params?
true
end
@@ -19,25 +21,33 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
tag.statuses.where('statuses.id BETWEEN ? AND ?', Mastodon::Snowflake.id_at(@start_at - length_of_period, with_random: false), Mastodon::Snowflake.id_at(@end_at - length_of_period, with_random: false)).joins(:account).count('distinct accounts.domain')
end
def perform_data_query
sql = <<-SQL.squish
def sql_array
[sql_query_string, { start_at: @start_at, end_at: @end_at, tag_id: tag.id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id }]
end
def sql_query_string
<<~SQL.squish
SELECT axis.*, (
SELECT count(distinct accounts.domain) AS value
FROM statuses
INNER JOIN statuses_tags ON statuses.id = statuses_tags.status_id
INNER JOIN accounts ON statuses.account_id = accounts.id
WHERE statuses_tags.tag_id = $1
AND statuses.id BETWEEN $2 AND $3
WHERE statuses_tags.tag_id = :tag_id
AND statuses.id BETWEEN :earliest_status_id AND :latest_status_id
AND date_trunc('day', statuses.created_at)::date = axis.day
)
FROM (
SELECT generate_series(date_trunc('day', $4::timestamp)::date, date_trunc('day', $5::timestamp)::date, ('1 day')::interval) AS day
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, ('1 day')::interval) AS day
) as axis
SQL
end
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:id].to_i], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, @start_at], [nil, @end_at]])
def earliest_status_id
Mastodon::Snowflake.id_at(@start_at, with_random: false)
end
rows.map { |row| { date: row['day'], value: row['value'].to_s } }
def latest_status_id
Mastodon::Snowflake.id_at(@end_at, with_random: false)
end
def tag

View File

@@ -7,15 +7,15 @@ class LinkDetailsExtractor
# Some publications wrap their JSON-LD data in their <script> tags
# in commented-out CDATA blocks, they need to be removed before
# attempting to parse JSON
CDATA_JUNK_PATTERN = %r{^[\s]*(
(/\*[\s]*<!\[CDATA\[[\s]*\*/) # Block comment style opening
CDATA_JUNK_PATTERN = %r{^\s*(
(/\*\s*<!\[CDATA\[\s*\*/) # Block comment style opening
|
(//[\s]*<!\[CDATA\[) # Single-line comment style opening
(//\s*<!\[CDATA\[) # Single-line comment style opening
|
(/\*[\s]*\]\]>[\s]*\*/) # Block comment style closing
(/\*\s*\]\]>\s*\*/) # Block comment style closing
|
(//[\s]*\]\]>) # Single-line comment style closing
)[\s]*$}x
(//\s*\]\]>) # Single-line comment style closing
)\s*$}x
class StructuredData
SUPPORTED_TYPES = %w(
@@ -204,7 +204,7 @@ class LinkDetailsExtractor
def host_to_url(str)
return if str.blank?
str.start_with?(/https?:\/\//) ? str : "http://#{str}"
str.start_with?(%r{https?://}) ? str : "http://#{str}"
end
def valid_url_or_nil(str, same_origin_only: false)

View File

@@ -3,7 +3,7 @@
class PlainTextFormatter
include ActionView::Helpers::TextHelper
NEWLINE_TAGS_RE = /(<br \/>|<br>|<\/p>)+/
NEWLINE_TAGS_RE = %r{(<br />|<br>|</p>)+}
attr_reader :text, :local

View File

@@ -7,18 +7,18 @@ class TagManager
include RoutingHelper
def web_domain?(domain)
domain.nil? || domain.gsub(/[\/]/, '').casecmp(Rails.configuration.x.web_domain).zero?
domain.nil? || domain.delete('/').casecmp(Rails.configuration.x.web_domain).zero?
end
def local_domain?(domain)
domain.nil? || domain.gsub(/[\/]/, '').casecmp(Rails.configuration.x.local_domain).zero?
domain.nil? || domain.delete('/').casecmp(Rails.configuration.x.local_domain).zero?
end
def normalize_domain(domain)
return if domain.nil?
uri = Addressable::URI.new
uri.host = domain.gsub(/[\/]/, '')
uri.host = domain.delete('/')
uri.normalized_host
end

View File

@@ -5,7 +5,7 @@ class TextFormatter
include ERB::Util
include RoutingHelper
URL_PREFIX_REGEX = /\A(https?:\/\/(www\.)?|xmpp:)/
URL_PREFIX_REGEX = %r{\A(https?://(www\.)?|xmpp:)}
DEFAULT_REL = %w(nofollow noopener noreferrer).freeze

View File

@@ -13,7 +13,7 @@ class WebfingerResource
case resource
when /\Ahttps?/i
username_from_url
when /\@/
when /@/
username_from_acct
else
raise InvalidRequest

View File

@@ -0,0 +1,67 @@
# frozen_string_literal: true
class Webhooks::PayloadRenderer
class DocumentTraverser
INT_REGEX = /[0-9]+/
def initialize(document)
@document = document.with_indifferent_access
end
def get(path)
value = @document.dig(*parse_path(path))
string = Oj.dump(value)
# We want to make sure people can use the variable inside
# other strings, so it can't be wrapped in quotes.
if value.is_a?(String)
string[1...-1]
else
string
end
end
private
def parse_path(path)
path.split('.').filter_map do |segment|
if segment.match(INT_REGEX)
segment.to_i
else
segment.presence
end
end
end
end
class TemplateParser < Parslet::Parser
rule(:dot) { str('.') }
rule(:digit) { match('[0-9]') }
rule(:property_name) { match('[a-z_]').repeat(1) }
rule(:array_index) { digit.repeat(1) }
rule(:segment) { (property_name | array_index) }
rule(:path) { property_name >> (dot >> segment).repeat }
rule(:variable) { (str('}}').absent? >> path).repeat.as(:variable) }
rule(:expression) { str('{{') >> variable >> str('}}') }
rule(:text) { (str('{{').absent? >> any).repeat(1) }
rule(:text_with_expressions) { (text.as(:text) | expression).repeat.as(:text) }
root(:text_with_expressions)
end
EXPRESSION_REGEXP = /
\{\{
[a-z_]+
(\.
([a-z_]+|[0-9]+)
)*
\}\}
/iox
def initialize(json)
@document = DocumentTraverser.new(Oj.load(json))
end
def render(template)
template.gsub(EXPRESSION_REGEXP) { |match| @document.get(match[2...-2]) }
end
end