Merge commit '163db814c2b3cf544b78e427e7f7bbd99b94a025' into glitch-soc/merge-upstream

main
Claire 2024-01-22 19:12:05 +01:00
commit 0700eb58bc
17 changed files with 90 additions and 137 deletions

View File

@ -80,14 +80,12 @@ Rails/WhereExists:
- 'app/lib/activitypub/activity/create.rb'
- 'app/lib/delivery_failure_tracker.rb'
- 'app/lib/feed_manager.rb'
- 'app/lib/status_cache_hydrator.rb'
- 'app/lib/suspicious_sign_in_detector.rb'
- 'app/models/poll.rb'
- 'app/models/session_activation.rb'
- 'app/models/status.rb'
- 'app/policies/status_policy.rb'
- 'app/serializers/rest/announcement_serializer.rb'
- 'app/serializers/rest/tag_serializer.rb'
- 'app/services/activitypub/fetch_remote_status_service.rb'
- 'app/services/vote_service.rb'
- 'app/validators/reaction_validator.rb'

View File

@ -150,7 +150,7 @@ GEM
erubi (~> 1.4)
parser (>= 2.4)
smart_properties
bigdecimal (3.1.5)
bigdecimal (3.1.6)
bindata (2.4.15)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
@ -398,12 +398,12 @@ GEM
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
kt-paperclip (7.2.1)
kt-paperclip (7.2.2)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
marcel (~> 1.0.1)
mime-types
terrapin (~> 0.6.0)
terrapin (>= 0.6.0, < 2.0)
language_server-protocol (3.17.0.3)
launchy (2.5.2)
addressable (~> 2.8)
@ -600,8 +600,8 @@ GEM
rdf (3.3.1)
bcp47_spec (~> 0.2)
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.6.1)
rdf (~> 3.2)
rdf-normalize (0.7.0)
rdf (~> 3.3)
rdoc (6.6.2)
psych (>= 4.0.0)
redcarpet (3.6.0)

View File

@ -6,7 +6,7 @@ module Admin
def index
authorize :audit_log, :index?
@auditable_accounts = Account.where(id: Admin::ActionLog.select('distinct account_id')).select(:id, :username)
@auditable_accounts = Account.auditable.select(:id, :username)
end
private

View File

@ -21,7 +21,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
return [] if hide_results?
scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
scope = scope.not_excluded_by_account(current_account) unless current_account.nil? || current_account.id == @account.id
scope.merge(paginated_follows).to_a
end

View File

@ -21,7 +21,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
return [] if hide_results?
scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil? || current_account.id == @account.id
scope = scope.not_excluded_by_account(current_account) unless current_account.nil? || current_account.id == @account.id
scope.merge(paginated_follows).to_a
end

View File

@ -155,7 +155,7 @@ module JsonLdHelper
end
end
def fetch_resource(uri, id, on_behalf_of = nil)
def fetch_resource(uri, id, on_behalf_of = nil, request_options: {})
unless id
json = fetch_resource_without_id_validation(uri, on_behalf_of)
@ -164,14 +164,14 @@ module JsonLdHelper
uri = json['id']
end
json = fetch_resource_without_id_validation(uri, on_behalf_of)
json = fetch_resource_without_id_validation(uri, on_behalf_of, request_options: request_options)
json.present? && json['id'] == uri ? json : nil
end
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false)
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false, request_options: {})
on_behalf_of ||= Account.representative
build_request(uri, on_behalf_of).perform do |response|
build_request(uri, on_behalf_of, options: request_options).perform do |response|
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
body_to_json(response.body_with_limit) if response.code == 200
@ -204,8 +204,8 @@ module JsonLdHelper
response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code))
end
def build_request(uri, on_behalf_of = nil)
Request.new(:get, uri).tap do |request|
def build_request(uri, on_behalf_of = nil, options: {})
Request.new(:get, uri, **options).tap do |request|
request.on_behalf_of(on_behalf_of) if on_behalf_of
request.add_headers('Accept' => 'application/activity+json, application/ld+json')
end

View File

@ -1,12 +1,11 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import type { TypedUseSelectorHook } from 'react-redux';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useDispatch, useSelector } from 'react-redux';
import type { AppDispatch, RootState } from './store';
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: RootState;

View File

@ -100,9 +100,8 @@ table + p {
border-top-right-radius: 12px;
height: 140px;
vertical-align: bottom;
background-color: #f3f2f5;
background-position: center;
background-size: cover;
background-position: center !important;
background-size: cover !important;
}
.email-account-banner-inner-td {

View File

@ -26,11 +26,11 @@ class StatusCacheHydrator
def hydrate_non_reblog_payload(empty_payload, account_id)
empty_payload.tap do |payload|
payload[:favourited] = Favourite.where(account_id: account_id, status_id: @status.id).exists?
payload[:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.id).exists?
payload[:muted] = ConversationMute.where(account_id: account_id, conversation_id: @status.conversation_id).exists?
payload[:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.id).exists?
payload[:pinned] = StatusPin.where(account_id: account_id, status_id: @status.id).exists? if @status.account_id == account_id
payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.id)
payload[:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: @status.id)
payload[:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: @status.conversation_id)
payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.id)
payload[:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.id) if @status.account_id == account_id
payload[:filtered] = mapped_applied_custom_filter(account_id, @status)
if payload[:poll]
@ -51,11 +51,11 @@ class StatusCacheHydrator
# used to create the status, we need to hydrate it here too
payload[:reblog][:application] = payload_reblog_application if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id
payload[:reblog][:favourited] = Favourite.where(account_id: account_id, status_id: @status.reblog_of_id).exists?
payload[:reblog][:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.reblog_of_id).exists?
payload[:reblog][:muted] = ConversationMute.where(account_id: account_id, conversation_id: @status.reblog.conversation_id).exists?
payload[:reblog][:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.reblog_of_id).exists?
payload[:reblog][:pinned] = StatusPin.where(account_id: account_id, status_id: @status.reblog_of_id).exists? if @status.reblog.account_id == account_id
payload[:reblog][:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.reblog_of_id)
payload[:reblog][:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: @status.reblog_of_id)
payload[:reblog][:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: @status.reblog.conversation_id)
payload[:reblog][:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.reblog_of_id)
payload[:reblog][:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.reblog_of_id) if @status.reblog.account_id == account_id
payload[:reblog][:filtered] = payload[:filtered]
if payload[:reblog][:poll]

View File

@ -130,6 +130,7 @@ class Account < ApplicationRecord
scope :matches_username, ->(value) { where('lower((username)::text) LIKE lower(?)', "#{value}%") }
scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
scope :without_unapproved, -> { left_outer_joins(:user).merge(User.approved.confirmed).or(remote) }
scope :auditable, -> { where(id: Admin::ActionLog.select(:account_id).distinct) }
scope :searchable, -> { without_unapproved.without_suspended.where(moved_to_account_id: nil) }
scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).joins(:account_stat) }
scope :by_recent_status, -> { includes(:account_stat).merge(AccountStat.order('last_status_at DESC NULLS LAST')).references(:account_stat) }

View File

@ -72,7 +72,7 @@ class Admin::ActionLogFilter
end
def results
scope = latest_action_logs.includes(:target)
scope = latest_action_logs.includes(:target, :account)
params.each do |key, value|
next if key.to_s == 'page'

View File

@ -19,7 +19,7 @@ class REST::TagSerializer < ActiveModel::Serializer
if instance_options && instance_options[:relationships]
instance_options[:relationships].following_map[object.id] || false
else
TagFollow.where(tag_id: object.id, account_id: current_user.account_id).exists?
TagFollow.exists?(tag_id: object.id, account_id: current_user.account_id)
end
end

View File

@ -37,7 +37,20 @@ class ActivityPub::FetchRepliesService < BaseService
return unless @allow_synchronous_requests
return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
fetch_resource_without_id_validation(collection_or_uri, nil, true)
# NOTE: For backward compatibility reasons, Mastodon signs outgoing
# queries incorrectly by default.
#
# While this is relevant for all URLs with query strings, this is
# the only code path where this happens in practice.
#
# Therefore, retry with correct signatures if this fails.
begin
fetch_resource_without_id_validation(collection_or_uri, nil, true)
rescue Mastodon::UnexpectedResponseError => e
raise unless e.response && e.response.code == 401 && Addressable::URI.parse(collection_or_uri).query.present?
fetch_resource_without_id_validation(collection_or_uri, nil, true, request_options: { with_query_string: true })
end
end
def filtered_replies

View File

@ -835,6 +835,25 @@ RSpec.describe Account do
end
describe 'scopes' do
describe 'auditable' do
let!(:alice) { Fabricate :account }
let!(:bob) { Fabricate :account }
before do
2.times { Fabricate :action_log, account: alice }
end
it 'returns distinct accounts with action log records' do
results = described_class.auditable
expect(results.size)
.to eq(1)
expect(results)
.to include(alice)
.and not_include(bob)
end
end
describe 'alphabetic' do
it 'sorts by alphabetic order of domain and username' do
matches = [

View File

@ -2,11 +2,11 @@
require 'rails_helper'
describe Api::V1::Accounts::FollowerAccountsController do
render_views
describe 'API V1 Accounts FollowerAccounts' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
let(:alice) { Fabricate(:account) }
let(:bob) { Fabricate(:account) }
@ -14,12 +14,11 @@ describe Api::V1::Accounts::FollowerAccountsController do
before do
alice.follow!(account)
bob.follow!(account)
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
describe 'GET /api/v1/accounts/:acount_id/followers' do
it 'returns accounts following the given account', :aggregate_failures do
get :index, params: { account_id: account.id, limit: 2 }
get "/api/v1/accounts/#{account.id}/followers", params: { limit: 2 }, headers: headers
expect(response).to have_http_status(200)
expect(body_as_json.size).to eq 2
@ -28,7 +27,7 @@ describe Api::V1::Accounts::FollowerAccountsController do
it 'does not return blocked users', :aggregate_failures do
user.account.block!(bob)
get :index, params: { account_id: account.id, limit: 2 }
get "/api/v1/accounts/#{account.id}/followers", params: { limit: 2 }, headers: headers
expect(response).to have_http_status(200)
expect(body_as_json.size).to eq 1
@ -41,7 +40,7 @@ describe Api::V1::Accounts::FollowerAccountsController do
end
it 'hides results' do
get :index, params: { account_id: account.id, limit: 2 }
get "/api/v1/accounts/#{account.id}/followers", params: { limit: 2 }, headers: headers
expect(body_as_json.size).to eq 0
end
end
@ -51,7 +50,7 @@ describe Api::V1::Accounts::FollowerAccountsController do
it 'returns all accounts, including muted accounts' do
account.mute!(bob)
get :index, params: { account_id: account.id, limit: 2 }
get "/api/v1/accounts/#{account.id}/followers", params: { limit: 2 }, headers: headers
expect(body_as_json.size).to eq 2
expect([body_as_json[0][:id], body_as_json[1][:id]]).to contain_exactly(alice.id.to_s, bob.id.to_s)

View File

@ -2,11 +2,11 @@
require 'rails_helper'
describe Api::V1::Accounts::FollowingAccountsController do
render_views
describe 'API V1 Accounts FollowingAccounts' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:account) { Fabricate(:account) }
let(:alice) { Fabricate(:account) }
let(:bob) { Fabricate(:account) }
@ -14,12 +14,11 @@ describe Api::V1::Accounts::FollowingAccountsController do
before do
account.follow!(alice)
account.follow!(bob)
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
describe 'GET /api/v1/accounts/:account_id/following' do
it 'returns accounts followed by the given account', :aggregate_failures do
get :index, params: { account_id: account.id, limit: 2 }
get "/api/v1/accounts/#{account.id}/following", params: { limit: 2 }, headers: headers
expect(response).to have_http_status(200)
expect(body_as_json.size).to eq 2
@ -28,7 +27,7 @@ describe Api::V1::Accounts::FollowingAccountsController do
it 'does not return blocked users', :aggregate_failures do
user.account.block!(bob)
get :index, params: { account_id: account.id, limit: 2 }
get "/api/v1/accounts/#{account.id}/following", params: { limit: 2 }, headers: headers
expect(response).to have_http_status(200)
expect(body_as_json.size).to eq 1
@ -41,7 +40,7 @@ describe Api::V1::Accounts::FollowingAccountsController do
end
it 'hides results' do
get :index, params: { account_id: account.id, limit: 2 }
get "/api/v1/accounts/#{account.id}/following", params: { limit: 2 }, headers: headers
expect(body_as_json.size).to eq 0
end
end
@ -51,7 +50,7 @@ describe Api::V1::Accounts::FollowingAccountsController do
it 'returns all accounts, including muted accounts' do
account.mute!(bob)
get :index, params: { account_id: account.id, limit: 2 }
get "/api/v1/accounts/#{account.id}/following", params: { limit: 2 }, headers: headers
expect(body_as_json.size).to eq 2
expect([body_as_json[0][:id], body_as_json[1][:id]]).to contain_exactly(alice.id.to_s, bob.id.to_s)

View File

@ -3786,16 +3786,6 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:6.9.1":
version: 6.9.1
resolution: "@typescript-eslint/scope-manager@npm:6.9.1"
dependencies:
"@typescript-eslint/types": "npm:6.9.1"
"@typescript-eslint/visitor-keys": "npm:6.9.1"
checksum: 53fa7c3813d22b119e464f9b6d7d23407dfe103ee8ad2dcacf9ad6d656fda20e2bb3346df39e62b0e6b6ce71572ce5838071c5d2cca6daa4e0ce117ff22eafe5
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:6.19.0":
version: 6.19.0
resolution: "@typescript-eslint/type-utils@npm:6.19.0"
@ -3820,13 +3810,6 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/types@npm:6.9.1":
version: 6.9.1
resolution: "@typescript-eslint/types@npm:6.9.1"
checksum: 4ba21ba18e256da210a4caedfbc5d4927cf8cb4f2c4d74f8ccc865576f3659b974e79119d3c94db2b68a4cec9cd687e43971d355450b7082d6d1736a5dd6db85
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:6.19.0":
version: 6.19.0
resolution: "@typescript-eslint/typescript-estree@npm:6.19.0"
@ -3846,25 +3829,7 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:6.9.1":
version: 6.9.1
resolution: "@typescript-eslint/typescript-estree@npm:6.9.1"
dependencies:
"@typescript-eslint/types": "npm:6.9.1"
"@typescript-eslint/visitor-keys": "npm:6.9.1"
debug: "npm:^4.3.4"
globby: "npm:^11.1.0"
is-glob: "npm:^4.0.3"
semver: "npm:^7.5.4"
ts-api-utils: "npm:^1.0.1"
peerDependenciesMeta:
typescript:
optional: true
checksum: 850b1865a90107879186c3f2969968a2c08fc6fcc56d146483c297cf5be376e33d505ac81533ba8e8103ca4d2edfea7d21b178de9e52217f7ee2922f51a445fa
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:6.19.0":
"@typescript-eslint/utils@npm:6.19.0, @typescript-eslint/utils@npm:^6.5.0":
version: 6.19.0
resolution: "@typescript-eslint/utils@npm:6.19.0"
dependencies:
@ -3881,23 +3846,6 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:^6.5.0":
version: 6.9.1
resolution: "@typescript-eslint/utils@npm:6.9.1"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0"
"@types/json-schema": "npm:^7.0.12"
"@types/semver": "npm:^7.5.0"
"@typescript-eslint/scope-manager": "npm:6.9.1"
"@typescript-eslint/types": "npm:6.9.1"
"@typescript-eslint/typescript-estree": "npm:6.9.1"
semver: "npm:^7.5.4"
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
checksum: 3d329d54c3d155ed29e2b456a602aef76bda1b88dfcf847145849362e4ddefabe5c95de236de750d08d5da9bedcfb2131bdfd784ce4eb87cf82728f0b6662033
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:6.19.0":
version: 6.19.0
resolution: "@typescript-eslint/visitor-keys@npm:6.19.0"
@ -3908,16 +3856,6 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:6.9.1":
version: 6.9.1
resolution: "@typescript-eslint/visitor-keys@npm:6.9.1"
dependencies:
"@typescript-eslint/types": "npm:6.9.1"
eslint-visitor-keys: "npm:^3.4.1"
checksum: ac5f375a177add30489e5b63cafa8d82a196b33624bb36418422ebe0d7973b3ba550dc7e0dda36ea75a94cf9b200b4fb5f5fb4d77c027fd801201c1a269d343b
languageName: node
linkType: hard
"@ungap/structured-clone@npm:^1.2.0":
version: 1.2.0
resolution: "@ungap/structured-clone@npm:1.2.0"
@ -9377,7 +9315,7 @@ __metadata:
languageName: node
linkType: hard
"intl-messageformat@npm:10.5.10":
"intl-messageformat@npm:10.5.10, intl-messageformat@npm:^10.3.5":
version: 10.5.10
resolution: "intl-messageformat@npm:10.5.10"
dependencies:
@ -9389,18 +9327,6 @@ __metadata:
languageName: node
linkType: hard
"intl-messageformat@npm:^10.3.5":
version: 10.5.8
resolution: "intl-messageformat@npm:10.5.8"
dependencies:
"@formatjs/ecma402-abstract": "npm:1.18.0"
"@formatjs/fast-memoize": "npm:2.2.0"
"@formatjs/icu-messageformat-parser": "npm:2.7.3"
tslib: "npm:^2.4.0"
checksum: 1d2854aae8471ec48165ca265760d6c5b1814eca831c88db698eb29b5ed20bee21ca8533090c9d28d9c6f1d844dda210b0bc58a2e036446158fae0845e5eed4f
languageName: node
linkType: hard
"invariant@npm:^2.2.2, invariant@npm:^2.2.4":
version: 2.2.4
resolution: "invariant@npm:2.2.4"
@ -13897,8 +13823,8 @@ __metadata:
linkType: hard
"react-redux@npm:^9.0.4":
version: 9.0.4
resolution: "react-redux@npm:9.0.4"
version: 9.1.0
resolution: "react-redux@npm:9.1.0"
dependencies:
"@types/use-sync-external-store": "npm:^0.0.3"
use-sync-external-store: "npm:^1.0.0"
@ -13914,7 +13840,7 @@ __metadata:
optional: true
redux:
optional: true
checksum: 23af10014b129aeb051de729bde01de21175170b860deefb7ad83483feab5816253f770a4cea93333fc22a53ac9ac699b27f5c3705c388dab53dbcb2906a571a
checksum: 53161b5dc4d109020fbc42d26906ace92fed9ba1d7ab6274af60e9c0684583d20d1c8ec6d58601ac7b833c6468a652bbf3d4a102149d1793cb8a28b05b042f73
languageName: node
linkType: hard
@ -14775,15 +14701,15 @@ __metadata:
linkType: hard
"sass@npm:^1.62.1":
version: 1.69.7
resolution: "sass@npm:1.69.7"
version: 1.70.0
resolution: "sass@npm:1.70.0"
dependencies:
chokidar: "npm:>=3.0.0 <4.0.0"
immutable: "npm:^4.0.0"
source-map-js: "npm:>=0.6.2 <2.0.0"
bin:
sass: sass.js
checksum: 773d0938e7d4ff3972d3fda3132f34fe98a2f712e028a58e28fecd615434795eff3266eddc38d5e13f03b90c0d6360d0e737b30bff2949a47280c64a18e0fb18
checksum: 7c309ee1c096d591746d122da9f1ebd65b4c4b3a60c2cc0ec720fd98fe1205fa8b44c9f563d113b9fdfeb25af1e32ec9b3e048bd4b8e05d267f020953bd7baf0
languageName: node
linkType: hard