Merge commit '52ab8a59c6e77b6409a7d4d81b15751732b3af91' into glitch-soc/merge-upstream
commit
0fb469e2f3
2
Gemfile
2
Gemfile
|
@ -207,3 +207,5 @@ gem 'net-http', '~> 0.4.0'
|
|||
gem 'rubyzip', '~> 2.3'
|
||||
|
||||
gem 'hcaptcha', '~> 7.1'
|
||||
|
||||
gem 'mail', '~> 2.8'
|
||||
|
|
|
@ -356,7 +356,7 @@ GEM
|
|||
rdoc
|
||||
reline (>= 0.4.2)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.1)
|
||||
json (2.7.2)
|
||||
json-canonicalization (1.0.0)
|
||||
json-jwt (1.15.3.1)
|
||||
activesupport (>= 4.2)
|
||||
|
@ -607,7 +607,7 @@ GEM
|
|||
redlock (1.3.2)
|
||||
redis (>= 3.0.0, < 6.0)
|
||||
regexp_parser (2.9.0)
|
||||
reline (0.4.3)
|
||||
reline (0.5.0)
|
||||
io-console (~> 0.5)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
|
@ -671,7 +671,7 @@ GEM
|
|||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rspec (2.28.0)
|
||||
rubocop-rspec (2.29.1)
|
||||
rubocop (~> 1.40)
|
||||
rubocop-capybara (~> 2.17)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
|
@ -692,7 +692,7 @@ GEM
|
|||
sanitize (6.1.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
scenic (1.7.0)
|
||||
scenic (1.8.0)
|
||||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
selenium-webdriver (4.19.0)
|
||||
|
@ -880,6 +880,7 @@ DEPENDENCIES
|
|||
letter_opener_web (~> 2.0)
|
||||
link_header (~> 0.0)
|
||||
lograge (~> 0.12)
|
||||
mail (~> 2.8)
|
||||
mario-redis-lock (~> 1.2)
|
||||
md-paperclip-azure (~> 2.2)
|
||||
memory_profiler
|
||||
|
|
|
@ -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
|
||||
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: 8080, host: 8080
|
||||
config.vm.network :forwarded_port, guest: 9200, host: 9200
|
||||
|
|
|
@ -3,7 +3,9 @@ import * as html from '../html';
|
|||
describe('html', () => {
|
||||
describe('unescapeHTML', () => {
|
||||
it('returns unescaped HTML', () => {
|
||||
const output = html.unescapeHTML('<p>lorem</p><p>ipsum</p><br><br>');
|
||||
const output = html.unescapeHTML(
|
||||
'<p>lorem</p><p>ipsum</p><br><br>',
|
||||
);
|
||||
expect(output).toEqual('lorem\n\nipsum\n<br>');
|
||||
});
|
||||
});
|
|
@ -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]);
|
||||
});
|
||||
});
|
|
@ -112,9 +112,11 @@
|
|||
border: 0;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
border-width: 1px !important;
|
||||
border-color: $ui-button-background-color;
|
||||
}
|
||||
|
||||
&::-webkit-search-cancel-button {
|
||||
|
|
|
@ -46,11 +46,11 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
def tag
|
||||
|
|
|
@ -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
|
|
@ -13,16 +13,14 @@
|
|||
#
|
||||
|
||||
class CustomFilterKeyword < ApplicationRecord
|
||||
include CustomFilterCache
|
||||
|
||||
belongs_to :custom_filter
|
||||
|
||||
validates :keyword, presence: true
|
||||
|
||||
alias_attribute :phrase, :keyword
|
||||
|
||||
before_save :prepare_cache_invalidation!
|
||||
before_destroy :prepare_cache_invalidation!
|
||||
after_commit :invalidate_cache!
|
||||
|
||||
def to_regex
|
||||
if whole_word?
|
||||
/(?mix:#{to_regex_sb}#{Regexp.escape(keyword)}#{to_regex_eb})/
|
||||
|
@ -40,12 +38,4 @@ class CustomFilterKeyword < ApplicationRecord
|
|||
def to_regex_eb
|
||||
/[[:word:]]\z/.match?(keyword) ? '\b' : ''
|
||||
end
|
||||
|
||||
def prepare_cache_invalidation!
|
||||
custom_filter.prepare_cache_invalidation!
|
||||
end
|
||||
|
||||
def invalidate_cache!
|
||||
custom_filter.invalidate_cache!
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,27 +12,17 @@
|
|||
#
|
||||
|
||||
class CustomFilterStatus < ApplicationRecord
|
||||
include CustomFilterCache
|
||||
|
||||
belongs_to :custom_filter
|
||||
belongs_to :status
|
||||
|
||||
validates :status, uniqueness: { scope: :custom_filter }
|
||||
validate :validate_status_access
|
||||
|
||||
before_save :prepare_cache_invalidation!
|
||||
before_destroy :prepare_cache_invalidation!
|
||||
after_commit :invalidate_cache!
|
||||
|
||||
private
|
||||
|
||||
def validate_status_access
|
||||
errors.add(:status_id, :invalid) unless StatusPolicy.new(custom_filter.account, status).show?
|
||||
end
|
||||
|
||||
def prepare_cache_invalidation!
|
||||
custom_filter.prepare_cache_invalidation!
|
||||
end
|
||||
|
||||
def invalidate_cache!
|
||||
custom_filter.invalidate_cache!
|
||||
end
|
||||
end
|
||||
|
|
|
@ -95,6 +95,8 @@ class User < ApplicationRecord
|
|||
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 :email, presence: true, email_address: true
|
||||
|
||||
validates_with BlacklistedEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? }
|
||||
validates_with EmailMxValidator, if: :validate_email_dns?
|
||||
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
|
||||
|
|
|
@ -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
|
|
@ -4,7 +4,7 @@
|
|||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%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]
|
||||
<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
|
||||
|
@ -24,7 +24,7 @@
|
|||
%tr
|
||||
%td.email-column-td
|
||||
- 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
|
||||
/[if mso]
|
||||
</td></tr></table>
|
||||
|
|
|
@ -68,7 +68,4 @@
|
|||
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
|
||||
%tr
|
||||
%td.email-extra-td
|
||||
= render 'application/mailer/feature', feature: 'control', text_first_on_desktop: true
|
||||
= 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
|
||||
= render partial: 'application/mailer/feature', collection: %w(control audience moderation creativity)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require 'rails_helper'
|
||||
|
||||
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(:end_at) { Time.now.utc }
|
||||
|
@ -11,8 +11,21 @@ describe Admin::Metrics::Dimension::LanguagesDimension do
|
|||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
let(:alice) { Fabricate(:user, locale: 'en', current_sign_in_at: 1.day.ago) }
|
||||
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
|
||||
|
|
|
@ -247,6 +247,12 @@ describe UserMailer do
|
|||
describe '#welcome' do
|
||||
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
|
||||
expect(mail)
|
||||
.to be_present
|
||||
|
|
|
@ -38,6 +38,12 @@ RSpec.describe User do
|
|||
user.save(validate: false)
|
||||
expect(user.valid?).to be true
|
||||
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
|
||||
|
||||
describe 'Normalizations' do
|
||||
|
|
|
@ -137,6 +137,18 @@ RSpec.describe '/api/web/embed' do
|
|||
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
|
||||
let(:call_result) { nil }
|
||||
|
||||
|
|
Loading…
Reference in New Issue