From 5fb1c3e934a1a782972ac2732ce7f0208c341ac2 Mon Sep 17 00:00:00 2001 From: Francis Murillo Date: Thu, 15 Dec 2022 14:47:06 +0000 Subject: Revoke all authorized applications on password reset (#21325) * Clear sessions on password change * Rename User::clear_sessions to revoke_access for a clearer meaning * Add reset paassword controller test * Use User.find instead of User.find_for_authentication for reset password test * Use redirect and render for better test meaning in reset password Co-authored-by: Effy Elden --- app/models/user.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'app/models') diff --git a/app/models/user.rb b/app/models/user.rb index 5530a9070..ca98a0afa 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -377,6 +377,15 @@ class User < ApplicationRecord super end + def revoke_access! + Doorkeeper::AccessGrant.by_resource_owner(self).update_all(revoked_at: Time.now.utc) + + Doorkeeper::AccessToken.by_resource_owner(self).in_batches do |batch| + batch.update_all(revoked_at: Time.now.utc) + Web::PushSubscription.where(access_token_id: batch).delete_all + end + end + def reset_password! # First, change password to something random and deactivate all sessions transaction do @@ -385,12 +394,7 @@ class User < ApplicationRecord end # Then, remove all authorized applications and connected push subscriptions - Doorkeeper::AccessGrant.by_resource_owner(self).in_batches.update_all(revoked_at: Time.now.utc) - - Doorkeeper::AccessToken.by_resource_owner(self).in_batches do |batch| - batch.update_all(revoked_at: Time.now.utc) - Web::PushSubscription.where(access_token_id: batch).delete_all - end + revoke_access! # Finally, send a reset password prompt to the user send_reset_password_instructions -- cgit From d412147d02e84cb76b252706a5357fe5d434c3db Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Fri, 16 Dec 2022 01:11:14 +0900 Subject: Save avatar or header correctly even if other one fails (#18465) * Save avatar or header correctly if other one fails * Fix test --- app/models/account.rb | 12 +++++++++--- spec/models/account_spec.rb | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'app/models') diff --git a/app/models/account.rb b/app/models/account.rb index fc7359cfc..a7bda15d3 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -341,9 +341,15 @@ class Account < ApplicationRecord def save_with_optional_media! save! - rescue ActiveRecord::RecordInvalid - self.avatar = nil - self.header = nil + rescue ActiveRecord::RecordInvalid => e + errors = e.record.errors.errors + errors.each do |err| + if err.attribute == :avatar + self.avatar = nil + elsif err.attribute == :header + self.header = nil + end + end save! end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index edae05f9d..c9d782cee 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -160,7 +160,7 @@ RSpec.describe Account, type: :model do expect(account.avatar_remote_url).to eq 'https://remote.test/invalid_avatar' expect(account.header_remote_url).to eq expectation.header_remote_url expect(account.avatar_file_name).to eq nil - expect(account.header_file_name).to eq nil + expect(account.header_file_name).to eq expectation.header_file_name end end end -- cgit From 3656a6b9cc353f7f08a2d8f00c1b3f2fd8e3fb21 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Fri, 16 Dec 2022 01:30:47 +0900 Subject: Add "disabled" user filter for admin/accounts UI (#21282) --- app/models/account_filter.rb | 2 +- app/views/admin/accounts/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app/models') diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index 3a4ac0492..d27bb46fc 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -81,7 +81,7 @@ class AccountFilter when 'suspended' Account.suspended when 'disabled' - accounts_with_users.merge(User.disabled) + accounts_with_users.merge(User.disabled).without_suspended when 'silenced' Account.silenced when 'sensitized' diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml index f33f788ed..d0897221d 100644 --- a/app/views/admin/accounts/index.html.haml +++ b/app/views/admin/accounts/index.html.haml @@ -13,7 +13,7 @@ .filter-subset.filter-subset--with-select %strong= t('admin.accounts.moderation.title') .input.select.optional - = select_tag :status, options_for_select([[t('admin.accounts.moderation.active'), 'active'], [t('admin.accounts.moderation.silenced'), 'silenced'], [t('admin.accounts.moderation.suspended'), 'suspended'], [safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), 'pending']], params[:status]), prompt: I18n.t('generic.all') + = select_tag :status, options_for_select([[t('admin.accounts.moderation.active'), 'active'], [t('admin.accounts.moderation.silenced'), 'silenced'], [t('admin.accounts.moderation.disabled'), 'disabled'], [t('admin.accounts.moderation.suspended'), 'suspended'], [safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), 'pending']], params[:status]), prompt: I18n.t('generic.all') .filter-subset.filter-subset--with-select %strong= t('admin.accounts.role') .input.select.optional -- cgit From 2644a28cb30dfb57b9543dd045657e8ed660876a Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 18:09:48 +0100 Subject: Change remote media files to be downloaded outside of transactions (#21796) --- app/models/media_attachment.rb | 2 ++ .../activitypub/process_status_update_service.rb | 42 ++++++++++++---------- 2 files changed, 26 insertions(+), 18 deletions(-) (limited to 'app/models') diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 51b256482..5916b0b4b 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -210,6 +210,8 @@ class MediaAttachment < ApplicationRecord default_scope { order(id: :asc) } + attr_accessor :skip_download + def local? remote_url.blank? end diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index fad19f87f..11b38ab92 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -45,6 +45,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService create_edits! end + download_media_files! queue_poll_notifications! next unless significant_changes? @@ -66,12 +67,12 @@ class ActivityPub::ProcessStatusUpdateService < BaseService def update_media_attachments! previous_media_attachments = @status.media_attachments.to_a previous_media_attachments_ids = @status.ordered_media_attachment_ids || previous_media_attachments.map(&:id) - next_media_attachments = [] + @next_media_attachments = [] as_array(@json['attachment']).each do |attachment| media_attachment_parser = ActivityPub::Parser::MediaAttachmentParser.new(attachment) - next if media_attachment_parser.remote_url.blank? || next_media_attachments.size > 4 + next if media_attachment_parser.remote_url.blank? || @next_media_attachments.size > 4 begin media_attachment = previous_media_attachments.find { |previous_media_attachment| previous_media_attachment.remote_url == media_attachment_parser.remote_url } @@ -87,34 +88,39 @@ class ActivityPub::ProcessStatusUpdateService < BaseService media_attachment.focus = media_attachment_parser.focus media_attachment.thumbnail_remote_url = media_attachment_parser.thumbnail_remote_url media_attachment.blurhash = media_attachment_parser.blurhash + media_attachment.status_id = @status.id + media_attachment.skip_download = unsupported_media_type?(media_attachment_parser.file_content_type) || skip_download? media_attachment.save! - next_media_attachments << media_attachment - - next if unsupported_media_type?(media_attachment_parser.file_content_type) || skip_download? - - begin - media_attachment.download_file! if media_attachment.remote_url_previously_changed? - media_attachment.download_thumbnail! if media_attachment.thumbnail_remote_url_previously_changed? - media_attachment.save - rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError - RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id) - end + @next_media_attachments << media_attachment rescue Addressable::URI::InvalidURIError => e Rails.logger.debug "Invalid URL in attachment: #{e}" end end - added_media_attachments = next_media_attachments - previous_media_attachments + added_media_attachments = @next_media_attachments - previous_media_attachments - MediaAttachment.where(id: added_media_attachments.map(&:id)).update_all(status_id: @status.id) - - @status.ordered_media_attachment_ids = next_media_attachments.map(&:id) - @status.media_attachments.reload + @status.ordered_media_attachment_ids = @next_media_attachments.map(&:id) @media_attachments_changed = true if @status.ordered_media_attachment_ids != previous_media_attachments_ids end + def download_media_files! + @next_media_attachments.each do |media_attachment| + next if media_attachment.skip_download + + media_attachment.download_file! if media_attachment.remote_url_previously_changed? + media_attachment.download_thumbnail! if media_attachment.thumbnail_remote_url_previously_changed? + media_attachment.save + rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError + RedownloadMediaWorker.perform_in(rand(30..600).seconds, media_attachment.id) + rescue Seahorse::Client::NetworkingError => e + Rails.logger.warn "Error storing media attachment: #{e}" + end + + @status.media_attachments.reload + end + def update_poll!(allow_significant_changes: true) previous_poll = @status.preloadable_poll @previous_expires_at = previous_poll&.expires_at -- cgit From 70415714f14e067aba518a105c96475db31fa124 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 15 Dec 2022 18:50:11 +0100 Subject: Add follow request banner on account header (#20785) * Add requested_by to relationship maps * Display whether an account has requested to follow you on their profile --- .../account/components/follow_request_note.js | 37 ++++++++++++++++++++++ .../mastodon/features/account/components/header.js | 3 ++ .../containers/follow_request_note_container.js | 15 +++++++++ app/javascript/mastodon/reducers/relationships.js | 11 +++++++ app/javascript/styles/mastodon/components.scss | 32 ++++++++++++++++++- app/models/concerns/account_interactions.rb | 4 +++ app/presenters/account_relationships_presenter.rb | 6 +++- app/serializers/rest/relationship_serializer.rb | 8 +++-- spec/models/account_spec.rb | 6 ++++ .../account_relationships_presenter_spec.rb | 9 ++++++ 10 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 app/javascript/mastodon/features/account/components/follow_request_note.js create mode 100644 app/javascript/mastodon/features/account/containers/follow_request_note_container.js (limited to 'app/models') diff --git a/app/javascript/mastodon/features/account/components/follow_request_note.js b/app/javascript/mastodon/features/account/components/follow_request_note.js new file mode 100644 index 000000000..300ae4266 --- /dev/null +++ b/app/javascript/mastodon/features/account/components/follow_request_note.js @@ -0,0 +1,37 @@ +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { FormattedMessage } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Icon from 'mastodon/components/icon'; + +export default class FollowRequestNote extends ImmutablePureComponent { + + static propTypes = { + account: ImmutablePropTypes.map.isRequired, + }; + + render () { + const { account, onAuthorize, onReject } = this.props; + + return ( +
+
+ }} /> +
+ +
+ + + +
+
+ ); + } + +} diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index f117412be..dddbf4dd4 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -14,6 +14,7 @@ import ShortNumber from 'mastodon/components/short_number'; import { NavLink } from 'react-router-dom'; import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import AccountNoteContainer from '../containers/account_note_container'; +import FollowRequestNoteContainer from '../containers/follow_request_note_container'; import { PERMISSION_MANAGE_USERS } from 'mastodon/permissions'; import { Helmet } from 'react-helmet'; @@ -311,6 +312,8 @@ class Header extends ImmutablePureComponent { return (
+ {!(suspended || hidden || account.get('moved')) && account.getIn(['relationship', 'requested_by']) && } +
{!suspended && info} diff --git a/app/javascript/mastodon/features/account/containers/follow_request_note_container.js b/app/javascript/mastodon/features/account/containers/follow_request_note_container.js new file mode 100644 index 000000000..c33c3de59 --- /dev/null +++ b/app/javascript/mastodon/features/account/containers/follow_request_note_container.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux'; +import FollowRequestNote from '../components/follow_request_note'; +import { authorizeFollowRequest, rejectFollowRequest } from 'mastodon/actions/accounts'; + +const mapDispatchToProps = (dispatch, { account }) => ({ + onAuthorize () { + dispatch(authorizeFollowRequest(account.get('id'))); + }, + + onReject () { + dispatch(rejectFollowRequest(account.get('id'))); + }, +}); + +export default connect(null, mapDispatchToProps)(FollowRequestNote); diff --git a/app/javascript/mastodon/reducers/relationships.js b/app/javascript/mastodon/reducers/relationships.js index 53949258a..850ece351 100644 --- a/app/javascript/mastodon/reducers/relationships.js +++ b/app/javascript/mastodon/reducers/relationships.js @@ -1,3 +1,6 @@ +import { + NOTIFICATIONS_UPDATE, +} from '../actions/notifications'; import { ACCOUNT_FOLLOW_SUCCESS, ACCOUNT_FOLLOW_REQUEST, @@ -12,6 +15,8 @@ import { ACCOUNT_PIN_SUCCESS, ACCOUNT_UNPIN_SUCCESS, RELATIONSHIPS_FETCH_SUCCESS, + FOLLOW_REQUEST_AUTHORIZE_SUCCESS, + FOLLOW_REQUEST_REJECT_SUCCESS, } from '../actions/accounts'; import { DOMAIN_BLOCK_SUCCESS, @@ -44,6 +49,12 @@ const initialState = ImmutableMap(); export default function relationships(state = initialState, action) { switch(action.type) { + case FOLLOW_REQUEST_AUTHORIZE_SUCCESS: + return state.setIn([action.id, 'followed_by'], true).setIn([action.id, 'requested_by'], false); + case FOLLOW_REQUEST_REJECT_SUCCESS: + return state.setIn([action.id, 'followed_by'], false).setIn([action.id, 'requested_by'], false); + case NOTIFICATIONS_UPDATE: + return action.notification.type === 'follow_request' ? state.setIn([action.notification.account.id, 'requested_by'], true) : state; case ACCOUNT_FOLLOW_REQUEST: return state.getIn([action.id, 'following']) ? state : state.setIn([action.id, action.locked ? 'requested' : 'following'], true); case ACCOUNT_FOLLOW_FAIL: diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 15fc6aa69..6a22f6009 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -166,6 +166,30 @@ &:disabled { opacity: 0.5; } + + &.button--confirmation { + color: $valid-value-color; + border-color: $valid-value-color; + + &:active, + &:focus, + &:hover { + background: $valid-value-color; + color: $primary-text-color; + } + } + + &.button--destructive { + color: $error-value-color; + border-color: $error-value-color; + + &:active, + &:focus, + &:hover { + background: $error-value-color; + color: $primary-text-color; + } + } } &.button--block { @@ -6722,7 +6746,8 @@ noscript { } } -.moved-account-banner { +.moved-account-banner, +.follow-request-banner { padding: 20px; background: lighten($ui-base-color, 4%); display: flex; @@ -6745,6 +6770,7 @@ noscript { justify-content: space-between; align-items: center; gap: 15px; + width: 100%; } .detailed-status__display-name { @@ -6752,6 +6778,10 @@ noscript { } } +.follow-request-banner .button { + width: 100%; +} + .column-inline-form { padding: 15px; display: flex; diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index 15c49f2fe..de8bf338f 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -44,6 +44,10 @@ module AccountInteractions end end + def requested_by_map(target_account_ids, account_id) + follow_mapping(FollowRequest.where(account_id: target_account_ids, target_account_id: account_id), :account_id) + end + def endorsed_map(target_account_ids, account_id) follow_mapping(AccountPin.where(account_id: account_id, target_account_id: target_account_ids), :target_account_id) end diff --git a/app/presenters/account_relationships_presenter.rb b/app/presenters/account_relationships_presenter.rb index d662380f6..ab8bac412 100644 --- a/app/presenters/account_relationships_presenter.rb +++ b/app/presenters/account_relationships_presenter.rb @@ -2,7 +2,7 @@ class AccountRelationshipsPresenter attr_reader :following, :followed_by, :blocking, :blocked_by, - :muting, :requested, :domain_blocking, + :muting, :requested, :requested_by, :domain_blocking, :endorsed, :account_note def initialize(account_ids, current_account_id, **options) @@ -15,6 +15,7 @@ class AccountRelationshipsPresenter @blocked_by = cached[:blocked_by].merge(Account.blocked_by_map(@uncached_account_ids, @current_account_id)) @muting = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id)) @requested = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id)) + @requested_by = cached[:requested_by].merge(Account.requested_by_map(@uncached_account_ids, @current_account_id)) @domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id)) @endorsed = cached[:endorsed].merge(Account.endorsed_map(@uncached_account_ids, @current_account_id)) @account_note = cached[:account_note].merge(Account.account_note_map(@uncached_account_ids, @current_account_id)) @@ -27,6 +28,7 @@ class AccountRelationshipsPresenter @blocked_by.merge!(options[:blocked_by_map] || {}) @muting.merge!(options[:muting_map] || {}) @requested.merge!(options[:requested_map] || {}) + @requested_by.merge!(options[:requested_by_map] || {}) @domain_blocking.merge!(options[:domain_blocking_map] || {}) @endorsed.merge!(options[:endorsed_map] || {}) @account_note.merge!(options[:account_note_map] || {}) @@ -44,6 +46,7 @@ class AccountRelationshipsPresenter blocked_by: {}, muting: {}, requested: {}, + requested_by: {}, domain_blocking: {}, endorsed: {}, account_note: {}, @@ -73,6 +76,7 @@ class AccountRelationshipsPresenter blocked_by: { account_id => blocked_by[account_id] }, muting: { account_id => muting[account_id] }, requested: { account_id => requested[account_id] }, + requested_by: { account_id => requested_by[account_id] }, domain_blocking: { account_id => domain_blocking[account_id] }, endorsed: { account_id => endorsed[account_id] }, account_note: { account_id => account_note[account_id] }, diff --git a/app/serializers/rest/relationship_serializer.rb b/app/serializers/rest/relationship_serializer.rb index 31fc60eb2..b53387401 100644 --- a/app/serializers/rest/relationship_serializer.rb +++ b/app/serializers/rest/relationship_serializer.rb @@ -2,8 +2,8 @@ class REST::RelationshipSerializer < ActiveModel::Serializer attributes :id, :following, :showing_reblogs, :notifying, :languages, :followed_by, - :blocking, :blocked_by, :muting, :muting_notifications, :requested, - :domain_blocking, :endorsed, :note + :blocking, :blocked_by, :muting, :muting_notifications, + :requested, :requested_by, :domain_blocking, :endorsed, :note def id object.id.to_s @@ -54,6 +54,10 @@ class REST::RelationshipSerializer < ActiveModel::Serializer instance_options[:relationships].requested[object.id] ? true : false end + def requested_by + instance_options[:relationships].requested_by[object.id] ? true : false + end + def domain_blocking instance_options[:relationships].domain_blocking[object.id] || false end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index c9d782cee..6cd769dc8 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -658,6 +658,12 @@ RSpec.describe Account, type: :model do end end + describe '.requested_by_map' do + it 'returns an hash' do + expect(Account.requested_by_map([], 1)).to be_a Hash + end + end + describe 'MENTION_RE' do subject { Account::MENTION_RE } diff --git a/spec/presenters/account_relationships_presenter_spec.rb b/spec/presenters/account_relationships_presenter_spec.rb index edfbbb354..8a485d2b9 100644 --- a/spec/presenters/account_relationships_presenter_spec.rb +++ b/spec/presenters/account_relationships_presenter_spec.rb @@ -10,6 +10,7 @@ RSpec.describe AccountRelationshipsPresenter do allow(Account).to receive(:blocking_map).with(account_ids, current_account_id).and_return(default_map) allow(Account).to receive(:muting_map).with(account_ids, current_account_id).and_return(default_map) allow(Account).to receive(:requested_map).with(account_ids, current_account_id).and_return(default_map) + allow(Account).to receive(:requested_by_map).with(account_ids, current_account_id).and_return(default_map) allow(Account).to receive(:domain_blocking_map).with(account_ids, current_account_id).and_return(default_map) end @@ -71,6 +72,14 @@ RSpec.describe AccountRelationshipsPresenter do end end + context 'options[:requested_by_map] is set' do + let(:options) { { requested_by_map: { 6 => true } } } + + it 'sets @requested merged with default_map and options[:requested_by_map]' do + expect(presenter.requested_by).to eq default_map.merge(options[:requested_by_map]) + end + end + context 'options[:domain_blocking_map] is set' do let(:options) { { domain_blocking_map: { 7 => true } } } -- cgit From 115ab2869b2742f0cc68116a8c03359d220fd608 Mon Sep 17 00:00:00 2001 From: Partho Ghosh Date: Tue, 3 Jan 2023 17:12:48 -0800 Subject: Fix ・ detection in hashtag regex to construct hashtag correctly (#22888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix ・ detection in hashtag regex to construct hashtag correctly * Fixed rubocop liniting issues * More rubocop linting fix --- app/models/tag.rb | 22 ++++++++++++++----- spec/models/tag_spec.rb | 57 +++++++++++++++++++++++++++---------------------- 2 files changed, 49 insertions(+), 30 deletions(-) (limited to 'app/models') diff --git a/app/models/tag.rb b/app/models/tag.rb index b66f85423..47a05d00a 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -26,8 +26,12 @@ class Tag < ApplicationRecord has_many :featured_tags, dependent: :destroy, inverse_of: :tag has_many :followers, through: :passive_relationships, source: :account - HASHTAG_SEPARATORS = "_\u00B7\u200c" - HASHTAG_NAME_PAT = "([[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}][[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_])|([[:word:]_]*[[:alpha:]][[:word:]_]*)" + HASHTAG_SEPARATORS = "_\u00B7\u30FB\u200c" + HASHTAG_FIRST_SEQUENCE_CHUNK_ONE = "[[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}]" + HASHTAG_FIRST_SEQUENCE_CHUNK_TWO = "[[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_]" + HASHTAG_FIRST_SEQUENCE = "(#{HASHTAG_FIRST_SEQUENCE_CHUNK_ONE}#{HASHTAG_FIRST_SEQUENCE_CHUNK_TWO})" + HASTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' + HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASTAG_LAST_SEQUENCE}" HASHTAG_RE = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_PAT})/i HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i @@ -45,7 +49,11 @@ class Tag < ApplicationRecord scope :listable, -> { where(listable: [true, nil]) } scope :trendable, -> { Setting.trendable_by_default ? where(trendable: [true, nil]) : where(trendable: true) } scope :not_trendable, -> { where(trendable: false) } - scope :recently_used, ->(account) { joins(:statuses).where(statuses: { id: account.statuses.select(:id).limit(1000) }).group(:id).order(Arel.sql('count(*) desc')) } + scope :recently_used, ->(account) { + joins(:statuses) + .where(statuses: { id: account.statuses.select(:id).limit(1000) }) + .group(:id).order(Arel.sql('count(*) desc')) + } scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(Tag.normalize(term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index update_index('tags', :self) @@ -105,7 +113,8 @@ class Tag < ApplicationRecord names = Array(name_or_names).map { |str| [normalize(str), str] }.uniq(&:first) names.map do |(normalized_name, display_name)| - tag = matching_name(normalized_name).first || create(name: normalized_name, display_name: display_name.gsub(HASHTAG_INVALID_CHARS_RE, '')) + tag = matching_name(normalized_name).first || create(name: normalized_name, + display_name: display_name.gsub(HASHTAG_INVALID_CHARS_RE, '')) yield tag if block_given? @@ -154,6 +163,9 @@ class Tag < ApplicationRecord end def validate_display_name_change - errors.add(:display_name, I18n.t('tags.does_not_match_previous_name')) unless HashtagNormalizer.new.normalize(display_name).casecmp(name.mb_chars).zero? + unless HashtagNormalizer.new.normalize(display_name).casecmp(name.mb_chars).zero? + errors.add(:display_name, + I18n.t('tags.does_not_match_previous_name')) + end end end diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index b16f99a79..102d2f625 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -1,21 +1,22 @@ +# frozen_string_literal: true require 'rails_helper' -RSpec.describe Tag, type: :model do +RSpec.describe Tag do describe 'validations' do it 'invalid with #' do - expect(Tag.new(name: '#hello_world')).to_not be_valid + expect(described_class.new(name: '#hello_world')).not_to be_valid end it 'invalid with .' do - expect(Tag.new(name: '.abcdef123')).to_not be_valid + expect(described_class.new(name: '.abcdef123')).not_to be_valid end it 'invalid with spaces' do - expect(Tag.new(name: 'hello world')).to_not be_valid + expect(described_class.new(name: 'hello world')).not_to be_valid end it 'valid with aesthetic' do - expect(Tag.new(name: 'aesthetic')).to be_valid + expect(described_class.new(name: 'aesthetic')).to be_valid end end @@ -62,6 +63,10 @@ RSpec.describe Tag, type: :model do expect(subject.match('hello #one·two·three').to_s).to eq ' #one·two·three' end + it 'matches ・unicode in ぼっち・ざ・ろっく correctly' do + expect(subject.match('testing #ぼっち・ざ・ろっく').to_s).to eq ' #ぼっち・ざ・ろっく' + end + it 'matches ZWNJ' do expect(subject.match('just add #نرم‌افزار and').to_s).to eq ' #نرم‌افزار' end @@ -89,44 +94,46 @@ RSpec.describe Tag, type: :model do describe '.find_normalized' do it 'returns tag for a multibyte case-insensitive name' do upcase_string = 'abcABCabcABCやゆよ' - downcase_string = 'abcabcabcabcやゆよ'; + downcase_string = 'abcabcabcabcやゆよ' tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string)) - expect(Tag.find_normalized(upcase_string)).to eq tag + expect(described_class.find_normalized(upcase_string)).to eq tag end end describe '.matches_name' do it 'returns tags for multibyte case-insensitive names' do upcase_string = 'abcABCabcABCやゆよ' - downcase_string = 'abcabcabcabcやゆよ'; + downcase_string = 'abcabcabcabcやゆよ' tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string)) - expect(Tag.matches_name(upcase_string)).to eq [tag] + expect(described_class.matches_name(upcase_string)).to eq [tag] end it 'uses the LIKE operator' do - expect(Tag.matches_name('100%abc').to_sql).to eq %q[SELECT "tags".* FROM "tags" WHERE LOWER("tags"."name") LIKE LOWER('100abc%')] + result = %q[SELECT "tags".* FROM "tags" WHERE LOWER("tags"."name") LIKE LOWER('100abc%')] + expect(described_class.matches_name('100%abc').to_sql).to eq result end end describe '.matching_name' do it 'returns tags for multibyte case-insensitive names' do upcase_string = 'abcABCabcABCやゆよ' - downcase_string = 'abcabcabcabcやゆよ'; + downcase_string = 'abcabcabcabcやゆよ' tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string)) - expect(Tag.matching_name(upcase_string)).to eq [tag] + expect(described_class.matching_name(upcase_string)).to eq [tag] end end describe '.find_or_create_by_names' do + let(:upcase_string) { 'abcABCabcABCやゆよ' } + let(:downcase_string) { 'abcabcabcabcやゆよ' } + it 'runs a passed block once per tag regardless of duplicates' do - upcase_string = 'abcABCabcABCやゆよ' - downcase_string = 'abcabcabcabcやゆよ'; - count = 0 + count = 0 - Tag.find_or_create_by_names([upcase_string, downcase_string]) do |tag| + described_class.find_or_create_by_names([upcase_string, downcase_string]) do |_tag| count += 1 end @@ -136,28 +143,28 @@ RSpec.describe Tag, type: :model do describe '.search_for' do it 'finds tag records with matching names' do - tag = Fabricate(:tag, name: "match") - _miss_tag = Fabricate(:tag, name: "miss") + tag = Fabricate(:tag, name: 'match') + _miss_tag = Fabricate(:tag, name: 'miss') - results = Tag.search_for("match") + results = described_class.search_for('match') expect(results).to eq [tag] end it 'finds tag records in case insensitive' do - tag = Fabricate(:tag, name: "MATCH") - _miss_tag = Fabricate(:tag, name: "miss") + tag = Fabricate(:tag, name: 'MATCH') + _miss_tag = Fabricate(:tag, name: 'miss') - results = Tag.search_for("match") + results = described_class.search_for('match') expect(results).to eq [tag] end it 'finds the exact matching tag as the first item' do - similar_tag = Fabricate(:tag, name: "matchlater", reviewed_at: Time.now.utc) - tag = Fabricate(:tag, name: "match", reviewed_at: Time.now.utc) + similar_tag = Fabricate(:tag, name: 'matchlater', reviewed_at: Time.now.utc) + tag = Fabricate(:tag, name: 'match', reviewed_at: Time.now.utc) - results = Tag.search_for("match") + results = described_class.search_for('match') expect(results).to eq [tag, similar_tag] end -- cgit From 8eb29741b440e3eeac297ec89a49a3fb1c9deb8d Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Thu, 5 Jan 2023 20:29:49 +0800 Subject: Add webhook `account.approved` (#22938) * Webhook `account.approved` when preparing new user * Update Webhook.EVENTS --- app/models/user.rb | 1 + app/models/webhook.rb | 1 + 2 files changed, 2 insertions(+) (limited to 'app/models') diff --git a/app/models/user.rb b/app/models/user.rb index ca98a0afa..2a42ffaa1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -489,6 +489,7 @@ class User < ApplicationRecord BootstrapTimelineWorker.perform_async(account_id) ActivityTracker.increment('activity:accounts:local') UserMailer.welcome(self).deliver_later + TriggerWebhookWorker.perform_async('account.approved', 'Account', account_id) end def prepare_returning_user! diff --git a/app/models/webhook.rb b/app/models/webhook.rb index 431edd75d..4aafb1257 100644 --- a/app/models/webhook.rb +++ b/app/models/webhook.rb @@ -15,6 +15,7 @@ class Webhook < ApplicationRecord EVENTS = %w( + account.approved account.created report.created ).freeze -- cgit From 3654c945832405267e80fe24b14f7e1d74c395ba Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 5 Jan 2023 13:33:33 +0100 Subject: Strip spaces around URL when adding a relay (#22655) * Strip spaces around URL when adding a relay Fixes #22650 * Gracefuly handle URL parsing errors in URL validator --- app/models/relay.rb | 5 +++++ app/validators/url_validator.rb | 2 ++ 2 files changed, 7 insertions(+) (limited to 'app/models') diff --git a/app/models/relay.rb b/app/models/relay.rb index d6ddd30ed..c66bfe4ff 100644 --- a/app/models/relay.rb +++ b/app/models/relay.rb @@ -18,6 +18,7 @@ class Relay < ApplicationRecord scope :enabled, -> { accepted } + before_validation :strip_url before_destroy :ensure_disabled alias enabled? accepted? @@ -74,4 +75,8 @@ class Relay < ApplicationRecord def ensure_disabled disable! if enabled? end + + def strip_url + inbox_url&.strip! + end end diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb index 75d1edb87..a90fb6958 100644 --- a/app/validators/url_validator.rb +++ b/app/validators/url_validator.rb @@ -10,5 +10,7 @@ class URLValidator < ActiveModel::EachValidator def compliant?(url) parsed_url = Addressable::URI.parse(url) parsed_url && %w(http https).include?(parsed_url.scheme) && parsed_url.host + rescue Addressable::URI::InvalidURIError + false end end -- cgit From acec1fb745456dd3093ac31291c3757e55f39160 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 5 Jan 2023 13:42:03 +0100 Subject: Fix site upload validations (#22479) * Fix site settings media upload handling of DimensionsValidationError Fixes #22234 * Fix underlying validations not being performed for site uploads --- app/models/form/admin_settings.rb | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'app/models') diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 431d33bcd..dc6cc5ed3 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -66,6 +66,7 @@ class Form::AdminSettings validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks_rationale) } validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) } validates :site_short_description, length: { maximum: 200 }, if: -> { defined?(@site_short_description) } + validate :validate_site_uploads KEYS.each do |key| define_method(key) do @@ -87,11 +88,16 @@ class Form::AdminSettings define_method("#{key}=") do |file| value = public_send(key) value.file = file + rescue Mastodon::DimensionsValidationError => e + errors.add(key.to_sym, e.message) end end def save - return false unless valid? + # NOTE: Annoyingly, files are processed and can error out before + # validations are called, and `valid?` clears errors… + # So for now, return early if errors aren't empty. + return false unless errors.empty? && valid? KEYS.each do |key| next unless instance_variable_defined?("@#{key}") @@ -116,4 +122,16 @@ class Form::AdminSettings value end end + + def validate_site_uploads + UPLOAD_KEYS.each do |key| + next unless instance_variable_defined?("@#{key}") + upload = instance_variable_get("@#{key}") + next if upload.valid? + + upload.errors.each do |error| + errors.import(error, attribute: key) + end + end + end end -- cgit From 264655c53a8e86646adc60b5d83f762ab87ecee4 Mon Sep 17 00:00:00 2001 From: Darius Kazemi Date: Thu, 5 Jan 2023 22:35:52 -0800 Subject: Fix account search not returning followed accounts first (#22956) * Make autosuggest for mentions return followed accounts first This makes it so that (when elasticsearch is disabled) when a user types '@foo' in the compose box, they are first going to get accounts they follow ordered by the ranking algorithm, and then second they will get accounts they do not follow, also ordered by the ranking algorithm. This makes behavior more consistent with user expectation and also with results when elasticsearch is enabled. * Fix ranking order to correct direction * One more fixup per @gargron suggestion * Tweak to ranking to no longer include following modifier --- app/models/account.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'app/models') diff --git a/app/models/account.rb b/app/models/account.rb index a7bda15d3..b27fc748f 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -513,7 +513,8 @@ class Account < ApplicationRecord <<-SQL.squish SELECT accounts.*, - (count(f.id) + 1) * #{BOOST} * ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank + #{BOOST} * ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank, + count(f.id) AS followed FROM accounts LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = :id) OR (accounts.id = f.target_account_id AND f.account_id = :id) LEFT JOIN users ON accounts.id = users.account_id @@ -523,7 +524,7 @@ class Account < ApplicationRecord AND accounts.moved_to_account_id IS NULL AND (accounts.domain IS NOT NULL OR (users.approved = TRUE AND users.confirmed_at IS NOT NULL)) GROUP BY accounts.id, s.id - ORDER BY rank DESC + ORDER BY followed DESC, rank DESC LIMIT :limit OFFSET :offset SQL end -- cgit From ae62e5fa533831c936b7bbeb12f5b7605125ce54 Mon Sep 17 00:00:00 2001 From: Kaspar V Date: Wed, 11 Jan 2023 21:57:24 +0100 Subject: Fix/remove calling private method with send in model (#22951) * fix(status): remove send usage for private unlink_from_conversations - make unlink_from_conversations public method - rename unlink_from_conversations to unlink_from_conversations! - fix send call on private method in statuses_vacuum and batched_remove_status_service * fix(feeds_vacuum): replace find_in_batches with in_batches because active record query results should be a little more efficient than itterating with map and each. Postgres can grasp such lists of ids much quicker than ruby can. Will probably make allmost no difference, but cannot hurt either. --- app/lib/vacuum/feeds_vacuum.rb | 8 ++++---- app/lib/vacuum/statuses_vacuum.rb | 5 +---- app/models/status.rb | 28 +++++++++++++-------------- app/services/batched_remove_status_service.rb | 4 +--- 4 files changed, 20 insertions(+), 25 deletions(-) (limited to 'app/models') diff --git a/app/lib/vacuum/feeds_vacuum.rb b/app/lib/vacuum/feeds_vacuum.rb index f46bcf75f..fb0b8a847 100644 --- a/app/lib/vacuum/feeds_vacuum.rb +++ b/app/lib/vacuum/feeds_vacuum.rb @@ -9,14 +9,14 @@ class Vacuum::FeedsVacuum private def vacuum_inactive_home_feeds! - inactive_users.select(:id, :account_id).find_in_batches do |users| - feed_manager.clean_feeds!(:home, users.map(&:account_id)) + inactive_users.select(:id, :account_id).in_batches do |users| + feed_manager.clean_feeds!(:home, users.pluck(:account_id)) end end def vacuum_inactive_list_feeds! - inactive_users_lists.select(:id).find_in_batches do |lists| - feed_manager.clean_feeds!(:list, lists.map(&:id)) + inactive_users_lists.select(:id).in_batches do |lists| + feed_manager.clean_feeds!(:list, lists.ids) end end diff --git a/app/lib/vacuum/statuses_vacuum.rb b/app/lib/vacuum/statuses_vacuum.rb index d1c4e7197..28c087b1c 100644 --- a/app/lib/vacuum/statuses_vacuum.rb +++ b/app/lib/vacuum/statuses_vacuum.rb @@ -19,10 +19,7 @@ class Vacuum::StatusesVacuum # as the search index, must be handled first. statuses.direct_visibility .includes(mentions: :account) - .find_each do |status| - # TODO: replace temporary solution - call of private model method - status.send(:unlink_from_conversations) - end + .find_each(&:unlink_from_conversations!) remove_from_search_index(statuses.ids) if Chewy.enabled? # Foreign keys take care of most associated records for us. diff --git a/app/models/status.rb b/app/models/status.rb index 2fe9f2de0..fa9fb9fad 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -29,7 +29,7 @@ # class Status < ApplicationRecord - before_destroy :unlink_from_conversations + before_destroy :unlink_from_conversations! include Discard::Model include Paginable @@ -309,14 +309,14 @@ class Status < ApplicationRecord after_create_commit :store_uri, if: :local? after_create_commit :update_statistics, if: :local? - around_create Mastodon::Snowflake::Callbacks - before_validation :prepare_contents, if: :local? before_validation :set_reblog before_validation :set_visibility before_validation :set_conversation before_validation :set_local + around_create Mastodon::Snowflake::Callbacks + after_create :set_poll_id class << self @@ -447,6 +447,17 @@ class Status < ApplicationRecord update_attribute(:deleted_at, discard_time) end + def unlink_from_conversations! + return unless direct_visibility? + + inbox_owners = mentioned_accounts.local + inbox_owners += [account] if account.local? + + inbox_owners.each do |inbox_owner| + AccountConversation.remove_status(inbox_owner, self) + end + end + private def update_status_stat!(attrs) @@ -524,15 +535,4 @@ class Status < ApplicationRecord reblog&.decrement_count!(:reblogs_count) if reblog? thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && distributable? end - - def unlink_from_conversations - return unless direct_visibility? - - inbox_owners = mentioned_accounts.local - inbox_owners += [account] if account.local? - - inbox_owners.each do |inbox_owner| - AccountConversation.remove_status(inbox_owner, self) - end - end end diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb index 5000062e4..54e5f10a4 100644 --- a/app/services/batched_remove_status_service.rb +++ b/app/services/batched_remove_status_service.rb @@ -19,9 +19,7 @@ class BatchedRemoveStatusService < BaseService ActiveRecord::Associations::Preloader.new.preload(statuses_with_account_conversations, [mentions: :account]) - statuses_with_account_conversations.each do |status| - status.send(:unlink_from_conversations) - end + statuses_with_account_conversations.each(&:unlink_from_conversations!) # We do not batch all deletes into one to avoid having a long-running # transaction lock the database, but we use the delete method instead -- cgit From 21a1a8ee887f82cb36b3d21011a0235e7bfc8e45 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 13 Jan 2023 10:46:52 +0100 Subject: Fix crash when marking statuses as sensitive while some statuses are deleted (#22134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Do not offer to mark statuses as sensitive if there is no undeleted status with media attachments * Fix crash when marking statuses as sensitive while some statuses are deleted Fixes #21910 * Fix multiple strikes being created for a single report when selecting “Mark as sensitive” * Add tests --- app/models/admin/status_batch_action.rb | 16 ++++----- app/views/admin/reports/_actions.html.haml | 2 +- .../admin/reports/actions_controller_spec.rb | 42 ++++++++++++++++++++++ 3 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 spec/controllers/admin/reports/actions_controller_spec.rb (limited to 'app/models') diff --git a/app/models/admin/status_batch_action.rb b/app/models/admin/status_batch_action.rb index 0f019b854..39cd7d0eb 100644 --- a/app/models/admin/status_batch_action.rb +++ b/app/models/admin/status_batch_action.rb @@ -73,7 +73,7 @@ class Admin::StatusBatchAction # Can't use a transaction here because UpdateStatusService queues # Sidekiq jobs statuses.includes(:media_attachments, :preview_cards).find_each do |status| - next unless status.with_media? || status.with_preview_card? + next if status.discarded? || !(status.with_media? || status.with_preview_card?) authorize([:admin, status], :update?) @@ -89,15 +89,15 @@ class Admin::StatusBatchAction report.resolve!(current_account) log_action(:resolve, report) end - - @warning = target_account.strikes.create!( - action: :mark_statuses_as_sensitive, - account: current_account, - report: report, - status_ids: status_ids - ) end + @warning = target_account.strikes.create!( + action: :mark_statuses_as_sensitive, + account: current_account, + report: report, + status_ids: status_ids + ) + UserMailer.warning(target_account.user, @warning).deliver_later! if warnable? end diff --git a/app/views/admin/reports/_actions.html.haml b/app/views/admin/reports/_actions.html.haml index 404d53a77..486eb486c 100644 --- a/app/views/admin/reports/_actions.html.haml +++ b/app/views/admin/reports/_actions.html.haml @@ -5,7 +5,7 @@ = link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), method: :post, class: 'button' .report-actions__item__description = t('admin.reports.actions.resolve_description_html') - - if @statuses.any? { |status| status.with_media? || status.with_preview_card? } + - if @statuses.any? { |status| (status.with_media? || status.with_preview_card?) && !status.discarded? } .report-actions__item .report-actions__item__button = button_tag t('admin.reports.mark_as_sensitive'), name: :mark_as_sensitive, class: 'button' diff --git a/spec/controllers/admin/reports/actions_controller_spec.rb b/spec/controllers/admin/reports/actions_controller_spec.rb new file mode 100644 index 000000000..6609798dc --- /dev/null +++ b/spec/controllers/admin/reports/actions_controller_spec.rb @@ -0,0 +1,42 @@ +require 'rails_helper' + +describe Admin::Reports::ActionsController do + render_views + + let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + let(:account) { Fabricate(:account) } + let!(:status) { Fabricate(:status, account: account) } + let(:media_attached_status) { Fabricate(:status, account: account) } + let!(:media_attachment) { Fabricate(:media_attachment, account: account, status: media_attached_status) } + let(:media_attached_deleted_status) { Fabricate(:status, account: account, deleted_at: 1.day.ago) } + let!(:media_attachment2) { Fabricate(:media_attachment, account: account, status: media_attached_deleted_status) } + let(:last_media_attached_status) { Fabricate(:status, account: account) } + let!(:last_media_attachment) { Fabricate(:media_attachment, account: account, status: last_media_attached_status) } + let!(:last_status) { Fabricate(:status, account: account) } + + before do + sign_in user, scope: :user + end + + describe 'POST #create' do + let(:report) { Fabricate(:report, status_ids: status_ids, account: user.account, target_account: account) } + let(:status_ids) { [media_attached_status.id, media_attached_deleted_status.id] } + + before do + post :create, params: { report_id: report.id, action => '' } + end + + context 'when action is mark_as_sensitive' do + + let(:action) { 'mark_as_sensitive' } + + it 'resolves the report' do + expect(report.reload.action_taken_at).to_not be_nil + end + + it 'marks the non-deleted as sensitive' do + expect(media_attached_status.reload.sensitive).to eq true + end + end + end +end -- cgit From ff70e5019910c309f8ab38d729c4eb5819512698 Mon Sep 17 00:00:00 2001 From: David Freedman Date: Fri, 13 Jan 2023 15:40:06 +0000 Subject: Don't crash on unobtainable avatars (#22462) --- app/models/concerns/omniauthable.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'app/models') diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb index a90d5d888..feac0a1f5 100644 --- a/app/models/concerns/omniauthable.rb +++ b/app/models/concerns/omniauthable.rb @@ -55,7 +55,14 @@ module Omniauthable user = User.new(user_params_from_auth(email, auth)) - user.account.avatar_remote_url = auth.info.image if /\A#{URI::DEFAULT_PARSER.make_regexp(%w(http https))}\z/.match?(auth.info.image) + begin + if /\A#{URI::DEFAULT_PARSER.make_regexp(%w(http https))}\z/.match?(auth.info.image) + user.account.avatar_remote_url = auth.info.image + end + rescue Mastodon::UnexpectedResponseError + user.account.avatar_remote_url = nil + end + user.skip_confirmation! if email_is_verified user.save! user -- cgit