Merge commit '52ab8a59c6e77b6409a7d4d81b15751732b3af91' into glitch-soc/merge-upstream

This commit is contained in:
Claire 2024-05-01 17:30:52 +02:00
commit 0fb469e2f3
18 changed files with 125 additions and 42 deletions

View file

@ -207,3 +207,5 @@ gem 'net-http', '~> 0.4.0'
gem 'rubyzip', '~> 2.3' gem 'rubyzip', '~> 2.3'
gem 'hcaptcha', '~> 7.1' gem 'hcaptcha', '~> 7.1'
gem 'mail', '~> 2.8'

View file

@ -356,7 +356,7 @@ GEM
rdoc rdoc
reline (>= 0.4.2) reline (>= 0.4.2)
jmespath (1.6.2) jmespath (1.6.2)
json (2.7.1) json (2.7.2)
json-canonicalization (1.0.0) json-canonicalization (1.0.0)
json-jwt (1.15.3.1) json-jwt (1.15.3.1)
activesupport (>= 4.2) activesupport (>= 4.2)
@ -607,7 +607,7 @@ GEM
redlock (1.3.2) redlock (1.3.2)
redis (>= 3.0.0, < 6.0) redis (>= 3.0.0, < 6.0)
regexp_parser (2.9.0) regexp_parser (2.9.0)
reline (0.4.3) reline (0.5.0)
io-console (~> 0.5) io-console (~> 0.5)
request_store (1.5.1) request_store (1.5.1)
rack (>= 1.4) rack (>= 1.4)
@ -671,7 +671,7 @@ GEM
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0) rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (2.28.0) rubocop-rspec (2.29.1)
rubocop (~> 1.40) rubocop (~> 1.40)
rubocop-capybara (~> 2.17) rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22) rubocop-factory_bot (~> 2.22)
@ -692,7 +692,7 @@ GEM
sanitize (6.1.0) sanitize (6.1.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
scenic (1.7.0) scenic (1.8.0)
activerecord (>= 4.0.0) activerecord (>= 4.0.0)
railties (>= 4.0.0) railties (>= 4.0.0)
selenium-webdriver (4.19.0) selenium-webdriver (4.19.0)
@ -880,6 +880,7 @@ DEPENDENCIES
letter_opener_web (~> 2.0) letter_opener_web (~> 2.0)
link_header (~> 0.0) link_header (~> 0.0)
lograge (~> 0.12) lograge (~> 0.12)
mail (~> 2.8)
mario-redis-lock (~> 1.2) mario-redis-lock (~> 1.2)
md-paperclip-azure (~> 2.2) md-paperclip-azure (~> 2.2)
memory_profiler memory_profiler

1
Vagrantfile vendored
View file

@ -173,6 +173,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# Otherwise, you can access the site at http://localhost:3000 and http://localhost:4000 , http://localhost:8080 # Otherwise, you can access the site at http://localhost:3000 and http://localhost:4000 , http://localhost:8080
config.vm.network :forwarded_port, guest: 3000, host: 3000 config.vm.network :forwarded_port, guest: 3000, host: 3000
config.vm.network :forwarded_port, guest: 3035, host: 3035
config.vm.network :forwarded_port, guest: 4000, host: 4000 config.vm.network :forwarded_port, guest: 4000, host: 4000
config.vm.network :forwarded_port, guest: 8080, host: 8080 config.vm.network :forwarded_port, guest: 8080, host: 8080
config.vm.network :forwarded_port, guest: 9200, host: 9200 config.vm.network :forwarded_port, guest: 9200, host: 9200

View file

@ -3,7 +3,9 @@ import * as html from '../html';
describe('html', () => { describe('html', () => {
describe('unescapeHTML', () => { describe('unescapeHTML', () => {
it('returns unescaped HTML', () => { it('returns unescaped HTML', () => {
const output = html.unescapeHTML('<p>lorem</p><p>ipsum</p><br>&lt;br&gt;'); const output = html.unescapeHTML(
'<p>lorem</p><p>ipsum</p><br>&lt;br&gt;',
);
expect(output).toEqual('lorem\n\nipsum\n<br>'); expect(output).toEqual('lorem\n\nipsum\n<br>');
}); });
}); });

View file

@ -0,0 +1,24 @@
import { DECIMAL_UNITS, toShortNumber } from '../numbers';
interface TableRow {
input: number;
base: number;
unit: number;
digits: number;
}
describe.each`
input | base | unit | digits
${10_000_000} | ${10} | ${DECIMAL_UNITS.MILLION} | ${0}
${2_789_123} | ${2.789123} | ${DECIMAL_UNITS.MILLION} | ${1}
${12_345_789} | ${12.345789} | ${DECIMAL_UNITS.MILLION} | ${0}
${10_000_000_000} | ${10} | ${DECIMAL_UNITS.BILLION} | ${0}
${12} | ${12} | ${DECIMAL_UNITS.ONE} | ${0}
${123} | ${123} | ${DECIMAL_UNITS.ONE} | ${0}
${1234} | ${1.234} | ${DECIMAL_UNITS.THOUSAND} | ${1}
${6666} | ${6.666} | ${DECIMAL_UNITS.THOUSAND} | ${1}
`('toShortNumber', ({ input, base, unit, digits }: TableRow) => {
test(`correctly formats ${input}`, () => {
expect(toShortNumber(input)).toEqual([base, unit, digits]);
});
});

View file

@ -112,9 +112,11 @@
border: 0; border: 0;
} }
&:focus, &:active,
&:active { &:focus {
outline: none !important; outline: none !important;
border-width: 1px !important;
border-color: $ui-button-background-color;
} }
&::-webkit-search-cancel-button { &::-webkit-search-cancel-button {

View file

@ -46,11 +46,11 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
end end
def earliest_status_id def earliest_status_id
Mastodon::Snowflake.id_at(@start_at, with_random: false) Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false)
end end
def latest_status_id def latest_status_id
Mastodon::Snowflake.id_at(@end_at, with_random: false) Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false)
end end
def tag def tag

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module CustomFilterCache
extend ActiveSupport::Concern
included do
after_commit :invalidate_cache!
before_destroy :prepare_cache_invalidation!
before_save :prepare_cache_invalidation!
delegate(
:invalidate_cache!,
:prepare_cache_invalidation!,
to: :custom_filter
)
end
end

View file

@ -13,16 +13,14 @@
# #
class CustomFilterKeyword < ApplicationRecord class CustomFilterKeyword < ApplicationRecord
include CustomFilterCache
belongs_to :custom_filter belongs_to :custom_filter
validates :keyword, presence: true validates :keyword, presence: true
alias_attribute :phrase, :keyword alias_attribute :phrase, :keyword
before_save :prepare_cache_invalidation!
before_destroy :prepare_cache_invalidation!
after_commit :invalidate_cache!
def to_regex def to_regex
if whole_word? if whole_word?
/(?mix:#{to_regex_sb}#{Regexp.escape(keyword)}#{to_regex_eb})/ /(?mix:#{to_regex_sb}#{Regexp.escape(keyword)}#{to_regex_eb})/
@ -40,12 +38,4 @@ class CustomFilterKeyword < ApplicationRecord
def to_regex_eb def to_regex_eb
/[[:word:]]\z/.match?(keyword) ? '\b' : '' /[[:word:]]\z/.match?(keyword) ? '\b' : ''
end end
def prepare_cache_invalidation!
custom_filter.prepare_cache_invalidation!
end
def invalidate_cache!
custom_filter.invalidate_cache!
end
end end

View file

@ -12,27 +12,17 @@
# #
class CustomFilterStatus < ApplicationRecord class CustomFilterStatus < ApplicationRecord
include CustomFilterCache
belongs_to :custom_filter belongs_to :custom_filter
belongs_to :status belongs_to :status
validates :status, uniqueness: { scope: :custom_filter } validates :status, uniqueness: { scope: :custom_filter }
validate :validate_status_access validate :validate_status_access
before_save :prepare_cache_invalidation!
before_destroy :prepare_cache_invalidation!
after_commit :invalidate_cache!
private private
def validate_status_access def validate_status_access
errors.add(:status_id, :invalid) unless StatusPolicy.new(custom_filter.account, status).show? errors.add(:status_id, :invalid) unless StatusPolicy.new(custom_filter.account, status).show?
end end
def prepare_cache_invalidation!
custom_filter.prepare_cache_invalidation!
end
def invalidate_cache!
custom_filter.invalidate_cache!
end
end end

View file

@ -95,6 +95,8 @@ class User < ApplicationRecord
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? && !Setting.require_invite_text } accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? && !Setting.require_invite_text }
validates :invite_request, presence: true, on: :create, if: :invite_text_required? validates :invite_request, presence: true, on: :create, if: :invite_text_required?
validates :email, presence: true, email_address: true
validates_with BlacklistedEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? } validates_with BlacklistedEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? }
validates_with EmailMxValidator, if: :validate_email_dns? validates_with EmailMxValidator, if: :validate_email_dns?
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
# NOTE: I initially wrote this as `EmailValidator` but it ended up clashing
# with an indirect dependency of ours, `validate_email`, which, turns out,
# has the same approach as we do, but with an extra check disallowing
# single-label domains. Decided to not switch to `validate_email` because
# we do want to allow at least `localhost`.
class EmailAddressValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
value = value.strip
address = Mail::Address.new(value)
record.errors.add(attribute, :invalid) if address.address != value
rescue Mail::Field::FieldError
record.errors.add(attribute, :invalid)
end
end

View file

@ -4,7 +4,7 @@
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
%tr %tr
%td.email-feature-td %td.email-feature-td
.email-desktop-flex{ class: ('email-dir-rtl' if defined?(text_first_on_desktop) && !text_first_on_desktop) } .email-desktop-flex{ class: ('email-dir-rtl' if feature_iteration.index.odd?) }
/[if mso] /[if mso]
<table border="0" cellpadding="0" cellspacing="0" align="center" style="width:100%;" role="presentation"><tr><td style="width:50%; vertical-align:top;"> <table border="0" cellpadding="0" cellspacing="0" align="center" style="width:100%;" role="presentation"><tr><td style="width:50%; vertical-align:top;">
.email-desktop-column .email-desktop-column
@ -24,7 +24,7 @@
%tr %tr
%td.email-column-td %td.email-column-td
- if defined?(feature) - if defined?(feature)
%p{ class: ('email-desktop-text-right' if defined?(text_first_on_desktop) && text_first_on_desktop) } %p{ class: ('email-desktop-text-right' if feature_iteration.index.even?) }
= image_tag frontend_asset_url("images/mailer-new/welcome/feature_#{feature}.png"), alt: '', width: 240, height: 230 = image_tag frontend_asset_url("images/mailer-new/welcome/feature_#{feature}.png"), alt: '', width: 240, height: 230
/[if mso] /[if mso]
</td></tr></table> </td></tr></table>

View file

@ -68,7 +68,4 @@
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
%tr %tr
%td.email-extra-td %td.email-extra-td
= render 'application/mailer/feature', feature: 'control', text_first_on_desktop: true = render partial: 'application/mailer/feature', collection: %w(control audience moderation creativity)
= render 'application/mailer/feature', feature: 'audience', text_first_on_desktop: false
= render 'application/mailer/feature', feature: 'moderation', text_first_on_desktop: true
= render 'application/mailer/feature', feature: 'creativity', text_first_on_desktop: false

View file

@ -3,7 +3,7 @@
require 'rails_helper' require 'rails_helper'
describe Admin::Metrics::Dimension::LanguagesDimension do describe Admin::Metrics::Dimension::LanguagesDimension do
subject(:dimension) { described_class.new(start_at, end_at, limit, params) } subject { described_class.new(start_at, end_at, limit, params) }
let(:start_at) { 2.days.ago } let(:start_at) { 2.days.ago }
let(:end_at) { Time.now.utc } let(:end_at) { Time.now.utc }
@ -11,8 +11,21 @@ describe Admin::Metrics::Dimension::LanguagesDimension do
let(:params) { ActionController::Parameters.new } let(:params) { ActionController::Parameters.new }
describe '#data' do describe '#data' do
it 'runs data query without error' do let(:alice) { Fabricate(:user, locale: 'en', current_sign_in_at: 1.day.ago) }
expect { dimension.data }.to_not raise_error let(:bob) { Fabricate(:user, locale: 'en', current_sign_in_at: 30.days.ago) }
before do
alice.update(current_sign_in_at: 1.day.ago)
bob.update(current_sign_in_at: 30.days.ago)
end
it 'returns locales with sign in counts' do
expect(subject.data.size)
.to eq(1)
expect(subject.data.map(&:symbolize_keys))
.to contain_exactly(
include(key: 'en', value: '1')
)
end end
end end
end end

View file

@ -247,6 +247,12 @@ describe UserMailer do
describe '#welcome' do describe '#welcome' do
let(:mail) { described_class.welcome(receiver) } let(:mail) { described_class.welcome(receiver) }
before do
# This is a bit hacky and low-level but this allows stubbing trending tags
tag_ids = Fabricate.times(5, :tag).pluck(:id)
allow(Trends.tags).to receive(:query).and_return(instance_double(Trends::Query, allowed: Tag.where(id: tag_ids)))
end
it 'renders welcome mail' do it 'renders welcome mail' do
expect(mail) expect(mail)
.to be_present .to be_present

View file

@ -38,6 +38,12 @@ RSpec.describe User do
user.save(validate: false) user.save(validate: false)
expect(user.valid?).to be true expect(user.valid?).to be true
end end
it 'is valid with a localhost e-mail address' do
user = Fabricate.build(:user, email: 'admin@localhost')
user.valid?
expect(user.valid?).to be true
end
end end
describe 'Normalizations' do describe 'Normalizations' do

View file

@ -137,6 +137,18 @@ RSpec.describe '/api/web/embed' do
end end
end end
context 'when sanitizing the fragment fails' do
let(:call_result) { { html: 'ok' } }
before { allow(Sanitize).to receive(:fragment).and_raise(ArgumentError) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
context 'when failing to fetch OEmbed' do context 'when failing to fetch OEmbed' do
let(:call_result) { nil } let(:call_result) { nil }