Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: - `app/controllers/activitypub/collections_controller.rb`: Conflict caused because we have additional code to make sure pinned local-only toots don't get rendered on the ActivityPub endpoints. Ported upstream changes.
This commit is contained in:
		
						commit
						e5f934ddf0
					
				
					 18 changed files with 128 additions and 138 deletions
				
			
		|  | @ -12,7 +12,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController | ||||||
| 
 | 
 | ||||||
|   def show |   def show | ||||||
|     expires_in 3.minutes, public: public_fetch_mode? |     expires_in 3.minutes, public: public_fetch_mode? | ||||||
|     render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true |     render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   private |   private | ||||||
|  | @ -20,17 +20,9 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController | ||||||
|   def set_items |   def set_items | ||||||
|     case params[:id] |     case params[:id] | ||||||
|     when 'featured' |     when 'featured' | ||||||
|       @items = begin |       @items = for_signed_account { cache_collection(@account.pinned_statuses.not_local_only, Status) } | ||||||
|         # Because in public fetch mode we cache the response, there would be no |     when 'tags' | ||||||
|         # benefit from performing the check below, since a blocked account or domain |       @items = for_signed_account { @account.featured_tags } | ||||||
|         # would likely be served the cache from the reverse proxy anyway |  | ||||||
| 
 |  | ||||||
|         if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain))) |  | ||||||
|           [] |  | ||||||
|         else |  | ||||||
|           cache_collection(@account.pinned_statuses.not_local_only, Status) |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     when 'devices' |     when 'devices' | ||||||
|       @items = @account.devices |       @items = @account.devices | ||||||
|     else |     else | ||||||
|  | @ -40,7 +32,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController | ||||||
| 
 | 
 | ||||||
|   def set_size |   def set_size | ||||||
|     case params[:id] |     case params[:id] | ||||||
|     when 'featured', 'devices' |     when 'featured', 'devices', 'tags' | ||||||
|       @size = @items.size |       @size = @items.size | ||||||
|     else |     else | ||||||
|       not_found |       not_found | ||||||
|  | @ -51,7 +43,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController | ||||||
|     case params[:id] |     case params[:id] | ||||||
|     when 'featured' |     when 'featured' | ||||||
|       @type = :ordered |       @type = :ordered | ||||||
|     when 'devices' |     when 'devices', 'tags' | ||||||
|       @type = :unordered |       @type = :unordered | ||||||
|     else |     else | ||||||
|       not_found |       not_found | ||||||
|  | @ -66,4 +58,16 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController | ||||||
|       items: @items |       items: @items | ||||||
|     ) |     ) | ||||||
|   end |   end | ||||||
|  | 
 | ||||||
|  |   def for_signed_account | ||||||
|  |     # Because in public fetch mode we cache the response, there would be no | ||||||
|  |     # benefit from performing the check below, since a blocked account or domain | ||||||
|  |     # would likely be served the cache from the reverse proxy anyway | ||||||
|  | 
 | ||||||
|  |     if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain))) | ||||||
|  |       [] | ||||||
|  |     else | ||||||
|  |       yield | ||||||
|  |     end | ||||||
|  |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -20,9 +20,9 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController | ||||||
|   def outbox_presenter |   def outbox_presenter | ||||||
|     if page_requested? |     if page_requested? | ||||||
|       ActivityPub::CollectionPresenter.new( |       ActivityPub::CollectionPresenter.new( | ||||||
|         id: account_outbox_url(@account, page_params), |         id: outbox_url(page_params), | ||||||
|         type: :ordered, |         type: :ordered, | ||||||
|         part_of: account_outbox_url(@account), |         part_of: outbox_url, | ||||||
|         prev: prev_page, |         prev: prev_page, | ||||||
|         next: next_page, |         next: next_page, | ||||||
|         items: @statuses |         items: @statuses | ||||||
|  | @ -32,12 +32,20 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController | ||||||
|         id: account_outbox_url(@account), |         id: account_outbox_url(@account), | ||||||
|         type: :ordered, |         type: :ordered, | ||||||
|         size: @account.statuses_count, |         size: @account.statuses_count, | ||||||
|         first: account_outbox_url(@account, page: true), |         first: outbox_url(page: true), | ||||||
|         last: account_outbox_url(@account, page: true, min_id: 0) |         last: outbox_url(page: true, min_id: 0) | ||||||
|       ) |       ) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def outbox_url(**kwargs) | ||||||
|  |     if params[:account_username].present? | ||||||
|  |       account_outbox_url(@account, **kwargs) | ||||||
|  |     else | ||||||
|  |       instance_actor_outbox_url(**kwargs) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def next_page |   def next_page | ||||||
|     account_outbox_url(@account, page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT |     account_outbox_url(@account, page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT | ||||||
|   end |   end | ||||||
|  | @ -65,4 +73,8 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController | ||||||
|   def page_params |   def page_params | ||||||
|     { page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact |     { page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact | ||||||
|   end |   end | ||||||
|  | 
 | ||||||
|  |   def set_account | ||||||
|  |     @account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative | ||||||
|  |   end | ||||||
| end | end | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								app/controllers/api/v1/accounts/featured_tags_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/controllers/api/v1/accounts/featured_tags_controller.rb
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class Api::V1::Accounts::FeaturedTagsController < Api::BaseController | ||||||
|  |   before_action :set_account | ||||||
|  |   before_action :set_featured_tags | ||||||
|  | 
 | ||||||
|  |   respond_to :json | ||||||
|  | 
 | ||||||
|  |   def index | ||||||
|  |     render json: @featured_tags, each_serializer: REST::AccountFeaturedTagSerializer | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   private | ||||||
|  | 
 | ||||||
|  |   def set_account | ||||||
|  |     @account = Account.find(params[:account_id]) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def set_featured_tags | ||||||
|  |     @featured_tags = @account.featured_tags | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -17,6 +17,6 @@ class InstanceActorsController < ApplicationController | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def restrict_fields_to |   def restrict_fields_to | ||||||
|     %i(id type preferred_username inbox public_key endpoints url manually_approves_followers) |     %i(id type preferred_username inbox outbox public_key endpoints url manually_approves_followers) | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| import api, { getLinks } from '../api'; | import api, { getLinks } from '../api'; | ||||||
| import openDB from '../storage/db'; | import { importFetchedAccount, importFetchedAccounts } from './importer'; | ||||||
| import { importAccount, importFetchedAccount, importFetchedAccounts } from './importer'; |  | ||||||
| 
 | 
 | ||||||
| export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; | export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; | ||||||
| export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; | export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; | ||||||
|  | @ -74,45 +73,13 @@ export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST'; | ||||||
| export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS'; | export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS'; | ||||||
| export const FOLLOW_REQUEST_REJECT_FAIL    = 'FOLLOW_REQUEST_REJECT_FAIL'; | export const FOLLOW_REQUEST_REJECT_FAIL    = 'FOLLOW_REQUEST_REJECT_FAIL'; | ||||||
| 
 | 
 | ||||||
| function getFromDB(dispatch, getState, index, id) { |  | ||||||
|   return new Promise((resolve, reject) => { |  | ||||||
|     const request = index.get(id); |  | ||||||
| 
 |  | ||||||
|     request.onerror = reject; |  | ||||||
| 
 |  | ||||||
|     request.onsuccess = () => { |  | ||||||
|       if (!request.result) { |  | ||||||
|         reject(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       dispatch(importAccount(request.result)); |  | ||||||
|       resolve(request.result.moved && getFromDB(dispatch, getState, index, request.result.moved)); |  | ||||||
|     }; |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function fetchAccount(id) { | export function fetchAccount(id) { | ||||||
|   return (dispatch, getState) => { |   return (dispatch, getState) => { | ||||||
|     dispatch(fetchRelationships([id])); |     dispatch(fetchRelationships([id])); | ||||||
| 
 |  | ||||||
|     if (getState().getIn(['accounts', id], null) !== null) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     dispatch(fetchAccountRequest(id)); |     dispatch(fetchAccountRequest(id)); | ||||||
| 
 | 
 | ||||||
|     openDB().then(db => getFromDB( |     api(getState).get(`/api/v1/accounts/${id}`).then(response => { | ||||||
|       dispatch, |  | ||||||
|       getState, |  | ||||||
|       db.transaction('accounts', 'read').objectStore('accounts').index('id'), |  | ||||||
|       id, |  | ||||||
|     ).then(() => db.close(), error => { |  | ||||||
|       db.close(); |  | ||||||
|       throw error; |  | ||||||
|     })).catch(() => api(getState).get(`/api/v1/accounts/${id}`).then(response => { |  | ||||||
|       dispatch(importFetchedAccount(response.data)); |       dispatch(importFetchedAccount(response.data)); | ||||||
|     })).then(() => { |  | ||||||
|       dispatch(fetchAccountSuccess()); |       dispatch(fetchAccountSuccess()); | ||||||
|     }).catch(error => { |     }).catch(error => { | ||||||
|       dispatch(fetchAccountFail(id, error)); |       dispatch(fetchAccountFail(id, error)); | ||||||
|  |  | ||||||
|  | @ -1,9 +1,7 @@ | ||||||
| import api from '../api'; | import api from '../api'; | ||||||
| import openDB from '../storage/db'; |  | ||||||
| import { evictStatus } from '../storage/modifier'; |  | ||||||
| 
 | 
 | ||||||
| import { deleteFromTimelines } from './timelines'; | import { deleteFromTimelines } from './timelines'; | ||||||
| import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus, importFetchedAccount } from './importer'; | import { importFetchedStatus, importFetchedStatuses, importFetchedAccount } from './importer'; | ||||||
| import { ensureComposeIsVisible } from './compose'; | import { ensureComposeIsVisible } from './compose'; | ||||||
| 
 | 
 | ||||||
| export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; | export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; | ||||||
|  | @ -40,48 +38,6 @@ export function fetchStatusRequest(id, skipLoading) { | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| function getFromDB(dispatch, getState, accountIndex, index, id) { |  | ||||||
|   return new Promise((resolve, reject) => { |  | ||||||
|     const request = index.get(id); |  | ||||||
| 
 |  | ||||||
|     request.onerror = reject; |  | ||||||
| 
 |  | ||||||
|     request.onsuccess = () => { |  | ||||||
|       const promises = []; |  | ||||||
| 
 |  | ||||||
|       if (!request.result) { |  | ||||||
|         reject(); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       dispatch(importStatus(request.result)); |  | ||||||
| 
 |  | ||||||
|       if (getState().getIn(['accounts', request.result.account], null) === null) { |  | ||||||
|         promises.push(new Promise((accountResolve, accountReject) => { |  | ||||||
|           const accountRequest = accountIndex.get(request.result.account); |  | ||||||
| 
 |  | ||||||
|           accountRequest.onerror = accountReject; |  | ||||||
|           accountRequest.onsuccess = () => { |  | ||||||
|             if (!request.result) { |  | ||||||
|               accountReject(); |  | ||||||
|               return; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             dispatch(importAccount(accountRequest.result)); |  | ||||||
|             accountResolve(); |  | ||||||
|           }; |  | ||||||
|         })); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (request.result.reblog && getState().getIn(['statuses', request.result.reblog], null) === null) { |  | ||||||
|         promises.push(getFromDB(dispatch, getState, accountIndex, index, request.result.reblog)); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       resolve(Promise.all(promises)); |  | ||||||
|     }; |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function fetchStatus(id) { | export function fetchStatus(id) { | ||||||
|   return (dispatch, getState) => { |   return (dispatch, getState) => { | ||||||
|     const skipLoading = getState().getIn(['statuses', id], null) !== null; |     const skipLoading = getState().getIn(['statuses', id], null) !== null; | ||||||
|  | @ -94,23 +50,10 @@ export function fetchStatus(id) { | ||||||
| 
 | 
 | ||||||
|     dispatch(fetchStatusRequest(id, skipLoading)); |     dispatch(fetchStatusRequest(id, skipLoading)); | ||||||
| 
 | 
 | ||||||
|     openDB().then(db => { |     api(getState).get(`/api/v1/statuses/${id}`).then(response => { | ||||||
|       const transaction = db.transaction(['accounts', 'statuses'], 'read'); |  | ||||||
|       const accountIndex = transaction.objectStore('accounts').index('id'); |  | ||||||
|       const index = transaction.objectStore('statuses').index('id'); |  | ||||||
| 
 |  | ||||||
|       return getFromDB(dispatch, getState, accountIndex, index, id).then(() => { |  | ||||||
|         db.close(); |  | ||||||
|       }, error => { |  | ||||||
|         db.close(); |  | ||||||
|         throw error; |  | ||||||
|       }); |  | ||||||
|     }).then(() => { |  | ||||||
|       dispatch(fetchStatusSuccess(skipLoading)); |  | ||||||
|     }, () => api(getState).get(`/api/v1/statuses/${id}`).then(response => { |  | ||||||
|       dispatch(importFetchedStatus(response.data)); |       dispatch(importFetchedStatus(response.data)); | ||||||
|       dispatch(fetchStatusSuccess(skipLoading)); |       dispatch(fetchStatusSuccess(skipLoading)); | ||||||
|     })).catch(error => { |     }).catch(error => { | ||||||
|       dispatch(fetchStatusFail(id, error, skipLoading)); |       dispatch(fetchStatusFail(id, error, skipLoading)); | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
|  | @ -152,7 +95,6 @@ export function deleteStatus(id, routerHistory, withRedraft = false) { | ||||||
|     dispatch(deleteStatusRequest(id)); |     dispatch(deleteStatusRequest(id)); | ||||||
| 
 | 
 | ||||||
|     api(getState).delete(`/api/v1/statuses/${id}`).then(response => { |     api(getState).delete(`/api/v1/statuses/${id}`).then(response => { | ||||||
|       evictStatus(id); |  | ||||||
|       dispatch(deleteStatusSuccess(id)); |       dispatch(deleteStatusSuccess(id)); | ||||||
|       dispatch(deleteFromTimelines(id)); |       dispatch(deleteFromTimelines(id)); | ||||||
|       dispatch(importFetchedAccount(response.data.account)); |       dispatch(importFetchedAccount(response.data.account)); | ||||||
|  |  | ||||||
|  | @ -256,14 +256,6 @@ html { | ||||||
|   background: $ui-base-color; |   background: $ui-base-color; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .status.status-direct { |  | ||||||
|   background: lighten($ui-base-color, 4%); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .focusable:focus .status.status-direct { |  | ||||||
|   background: lighten($ui-base-color, 8%); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .detailed-status, | .detailed-status, | ||||||
| .detailed-status__action-bar { | .detailed-status__action-bar { | ||||||
|   background: $white; |   background: $white; | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base | ||||||
|     moved_to: { 'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' } }, |     moved_to: { 'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' } }, | ||||||
|     also_known_as: { 'alsoKnownAs' => { '@id' => 'as:alsoKnownAs', '@type' => '@id' } }, |     also_known_as: { 'alsoKnownAs' => { '@id' => 'as:alsoKnownAs', '@type' => '@id' } }, | ||||||
|     emoji: { 'toot' => 'http://joinmastodon.org/ns#', 'Emoji' => 'toot:Emoji' }, |     emoji: { 'toot' => 'http://joinmastodon.org/ns#', 'Emoji' => 'toot:Emoji' }, | ||||||
|     featured: { 'toot' => 'http://joinmastodon.org/ns#', 'featured' => { '@id' => 'toot:featured', '@type' => '@id' } }, |     featured: { 'toot' => 'http://joinmastodon.org/ns#', 'featured' => { '@id' => 'toot:featured', '@type' => '@id' }, 'featuredTags' => { '@id' => 'toot:featuredTags', '@type' => '@id' } }, | ||||||
|     property_value: { 'schema' => 'http://schema.org#', 'PropertyValue' => 'schema:PropertyValue', 'value' => 'schema:value' }, |     property_value: { 'schema' => 'http://schema.org#', 'PropertyValue' => 'schema:PropertyValue', 'value' => 'schema:value' }, | ||||||
|     atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' }, |     atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' }, | ||||||
|     conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' }, |     conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' }, | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer | ||||||
|                      :discoverable, :olm |                      :discoverable, :olm | ||||||
| 
 | 
 | ||||||
|   attributes :id, :type, :following, :followers, |   attributes :id, :type, :following, :followers, | ||||||
|              :inbox, :outbox, :featured, |              :inbox, :outbox, :featured, :featured_tags, | ||||||
|              :preferred_username, :name, :summary, |              :preferred_username, :name, :summary, | ||||||
|              :url, :manually_approves_followers, |              :url, :manually_approves_followers, | ||||||
|              :discoverable |              :discoverable | ||||||
|  | @ -74,13 +74,17 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def outbox |   def outbox | ||||||
|     account_outbox_url(object) |     object.instance_actor? ? instance_actor_outbox_url : account_outbox_url(object) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def featured |   def featured | ||||||
|     account_collection_url(object, :featured) |     account_collection_url(object, :featured) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def featured_tags | ||||||
|  |     account_collection_url(object, :tags) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def endpoints |   def endpoints | ||||||
|     object |     object | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -16,6 +16,8 @@ class ActivityPub::CollectionSerializer < ActivityPub::Serializer | ||||||
|       ActivityPub::NoteSerializer |       ActivityPub::NoteSerializer | ||||||
|     when 'Device' |     when 'Device' | ||||||
|       ActivityPub::DeviceSerializer |       ActivityPub::DeviceSerializer | ||||||
|  |     when 'FeaturedTag' | ||||||
|  |       ActivityPub::HashtagSerializer | ||||||
|     when 'ActivityPub::CollectionPresenter' |     when 'ActivityPub::CollectionPresenter' | ||||||
|       ActivityPub::CollectionSerializer |       ActivityPub::CollectionSerializer | ||||||
|     when 'String' |     when 'String' | ||||||
|  |  | ||||||
							
								
								
									
										23
									
								
								app/serializers/activitypub/hashtag_serializer.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/serializers/activitypub/hashtag_serializer.rb
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class ActivityPub::HashtagSerializer < ActivityPub::Serializer | ||||||
|  |   include RoutingHelper | ||||||
|  | 
 | ||||||
|  |   attributes :type, :href, :name | ||||||
|  | 
 | ||||||
|  |   def type | ||||||
|  |     'Hashtag' | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def name | ||||||
|  |     "##{object.name}" | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def href | ||||||
|  |     if object.class.name == 'FeaturedTag' | ||||||
|  |       short_account_tag_url(object.account, object.tag) | ||||||
|  |     else | ||||||
|  |       tag_url(object) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										19
									
								
								app/serializers/rest/account_featured_tag_serializer.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/serializers/rest/account_featured_tag_serializer.rb
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class REST::AccountFeaturedTagSerializer < ActiveModel::Serializer | ||||||
|  |   include RoutingHelper | ||||||
|  | 
 | ||||||
|  |   attributes :id, :name, :url | ||||||
|  | 
 | ||||||
|  |   def id | ||||||
|  |     object.tag.id.to_s | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def name | ||||||
|  |     "##{object.name}" | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def url | ||||||
|  |     short_account_tag_url(object.account, object.tag) | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -37,6 +37,7 @@ Rails.application.routes.draw do | ||||||
| 
 | 
 | ||||||
|   resource :instance_actor, path: 'actor', only: [:show] do |   resource :instance_actor, path: 'actor', only: [:show] do | ||||||
|     resource :inbox, only: [:create], module: :activitypub |     resource :inbox, only: [:create], module: :activitypub | ||||||
|  |     resource :outbox, only: [:show], module: :activitypub | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   devise_scope :user do |   devise_scope :user do | ||||||
|  | @ -438,6 +439,7 @@ Rails.application.routes.draw do | ||||||
|         resources :following, only: :index, controller: 'accounts/following_accounts' |         resources :following, only: :index, controller: 'accounts/following_accounts' | ||||||
|         resources :lists, only: :index, controller: 'accounts/lists' |         resources :lists, only: :index, controller: 'accounts/lists' | ||||||
|         resources :identity_proofs, only: :index, controller: 'accounts/identity_proofs' |         resources :identity_proofs, only: :index, controller: 'accounts/identity_proofs' | ||||||
|  |         resources :featured_tags, only: :index, controller: 'accounts/featured_tags' | ||||||
| 
 | 
 | ||||||
|         member do |         member do | ||||||
|           post :follow |           post :follow | ||||||
|  |  | ||||||
|  | @ -67,6 +67,7 @@ module Mastodon | ||||||
|       when :s3 |       when :s3 | ||||||
|         paperclip_instance = MediaAttachment.new.file |         paperclip_instance = MediaAttachment.new.file | ||||||
|         s3_interface       = paperclip_instance.s3_interface |         s3_interface       = paperclip_instance.s3_interface | ||||||
|  |         s3_permissions     = Paperclip::Attachment.default_options[:s3_permissions] | ||||||
|         bucket             = s3_interface.bucket(Paperclip::Attachment.default_options[:s3_credentials][:bucket]) |         bucket             = s3_interface.bucket(Paperclip::Attachment.default_options[:s3_credentials][:bucket]) | ||||||
|         last_key           = options[:start_after] |         last_key           = options[:start_after] | ||||||
| 
 | 
 | ||||||
|  | @ -87,7 +88,7 @@ module Mastodon | ||||||
|           record_map = preload_records_from_mixed_objects(objects) |           record_map = preload_records_from_mixed_objects(objects) | ||||||
| 
 | 
 | ||||||
|           objects.each do |object| |           objects.each do |object| | ||||||
|             object.acl.put(acl: 'public-read') if options[:fix_permissions] && !options[:dry_run] |             object.acl.put(acl: s3_permissions) if options[:fix_permissions] && !options[:dry_run] | ||||||
| 
 | 
 | ||||||
|             path_segments = object.key.split('/') |             path_segments = object.key.split('/') | ||||||
|             path_segments.delete('cache') |             path_segments.delete('cache') | ||||||
|  |  | ||||||
|  | @ -448,7 +448,7 @@ RSpec.describe FeedManager do | ||||||
|       FeedManager.instance.push_to_home(receiver, another_status) |       FeedManager.instance.push_to_home(receiver, another_status) | ||||||
| 
 | 
 | ||||||
|       # We should have a tracking set and an entry in reblogs. |       # We should have a tracking set and an entry in reblogs. | ||||||
|       expect(Redis.current.exists(reblog_set_key)).to be true |       expect(Redis.current.exists?(reblog_set_key)).to be true | ||||||
|       expect(Redis.current.zrange(reblogs_key, 0, -1)).to eq [reblogged.id.to_s] |       expect(Redis.current.zrange(reblogs_key, 0, -1)).to eq [reblogged.id.to_s] | ||||||
| 
 | 
 | ||||||
|       # Push everything off the end of the feed. |       # Push everything off the end of the feed. | ||||||
|  | @ -461,7 +461,7 @@ RSpec.describe FeedManager do | ||||||
|       FeedManager.instance.trim('home', receiver.id) |       FeedManager.instance.trim('home', receiver.id) | ||||||
| 
 | 
 | ||||||
|       # We should not have any reblog tracking data. |       # We should not have any reblog tracking data. | ||||||
|       expect(Redis.current.exists(reblog_set_key)).to be false |       expect(Redis.current.exists?(reblog_set_key)).to be false | ||||||
|       expect(Redis.current.zrange(reblogs_key, 0, -1)).to be_empty |       expect(Redis.current.zrange(reblogs_key, 0, -1)).to be_empty | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -150,9 +150,9 @@ RSpec.describe SpamCheck do | ||||||
|     let(:redis_key) { spam_check.send(:redis_key) } |     let(:redis_key) { spam_check.send(:redis_key) } | ||||||
| 
 | 
 | ||||||
|     it 'remembers' do |     it 'remembers' do | ||||||
|       expect(Redis.current.exists(redis_key)).to be true |       expect(Redis.current.exists?(redis_key)).to be true | ||||||
|       spam_check.remember! |       spam_check.remember! | ||||||
|       expect(Redis.current.exists(redis_key)).to be true |       expect(Redis.current.exists?(redis_key)).to be true | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  | @ -166,9 +166,9 @@ RSpec.describe SpamCheck do | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it 'resets' do |     it 'resets' do | ||||||
|       expect(Redis.current.exists(redis_key)).to be true |       expect(Redis.current.exists?(redis_key)).to be true | ||||||
|       spam_check.reset! |       spam_check.reset! | ||||||
|       expect(Redis.current.exists(redis_key)).to be false |       expect(Redis.current.exists?(redis_key)).to be false | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -55,9 +55,9 @@ RSpec.describe UnallowDomainService, type: :service do | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       it 'removes the remote accounts\'s statuses and media attachments' do |       it 'removes the remote accounts\'s statuses and media attachments' do | ||||||
|         expect { bad_status1.reload }.to_not raise_exception ActiveRecord::RecordNotFound |         expect { bad_status1.reload }.to_not raise_error | ||||||
|         expect { bad_status2.reload }.to_not raise_exception ActiveRecord::RecordNotFound |         expect { bad_status2.reload }.to_not raise_error | ||||||
|         expect { bad_attachment.reload }.to_not raise_exception ActiveRecord::RecordNotFound |         expect { bad_attachment.reload }.to_not raise_error | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -16,8 +16,8 @@ describe Scheduler::FeedCleanupScheduler do | ||||||
| 
 | 
 | ||||||
|     expect(Redis.current.zcard(feed_key_for(inactive_user))).to eq 0 |     expect(Redis.current.zcard(feed_key_for(inactive_user))).to eq 0 | ||||||
|     expect(Redis.current.zcard(feed_key_for(active_user))).to eq 1 |     expect(Redis.current.zcard(feed_key_for(active_user))).to eq 1 | ||||||
|     expect(Redis.current.exists(feed_key_for(inactive_user, 'reblogs'))).to be false |     expect(Redis.current.exists?(feed_key_for(inactive_user, 'reblogs'))).to be false | ||||||
|     expect(Redis.current.exists(feed_key_for(inactive_user, 'reblogs:2'))).to be false |     expect(Redis.current.exists?(feed_key_for(inactive_user, 'reblogs:2'))).to be false | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def feed_key_for(user, subtype = nil) |   def feed_key_for(user, subtype = nil) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue