Browse Source
Conflicts: - `.github/workflows/build-image.yml`: Fix erroneous deletion in a previous merge. - `Gemfile`: Conflict caused by glitch-soc-only hCaptcha dependency - `app/controllers/auth/sessions_controller.rb`: Minor conflict due to glitch-soc's theming system. - `app/controllers/filters_controller.rb`: Minor conflict due to glitch-soc's theming system. - `app/serializers/rest/status_serializer.rb`: Minor conflict due to glitch-soc having an extra `local_only` propertymain

108 changed files with 2057 additions and 360 deletions
@ -0,0 +1,43 @@
|
||||
name: Build container image |
||||
on: |
||||
workflow_dispatch: |
||||
push: |
||||
branches: |
||||
- 'main' |
||||
tags: |
||||
- '*' |
||||
pull_request: |
||||
paths: |
||||
- .github/workflows/build-image.yml |
||||
- Dockerfile |
||||
jobs: |
||||
build-image: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v3 |
||||
- uses: docker/setup-qemu-action@v2 |
||||
- uses: docker/setup-buildx-action@v2 |
||||
- uses: docker/login-action@v2 |
||||
with: |
||||
registry: ghcr.io |
||||
username: ${{ github.repository_owner }} |
||||
password: ${{ secrets.GITHUB_TOKEN }} |
||||
if: github.event_name != 'pull_request' |
||||
- uses: docker/metadata-action@v4 |
||||
id: meta |
||||
with: |
||||
images: ghcr.io/${{ github.repository_owner }}/mastodon |
||||
flavor: | |
||||
latest=auto |
||||
tags: | |
||||
type=edge,branch=main |
||||
type=match,pattern=v(.*),group=0 |
||||
type=ref,event=pr |
||||
- uses: docker/build-push-action@v3 |
||||
with: |
||||
context: . |
||||
platforms: linux/amd64,linux/arm64 |
||||
push: ${{ github.event_name != 'pull_request' }} |
||||
tags: ${{ steps.meta.outputs.tags }} |
||||
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/mastodon:latest |
||||
cache-to: type=inline |
@ -0,0 +1,95 @@
|
||||
# frozen_string_literal: true |
||||
|
||||
class Api::V1::Admin::DomainAllowsController < Api::BaseController |
||||
include Authorization |
||||
include AccountableConcern |
||||
|
||||
LIMIT = 100 |
||||
|
||||
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:domain_allows' }, only: [:index, :show] |
||||
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:domain_allows' }, except: [:index, :show] |
||||
before_action :require_staff! |
||||
before_action :set_domain_allows, only: :index |
||||
before_action :set_domain_allow, only: [:show, :destroy] |
||||
|
||||
after_action :insert_pagination_headers, only: :index |
||||
|
||||
PAGINATION_PARAMS = %i(limit).freeze |
||||
|
||||
def create |
||||
authorize :domain_allow, :create? |
||||
|
||||
@domain_allow = DomainAllow.find_by(resource_params) |
||||
|
||||
if @domain_allow.nil? |
||||
@domain_allow = DomainAllow.create!(resource_params) |
||||
log_action :create, @domain_allow |
||||
end |
||||
|
||||
render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer |
||||
end |
||||
|
||||
def index |
||||
authorize :domain_allow, :index? |
||||
render json: @domain_allows, each_serializer: REST::Admin::DomainAllowSerializer |
||||
end |
||||
|
||||
def show |
||||
authorize @domain_allow, :show? |
||||
render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer |
||||
end |
||||
|
||||
def destroy |
||||
authorize @domain_allow, :destroy? |
||||
UnallowDomainService.new.call(@domain_allow) |
||||
log_action :destroy, @domain_allow |
||||
render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer |
||||
end |
||||
|
||||
private |
||||
|
||||
def set_domain_allows |
||||
@domain_allows = filtered_domain_allows.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) |
||||
end |
||||
|
||||
def set_domain_allow |
||||
@domain_allow = DomainAllow.find(params[:id]) |
||||
end |
||||
|
||||
def filtered_domain_allows |
||||
# TODO: no filtering yet |
||||
DomainAllow.all |
||||
end |
||||
|
||||
def insert_pagination_headers |
||||
set_pagination_headers(next_path, prev_path) |
||||
end |
||||
|
||||
def next_path |
||||
api_v1_admin_domain_allows_url(pagination_params(max_id: pagination_max_id)) if records_continue? |
||||
end |
||||
|
||||
def prev_path |
||||
api_v1_admin_domain_allows_url(pagination_params(min_id: pagination_since_id)) unless @domain_allows.empty? |
||||
end |
||||
|
||||
def pagination_max_id |
||||
@domain_allows.last.id |
||||
end |
||||
|
||||
def pagination_since_id |
||||
@domain_allows.first.id |
||||
end |
||||
|
||||
def records_continue? |
||||
@domain_allows.size == limit_param(LIMIT) |
||||
end |
||||
|
||||
def pagination_params(core_params) |
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) |
||||
end |
||||
|
||||
def resource_params |
||||
params.permit(:domain) |
||||
end |
||||
end |
@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true |
||||
|
||||
class Api::V1::Filters::KeywordsController < Api::BaseController |
||||
before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show] |
||||
before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show] |
||||
before_action :require_user! |
||||
|
||||
before_action :set_keywords, only: :index |
||||
before_action :set_keyword, only: [:show, :update, :destroy] |
||||
|
||||
def index |
||||
render json: @keywords, each_serializer: REST::FilterKeywordSerializer |
||||
end |
||||
|
||||
def create |
||||
@keyword = current_account.custom_filters.find(params[:filter_id]).keywords.create!(resource_params) |
||||
|
||||
render json: @keyword, serializer: REST::FilterKeywordSerializer |
||||
end |
||||
|
||||
def show |
||||
render json: @keyword, serializer: REST::FilterKeywordSerializer |
||||
end |
||||
|
||||
def update |
||||
@keyword.update!(resource_params) |
||||
|
||||
render json: @keyword, serializer: REST::FilterKeywordSerializer |
||||
end |
||||
|
||||
def destroy |
||||
@keyword.destroy! |
||||
render_empty |
||||
end |
||||
|
||||
private |
||||
|
||||
def set_keywords |
||||
filter = current_account.custom_filters.includes(:keywords).find(params[:filter_id]) |
||||
@keywords = filter.keywords |
||||
end |
||||
|
||||
def set_keyword |
||||
@keyword = CustomFilterKeyword.includes(:custom_filter).where(custom_filter: { account: current_account }).find(params[:id]) |
||||
end |
||||
|
||||
def resource_params |
||||
params.permit(:keyword, :whole_word) |
||||
end |
||||
end |
@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true |
||||
|
||||
class Api::V2::FiltersController < Api::BaseController |
||||
before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show] |
||||
before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show] |
||||
before_action :require_user! |
||||
before_action :set_filters, only: :index |
||||
before_action :set_filter, only: [:show, :update, :destroy] |
||||
|
||||
def index |
||||
render json: @filters, each_serializer: REST::FilterSerializer, rules_requested: true |
||||
end |
||||
|
||||
def create |
||||
@filter = current_account.custom_filters.create!(resource_params) |
||||
|
||||
render json: @filter, serializer: REST::FilterSerializer, rules_requested: true |
||||
end |
||||
|
||||
def show |
||||
render json: @filter, serializer: REST::FilterSerializer, rules_requested: true |
||||
end |
||||
|
||||
def update |
||||
@filter.update!(resource_params) |
||||
|
||||
render json: @filter, serializer: REST::FilterSerializer, rules_requested: true |
||||
end |
||||
|
||||
def destroy |
||||
@filter.destroy! |
||||
render_empty |
||||
end |
||||
|
||||
private |
||||
|
||||
def set_filters |
||||
@filters = current_account.custom_filters.includes(:keywords) |
||||
end |
||||
|
||||
def set_filter |
||||
@filter = current_account.custom_filters.find(params[:id]) |
||||
end |
||||
|
||||
def resource_params |
||||
params.permit(:title, :expires_in, :filter_action, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy]) |
||||
end |
||||
end |
@ -1,26 +0,0 @@
|
||||
import api from '../api'; |
||||
|
||||
export const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST'; |
||||
export const FILTERS_FETCH_SUCCESS = 'FILTERS_FETCH_SUCCESS'; |
||||
export const FILTERS_FETCH_FAIL = 'FILTERS_FETCH_FAIL'; |
||||
|
||||
export const fetchFilters = () => (dispatch, getState) => { |
||||
dispatch({ |
||||
type: FILTERS_FETCH_REQUEST, |
||||
skipLoading: true, |
||||
}); |
||||
|
||||
api(getState) |
||||
.get('/api/v1/filters') |
||||
.then(({ data }) => dispatch({ |
||||
type: FILTERS_FETCH_SUCCESS, |
||||
filters: data, |
||||
skipLoading: true, |
||||
})) |
||||
.catch(err => dispatch({ |
||||
type: FILTERS_FETCH_FAIL, |
||||
err, |
||||
skipLoading: true, |
||||
skipAlert: true, |
||||
})); |
||||
}; |
@ -0,0 +1,62 @@
|
||||
import React, { Fragment } from 'react'; |
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import PropTypes from 'prop-types'; |
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; |
||||
import ImmutablePureComponent from 'react-immutable-pure-component'; |
||||
import AvatarOverlay from 'mastodon/components/avatar_overlay'; |
||||
import RelativeTimestamp from 'mastodon/components/relative_timestamp'; |
||||
|
||||
const messages = defineMessages({ |
||||
openReport: { id: 'report_notification.open', defaultMessage: 'Open report' }, |
||||
other: { id: 'report_notification.categories.other', defaultMessage: 'Other' }, |
||||
spam: { id: 'report_notification.categories.spam', defaultMessage: 'Spam' }, |
||||
violation: { id: 'report_notification.categories.violation', defaultMessage: 'Rule violation' }, |
||||
}); |
||||
|
||||
export default @injectIntl |
||||
class Report extends ImmutablePureComponent { |
||||
|
||||
static propTypes = { |
||||
account: ImmutablePropTypes.map.isRequired, |
||||
report: ImmutablePropTypes.map.isRequired, |
||||
hidden: PropTypes.bool, |
||||
intl: PropTypes.object.isRequired, |
||||
}; |
||||
|
||||
render () { |
||||
const { intl, hidden, report, account } = this.props; |
||||
|
||||
if (!report) { |
||||
return null; |
||||
} |
||||
|
||||
if (hidden) { |
||||
return ( |
||||
<Fragment> |
||||
{report.get('id')} |
||||
</Fragment> |
||||
); |
||||
} |
||||
|
||||
return ( |
||||
<div className='notification__report'> |
||||
<div className='notification__report__avatar'> |
||||
<AvatarOverlay account={report.get('target_account')} friend={account} /> |
||||
</div> |
||||
|
||||
<div className='notification__report__details'> |
||||
<div> |
||||
<RelativeTimestamp timestamp={report.get('created_at')} short={false} /> · <FormattedMessage id='report_notification.attached_statuses' defaultMessage='{count, plural, one {{count} post} other {{count} posts}} attached' values={{ count: report.get('status_ids').size }} /> |
||||
<br /> |
||||
<strong>{intl.formatMessage(messages[report.get('category')])}</strong> |
||||
</div> |
||||
|
||||
<div className='notification__report__actions'> |
||||
<a href={`/admin/reports/${report.get('id')}`} className='button' target='_blank' rel='noopener noreferrer'>{intl.formatMessage(messages.openReport)}</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
} |