Merge remote-tracking branch 'parent/main' into upstream-20240125
This commit is contained in:
commit
9fa938eb0f
68 changed files with 824 additions and 94 deletions
16
.github/workflows/test-ruby.yml
vendored
16
.github/workflows/test-ruby.yml
vendored
|
@ -52,7 +52,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs*
|
tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs*
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
if: matrix.mode == 'test'
|
if: matrix.mode == 'test'
|
||||||
with:
|
with:
|
||||||
path: |-
|
path: |-
|
||||||
|
@ -118,7 +118,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: './'
|
path: './'
|
||||||
name: ${{ github.sha }}
|
name: ${{ github.sha }}
|
||||||
|
@ -195,7 +195,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: './public'
|
path: './public'
|
||||||
name: ${{ github.sha }}
|
name: ${{ github.sha }}
|
||||||
|
@ -215,14 +215,14 @@ jobs:
|
||||||
- run: bundle exec rake spec:system
|
- run: bundle exec rake spec:system
|
||||||
|
|
||||||
- name: Archive logs
|
- name: Archive logs
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: e2e-logs-${{ matrix.ruby-version }}
|
name: e2e-logs-${{ matrix.ruby-version }}
|
||||||
path: log/
|
path: log/
|
||||||
|
|
||||||
- name: Archive test screenshots
|
- name: Archive test screenshots
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: e2e-screenshots
|
name: e2e-screenshots
|
||||||
|
@ -286,7 +286,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: './public'
|
path: './public'
|
||||||
name: ${{ github.sha }}
|
name: ${{ github.sha }}
|
||||||
|
@ -336,14 +336,14 @@ jobs:
|
||||||
- run: bin/rspec --tag search
|
- run: bin/rspec --tag search
|
||||||
|
|
||||||
- name: Archive logs
|
- name: Archive logs
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: test-search-logs-${{ matrix.ruby-version }}
|
name: test-search-logs-${{ matrix.ruby-version }}
|
||||||
path: log/
|
path: log/
|
||||||
|
|
||||||
- name: Archive test screenshots
|
- name: Archive test screenshots
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: test-search-screenshots
|
name: test-search-screenshots
|
||||||
|
|
|
@ -80,13 +80,6 @@ Rails/WhereExists:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/controllers/activitypub/inboxes_controller.rb'
|
- 'app/controllers/activitypub/inboxes_controller.rb'
|
||||||
- 'app/controllers/admin/email_domain_blocks_controller.rb'
|
- 'app/controllers/admin/email_domain_blocks_controller.rb'
|
||||||
- 'app/lib/activitypub/activity/create.rb'
|
|
||||||
- 'app/lib/delivery_failure_tracker.rb'
|
|
||||||
- 'app/lib/feed_manager.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/policies/status_policy.rb'
|
||||||
- 'app/serializers/rest/announcement_serializer.rb'
|
- 'app/serializers/rest/announcement_serializer.rb'
|
||||||
- 'app/workers/move_worker.rb'
|
- 'app/workers/move_worker.rb'
|
||||||
|
|
17
Gemfile.lock
17
Gemfile.lock
|
@ -180,7 +180,7 @@ GEM
|
||||||
activesupport
|
activesupport
|
||||||
cbor (0.5.9.6)
|
cbor (0.5.9.6)
|
||||||
charlock_holmes (0.7.7)
|
charlock_holmes (0.7.7)
|
||||||
chewy (7.4.0)
|
chewy (7.5.0)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
elasticsearch (>= 7.12.0, < 7.14.0)
|
elasticsearch (>= 7.12.0, < 7.14.0)
|
||||||
elasticsearch-dsl
|
elasticsearch-dsl
|
||||||
|
@ -319,7 +319,7 @@ GEM
|
||||||
activesupport (>= 5.1)
|
activesupport (>= 5.1)
|
||||||
haml (>= 4.0.6)
|
haml (>= 4.0.6)
|
||||||
railties (>= 5.1)
|
railties (>= 5.1)
|
||||||
haml_lint (0.53.0)
|
haml_lint (0.55.0)
|
||||||
haml (>= 5.0)
|
haml (>= 5.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
rainbow
|
rainbow
|
||||||
|
@ -445,7 +445,7 @@ GEM
|
||||||
mime-types-data (3.2023.1205)
|
mime-types-data (3.2023.1205)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.5)
|
mini_portile2 (2.8.5)
|
||||||
minitest (5.20.0)
|
minitest (5.21.1)
|
||||||
msgpack (1.7.2)
|
msgpack (1.7.2)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.3.0)
|
multipart-post (2.3.0)
|
||||||
|
@ -504,7 +504,7 @@ GEM
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
ox (2.14.17)
|
ox (2.14.17)
|
||||||
parallel (1.24.0)
|
parallel (1.24.0)
|
||||||
parser (3.2.2.4)
|
parser (3.3.0.5)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
parslet (2.0.0)
|
parslet (2.0.0)
|
||||||
|
@ -610,7 +610,7 @@ GEM
|
||||||
redis (>= 4)
|
redis (>= 4)
|
||||||
redlock (1.3.2)
|
redlock (1.3.2)
|
||||||
redis (>= 3.0.0, < 6.0)
|
redis (>= 3.0.0, < 6.0)
|
||||||
regexp_parser (2.8.3)
|
regexp_parser (2.9.0)
|
||||||
reline (0.4.2)
|
reline (0.4.2)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
request_store (1.5.1)
|
request_store (1.5.1)
|
||||||
|
@ -652,11 +652,11 @@ GEM
|
||||||
rspec-mocks (~> 3.0)
|
rspec-mocks (~> 3.0)
|
||||||
sidekiq (>= 5, < 8)
|
sidekiq (>= 5, < 8)
|
||||||
rspec-support (3.12.1)
|
rspec-support (3.12.1)
|
||||||
rubocop (1.59.0)
|
rubocop (1.60.2)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.2.2.4)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
rexml (>= 3.2.5, < 4.0)
|
rexml (>= 3.2.5, < 4.0)
|
||||||
|
@ -698,7 +698,8 @@ GEM
|
||||||
scenic (1.7.0)
|
scenic (1.7.0)
|
||||||
activerecord (>= 4.0.0)
|
activerecord (>= 4.0.0)
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
selenium-webdriver (4.16.0)
|
selenium-webdriver (4.17.0)
|
||||||
|
base64 (~> 0.2)
|
||||||
rexml (~> 3.2, >= 3.2.5)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
rubyzip (>= 1.2.2, < 3.0)
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
websocket (~> 1.0)
|
websocket (~> 1.0)
|
||||||
|
|
30
app/controllers/api/v1/annual_reports_controller.rb
Normal file
30
app/controllers/api/v1/annual_reports_controller.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::AnnualReportsController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
|
||||||
|
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_annual_report, except: :index
|
||||||
|
|
||||||
|
def index
|
||||||
|
with_read_replica do
|
||||||
|
@presenter = AnnualReportsPresenter.new(GeneratedAnnualReport.where(account_id: current_account.id).pending)
|
||||||
|
@relationships = StatusRelationshipsPresenter.new(@presenter.statuses, current_account.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: @presenter,
|
||||||
|
serializer: REST::AnnualReportsSerializer,
|
||||||
|
relationships: @relationships
|
||||||
|
end
|
||||||
|
|
||||||
|
def read
|
||||||
|
@annual_report.view!
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_annual_report
|
||||||
|
@annual_report = GeneratedAnnualReport.find_by!(account_id: current_account.id, year: params[:id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -21,10 +21,19 @@ module WebAppControllerConcern
|
||||||
def redirect_unauthenticated_to_permalinks!
|
def redirect_unauthenticated_to_permalinks!
|
||||||
return if user_signed_in? && current_account.moved_to_account_id.nil?
|
return if user_signed_in? && current_account.moved_to_account_id.nil?
|
||||||
|
|
||||||
redirect_path = PermalinkRedirector.new(request.path).redirect_path
|
permalink_redirector = PermalinkRedirector.new(request.path)
|
||||||
return if redirect_path.blank?
|
return if permalink_redirector.redirect_path.blank?
|
||||||
|
|
||||||
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
||||||
redirect_to(redirect_path)
|
|
||||||
|
respond_to do |format|
|
||||||
|
format.html do
|
||||||
|
redirect_to(permalink_redirector.redirect_confirmation_path, allow_other_host: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
format.json do
|
||||||
|
redirect_to(permalink_redirector.redirect_uri, allow_other_host: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
10
app/controllers/redirect/accounts_controller.rb
Normal file
10
app/controllers/redirect/accounts_controller.rb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Redirect::AccountsController < ApplicationController
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_resource
|
||||||
|
@resource = Account.find(params[:id])
|
||||||
|
not_found if @resource.local?
|
||||||
|
end
|
||||||
|
end
|
24
app/controllers/redirect/base_controller.rb
Normal file
24
app/controllers/redirect/base_controller.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Redirect::BaseController < ApplicationController
|
||||||
|
vary_by 'Accept-Language'
|
||||||
|
|
||||||
|
before_action :set_resource
|
||||||
|
before_action :set_app_body_class
|
||||||
|
|
||||||
|
def show
|
||||||
|
@redirect_path = ActivityPub::TagManager.instance.url_for(@resource)
|
||||||
|
|
||||||
|
render 'redirects/show', layout: 'application'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_app_body_class
|
||||||
|
@body_classes = 'app-body'
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_resource
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
end
|
10
app/controllers/redirect/statuses_controller.rb
Normal file
10
app/controllers/redirect/statuses_controller.rb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Redirect::StatusesController < Redirect::BaseController
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_resource
|
||||||
|
@resource = Status.find(params[:id])
|
||||||
|
not_found if @resource.local? || !@resource.distributable?
|
||||||
|
end
|
||||||
|
end
|
|
@ -19,8 +19,10 @@ import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||||
import StarBorderIcon from '@/material-icons/400-24px/star.svg?react';
|
import StarBorderIcon from '@/material-icons/400-24px/star.svg?react';
|
||||||
import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react';
|
import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react';
|
||||||
import RepeatDisabledIcon from 'mastodon/../svg-icons/repeat_disabled.svg?react';
|
import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
|
||||||
import RepeatPrivateIcon from 'mastodon/../svg-icons/repeat_private.svg?react';
|
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
|
||||||
|
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
|
||||||
|
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
|
||||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||||
|
|
||||||
|
@ -444,7 +446,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (status.get('reblogged')) {
|
if (status.get('reblogged')) {
|
||||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||||
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
|
reblogIconComponent = publicStatus ? RepeatActiveIcon : RepeatPrivateActiveIcon;
|
||||||
} else if (publicStatus) {
|
} else if (publicStatus) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog);
|
reblogTitle = intl.formatMessage(messages.reblog);
|
||||||
reblogIconComponent = RepeatIcon;
|
reblogIconComponent = RepeatIcon;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -12,10 +13,15 @@ import {
|
||||||
|
|
||||||
import Search from '../components/search';
|
import Search from '../components/search';
|
||||||
|
|
||||||
|
const getRecentSearches = createSelector(
|
||||||
|
state => state.getIn(['search', 'recent']),
|
||||||
|
recent => recent.reverse(),
|
||||||
|
);
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
value: state.getIn(['search', 'value']),
|
value: state.getIn(['search', 'value']),
|
||||||
submitted: state.getIn(['search', 'submitted']),
|
submitted: state.getIn(['search', 'submitted']),
|
||||||
recent: state.getIn(['search', 'recent']).reverse(),
|
recent: getRecentSearches(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
|
@ -18,8 +18,10 @@ import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||||
import StarBorderIcon from '@/material-icons/400-24px/star.svg?react';
|
import StarBorderIcon from '@/material-icons/400-24px/star.svg?react';
|
||||||
import RepeatDisabledIcon from 'mastodon/../svg-icons/repeat_disabled.svg?react';
|
import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
|
||||||
import RepeatPrivateIcon from 'mastodon/../svg-icons/repeat_private.svg?react';
|
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
|
||||||
|
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
|
||||||
|
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
|
||||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||||
|
|
||||||
|
@ -356,7 +358,7 @@ class ActionBar extends PureComponent {
|
||||||
|
|
||||||
if (status.get('reblogged')) {
|
if (status.get('reblogged')) {
|
||||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||||
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
|
reblogIconComponent = publicStatus ? RepeatActiveIcon : RepeatPrivateActiveIcon;
|
||||||
} else if (publicStatus) {
|
} else if (publicStatus) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog);
|
reblogTitle = intl.formatMessage(messages.reblog);
|
||||||
reblogIconComponent = RepeatIcon;
|
reblogIconComponent = RepeatIcon;
|
||||||
|
|
|
@ -104,3 +104,59 @@
|
||||||
margin-inline-start: 10px;
|
margin-inline-start: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.redirect {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 18px;
|
||||||
|
|
||||||
|
&__logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__message {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $highlight-text-color;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
4
app/javascript/svg-icons/repeat_active.svg
Normal file
4
app/javascript/svg-icons/repeat_active.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7 22L3 18L7 14L8.4 15.45L6.85 17H17V13H19V19H6.85L8.4 20.55L7 22ZM5 11V5H17.15L15.6 3.45L17 2L21 6L17 10L15.6 8.55L17.15 7H7V11H5Z"/>
|
||||||
|
<path d="M9 9H15V15H9V9Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 275 B |
0
app/javascript/svg-icons/repeat_disabled.svg
Executable file → Normal file
0
app/javascript/svg-icons/repeat_disabled.svg
Executable file → Normal file
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 415 B |
0
app/javascript/svg-icons/repeat_private.svg
Executable file → Normal file
0
app/javascript/svg-icons/repeat_private.svg
Executable file → Normal file
Before Width: | Height: | Size: 879 B After Width: | Height: | Size: 879 B |
6
app/javascript/svg-icons/repeat_private_active.svg
Normal file
6
app/javascript/svg-icons/repeat_private_active.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.4 15.45L7 14L3 18L7 22L8.4 20.55L6.85 19H13.5V18C13.5 17.6567 13.5638 17.3171 13.6988 17H6.85L8.4 15.45Z"/>
|
||||||
|
<path d="M15 14.1883C14.8435 14.443 14.7232 14.7147 14.6398 15H9V9H15V14.1883Z"/>
|
||||||
|
<path d="M5 5V11H7V7H17.15L15.6 8.55L17 10L21 6L17 2L15.6 3.45L17.15 5H5Z"/>
|
||||||
|
<path d="M16 22C15.7167 22 15.475 21.9083 15.275 21.725C15.0917 21.525 15 21.2833 15 21V18C15 17.7167 15.0917 17.4833 15.275 17.3C15.475 17.1 15.7167 17 16 17V16C16 15.45 16.1917 14.9833 16.575 14.6C16.975 14.2 17.45 14 18 14C18.55 14 19.0167 14.2 19.4 14.6C19.8 14.9833 20 15.45 20 16V17C20.2833 17 20.5167 17.1 20.7 17.3C20.9 17.4833 21 17.7167 21 18V21C21 21.2833 20.9 21.525 20.7 21.725C20.5167 21.9083 20.2833 22 20 22H16ZM17 17H19V16C19 15.7167 18.9 15.4833 18.7 15.3C18.5167 15.1 18.2833 15 18 15C17.7167 15 17.475 15.1 17.275 15.3C17.0917 15.4833 17 15.7167 17 16V17Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 961 B |
|
@ -357,7 +357,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
already_voted = true
|
already_voted = true
|
||||||
|
|
||||||
with_redis_lock("vote:#{replied_to_status.poll_id}:#{@account.id}") do
|
with_redis_lock("vote:#{replied_to_status.poll_id}:#{@account.id}") do
|
||||||
already_voted = poll.votes.where(account: @account).exists?
|
already_voted = poll.votes.exists?(account: @account)
|
||||||
poll.votes.create!(account: @account, choice: poll.options.index(@object['name']), uri: object_uri)
|
poll.votes.create!(account: @account, choice: poll.options.index(@object['name']), uri: object_uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -503,7 +503,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
|
|
||||||
return false if local_usernames.empty?
|
return false if local_usernames.empty?
|
||||||
|
|
||||||
Account.local.where(username: local_usernames).exists?
|
Account.local.exists?(username: local_usernames)
|
||||||
end
|
end
|
||||||
|
|
||||||
def tombstone_exists?
|
def tombstone_exists?
|
||||||
|
|
43
app/lib/annual_report.rb
Normal file
43
app/lib/annual_report.rb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReport
|
||||||
|
include DatabaseHelper
|
||||||
|
|
||||||
|
SOURCES = [
|
||||||
|
AnnualReport::Archetype,
|
||||||
|
AnnualReport::TypeDistribution,
|
||||||
|
AnnualReport::TopStatuses,
|
||||||
|
AnnualReport::MostUsedApps,
|
||||||
|
AnnualReport::CommonlyInteractedWithAccounts,
|
||||||
|
AnnualReport::TimeSeries,
|
||||||
|
AnnualReport::TopHashtags,
|
||||||
|
AnnualReport::MostRebloggedAccounts,
|
||||||
|
AnnualReport::Percentiles,
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
SCHEMA = 1
|
||||||
|
|
||||||
|
def initialize(account, year)
|
||||||
|
@account = account
|
||||||
|
@year = year
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate
|
||||||
|
return if GeneratedAnnualReport.exists?(account: @account, year: @year)
|
||||||
|
|
||||||
|
GeneratedAnnualReport.create(
|
||||||
|
account: @account,
|
||||||
|
year: @year,
|
||||||
|
schema_version: SCHEMA,
|
||||||
|
data: data
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def data
|
||||||
|
with_read_replica do
|
||||||
|
SOURCES.each_with_object({}) { |klass, hsh| hsh.merge!(klass.new(@account, @year).generate) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
49
app/lib/annual_report/archetype.rb
Normal file
49
app/lib/annual_report/archetype.rb
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReport::Archetype < AnnualReport::Source
|
||||||
|
# Average number of posts (including replies and reblogs) made by
|
||||||
|
# each active user in a single year (2023)
|
||||||
|
AVERAGE_PER_YEAR = 113
|
||||||
|
|
||||||
|
def generate
|
||||||
|
{
|
||||||
|
archetype: archetype,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def archetype
|
||||||
|
if (standalone_count + replies_count + reblogs_count) < AVERAGE_PER_YEAR
|
||||||
|
:lurker
|
||||||
|
elsif reblogs_count > (standalone_count * 2)
|
||||||
|
:booster
|
||||||
|
elsif polls_count > (standalone_count * 0.1) # standalone_count includes posts with polls
|
||||||
|
:pollster
|
||||||
|
elsif replies_count > (standalone_count * 2)
|
||||||
|
:replier
|
||||||
|
else
|
||||||
|
:oracle
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def polls_count
|
||||||
|
@polls_count ||= base_scope.where.not(poll_id: nil).count
|
||||||
|
end
|
||||||
|
|
||||||
|
def reblogs_count
|
||||||
|
@reblogs_count ||= base_scope.where.not(reblog_of_id: nil).count
|
||||||
|
end
|
||||||
|
|
||||||
|
def replies_count
|
||||||
|
@replies_count ||= base_scope.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count
|
||||||
|
end
|
||||||
|
|
||||||
|
def standalone_count
|
||||||
|
@standalone_count ||= base_scope.without_replies.without_reblogs.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def base_scope
|
||||||
|
@account.statuses.where(id: year_as_snowflake_range)
|
||||||
|
end
|
||||||
|
end
|
22
app/lib/annual_report/commonly_interacted_with_accounts.rb
Normal file
22
app/lib/annual_report/commonly_interacted_with_accounts.rb
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReport::CommonlyInteractedWithAccounts < AnnualReport::Source
|
||||||
|
SET_SIZE = 40
|
||||||
|
|
||||||
|
def generate
|
||||||
|
{
|
||||||
|
commonly_interacted_with_accounts: commonly_interacted_with_accounts.map do |(account_id, count)|
|
||||||
|
{
|
||||||
|
account_id: account_id,
|
||||||
|
count: count,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def commonly_interacted_with_accounts
|
||||||
|
@account.statuses.reorder(nil).where(id: year_as_snowflake_range).where.not(in_reply_to_account_id: @account.id).group(:in_reply_to_account_id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('in_reply_to_account_id, count(*) AS total'))
|
||||||
|
end
|
||||||
|
end
|
22
app/lib/annual_report/most_reblogged_accounts.rb
Normal file
22
app/lib/annual_report/most_reblogged_accounts.rb
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReport::MostRebloggedAccounts < AnnualReport::Source
|
||||||
|
SET_SIZE = 10
|
||||||
|
|
||||||
|
def generate
|
||||||
|
{
|
||||||
|
most_reblogged_accounts: most_reblogged_accounts.map do |(account_id, count)|
|
||||||
|
{
|
||||||
|
account_id: account_id,
|
||||||
|
count: count,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def most_reblogged_accounts
|
||||||
|
@account.statuses.reorder(nil).where(id: year_as_snowflake_range).where.not(reblog_of_id: nil).joins(reblog: :account).group('accounts.id').having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('accounts.id, count(*) as total'))
|
||||||
|
end
|
||||||
|
end
|
22
app/lib/annual_report/most_used_apps.rb
Normal file
22
app/lib/annual_report/most_used_apps.rb
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReport::MostUsedApps < AnnualReport::Source
|
||||||
|
SET_SIZE = 10
|
||||||
|
|
||||||
|
def generate
|
||||||
|
{
|
||||||
|
most_used_apps: most_used_apps.map do |(name, count)|
|
||||||
|
{
|
||||||
|
name: name,
|
||||||
|
count: count,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def most_used_apps
|
||||||
|
@account.statuses.reorder(nil).where(id: year_as_snowflake_range).joins(:application).group('oauth_applications.name').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('oauth_applications.name, count(*) as total'))
|
||||||
|
end
|
||||||
|
end
|
62
app/lib/annual_report/percentiles.rb
Normal file
62
app/lib/annual_report/percentiles.rb
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReport::Percentiles < AnnualReport::Source
|
||||||
|
def generate
|
||||||
|
{
|
||||||
|
percentiles: {
|
||||||
|
followers: (total_with_fewer_followers / (total_with_any_followers + 1.0)) * 100,
|
||||||
|
statuses: (total_with_fewer_statuses / (total_with_any_statuses + 1.0)) * 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def followers_gained
|
||||||
|
@followers_gained ||= @account.passive_relationships.where("date_part('year', follows.created_at) = ?", @year).count
|
||||||
|
end
|
||||||
|
|
||||||
|
def statuses_created
|
||||||
|
@statuses_created ||= @account.statuses.where(id: year_as_snowflake_range).count
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_with_fewer_followers
|
||||||
|
@total_with_fewer_followers ||= Follow.find_by_sql([<<~SQL.squish, { year: @year, comparison: followers_gained }]).first.total
|
||||||
|
WITH tmp0 AS (
|
||||||
|
SELECT follows.target_account_id
|
||||||
|
FROM follows
|
||||||
|
INNER JOIN accounts ON accounts.id = follows.target_account_id
|
||||||
|
WHERE date_part('year', follows.created_at) = :year
|
||||||
|
AND accounts.domain IS NULL
|
||||||
|
GROUP BY follows.target_account_id
|
||||||
|
HAVING COUNT(*) < :comparison
|
||||||
|
)
|
||||||
|
SELECT count(*) AS total
|
||||||
|
FROM tmp0
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_with_fewer_statuses
|
||||||
|
@total_with_fewer_statuses ||= Status.find_by_sql([<<~SQL.squish, { comparison: statuses_created, min_id: year_as_snowflake_range.first, max_id: year_as_snowflake_range.last }]).first.total
|
||||||
|
WITH tmp0 AS (
|
||||||
|
SELECT statuses.account_id
|
||||||
|
FROM statuses
|
||||||
|
INNER JOIN accounts ON accounts.id = statuses.account_id
|
||||||
|
WHERE statuses.id BETWEEN :min_id AND :max_id
|
||||||
|
AND accounts.domain IS NULL
|
||||||
|
GROUP BY statuses.account_id
|
||||||
|
HAVING count(*) < :comparison
|
||||||
|
)
|
||||||
|
SELECT count(*) AS total
|
||||||
|
FROM tmp0
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_with_any_followers
|
||||||
|
@total_with_any_followers ||= Follow.where("date_part('year', follows.created_at) = ?", @year).joins(:target_account).merge(Account.local).count('distinct follows.target_account_id')
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_with_any_statuses
|
||||||
|
@total_with_any_statuses ||= Status.where(id: year_as_snowflake_range).joins(:account).merge(Account.local).count('distinct statuses.account_id')
|
||||||
|
end
|
||||||
|
end
|
16
app/lib/annual_report/source.rb
Normal file
16
app/lib/annual_report/source.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReport::Source
|
||||||
|
attr_reader :account, :year
|
||||||
|
|
||||||
|
def initialize(account, year)
|
||||||
|
@account = account
|
||||||
|
@year = year
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def year_as_snowflake_range
|
||||||
|
(Mastodon::Snowflake.id_at(DateTime.new(year, 1, 1))..Mastodon::Snowflake.id_at(DateTime.new(year, 12, 31)))
|
||||||
|
end
|
||||||
|
end
|
30
app/lib/annual_report/time_series.rb
Normal file
30
app/lib/annual_report/time_series.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReport::TimeSeries < AnnualReport::Source
|
||||||
|
def generate
|
||||||
|
{
|
||||||
|
time_series: (1..12).map do |month|
|
||||||
|
{
|
||||||
|
month: month,
|
||||||
|
statuses: statuses_per_month[month] || 0,
|
||||||
|
following: following_per_month[month] || 0,
|
||||||
|
followers: followers_per_month[month] || 0,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def statuses_per_month
|
||||||
|
@statuses_per_month ||= @account.statuses.reorder(nil).where(id: year_as_snowflake_range).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h
|
||||||
|
end
|
||||||
|
|
||||||
|
def following_per_month
|
||||||
|
@following_per_month ||= @account.active_relationships.where("date_part('year', created_at) = ?", @year).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h
|
||||||
|
end
|
||||||
|
|
||||||
|
def followers_per_month
|
||||||
|
@followers_per_month ||= @account.passive_relationships.where("date_part('year', created_at) = ?", @year).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h
|
||||||
|
end
|
||||||
|
end
|
22
app/lib/annual_report/top_hashtags.rb
Normal file
22
app/lib/annual_report/top_hashtags.rb
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReport::TopHashtags < AnnualReport::Source
|
||||||
|
SET_SIZE = 40
|
||||||
|
|
||||||
|
def generate
|
||||||
|
{
|
||||||
|
top_hashtags: top_hashtags.map do |(name, count)|
|
||||||
|
{
|
||||||
|
name: name,
|
||||||
|
count: count,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def top_hashtags
|
||||||
|
Tag.joins(:statuses).where(statuses: { id: @account.statuses.where(id: year_as_snowflake_range).reorder(nil).select(:id) }).group(:id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('COALESCE(tags.display_name, tags.name), count(*) AS total'))
|
||||||
|
end
|
||||||
|
end
|
21
app/lib/annual_report/top_statuses.rb
Normal file
21
app/lib/annual_report/top_statuses.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReport::TopStatuses < AnnualReport::Source
|
||||||
|
def generate
|
||||||
|
top_reblogs = base_scope.order(reblogs_count: :desc).first&.id
|
||||||
|
top_favourites = base_scope.where.not(id: top_reblogs).order(favourites_count: :desc).first&.id
|
||||||
|
top_replies = base_scope.where.not(id: [top_reblogs, top_favourites]).order(replies_count: :desc).first&.id
|
||||||
|
|
||||||
|
{
|
||||||
|
top_statuses: {
|
||||||
|
by_reblogs: top_reblogs,
|
||||||
|
by_favourites: top_favourites,
|
||||||
|
by_replies: top_replies,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def base_scope
|
||||||
|
@account.statuses.with_public_visibility.joins(:status_stat).where(id: year_as_snowflake_range).reorder(nil)
|
||||||
|
end
|
||||||
|
end
|
20
app/lib/annual_report/type_distribution.rb
Normal file
20
app/lib/annual_report/type_distribution.rb
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReport::TypeDistribution < AnnualReport::Source
|
||||||
|
def generate
|
||||||
|
{
|
||||||
|
type_distribution: {
|
||||||
|
total: base_scope.count,
|
||||||
|
reblogs: base_scope.where.not(reblog_of_id: nil).count,
|
||||||
|
replies: base_scope.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count,
|
||||||
|
standalone: base_scope.without_replies.without_reblogs.count,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def base_scope
|
||||||
|
@account.statuses.where(id: year_as_snowflake_range)
|
||||||
|
end
|
||||||
|
end
|
|
@ -28,7 +28,7 @@ class DeliveryFailureTracker
|
||||||
end
|
end
|
||||||
|
|
||||||
def available?
|
def available?
|
||||||
!UnavailableDomain.where(domain: @host).exists?
|
!UnavailableDomain.exists?(domain: @host)
|
||||||
end
|
end
|
||||||
|
|
||||||
def exhausted_deliveries_days
|
def exhausted_deliveries_days
|
||||||
|
|
|
@ -458,8 +458,8 @@ class FeedManager
|
||||||
check_for_blocks = status.active_mentions.pluck(:account_id)
|
check_for_blocks = status.active_mentions.pluck(:account_id)
|
||||||
check_for_blocks.push(status.in_reply_to_account) if status.reply? && !status.in_reply_to_account_id.nil?
|
check_for_blocks.push(status.in_reply_to_account) if status.reply? && !status.in_reply_to_account_id.nil?
|
||||||
|
|
||||||
should_filter = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted)
|
should_filter = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted)
|
||||||
should_filter ||= status.account.silenced? && !Follow.where(account_id: receiver_id, target_account_id: status.account_id).exists? # of if the account is silenced and I'm not following them
|
should_filter ||= status.account.silenced? && !Follow.exists?(account_id: receiver_id, target_account_id: status.account_id) # Filter if the account is silenced and I'm not following them
|
||||||
|
|
||||||
should_filter
|
should_filter
|
||||||
end
|
end
|
||||||
|
@ -472,7 +472,7 @@ class FeedManager
|
||||||
if status.reply? && status.in_reply_to_account_id != status.account_id
|
if status.reply? && status.in_reply_to_account_id != status.account_id
|
||||||
should_filter = status.in_reply_to_account_id != list.account_id
|
should_filter = status.in_reply_to_account_id != list.account_id
|
||||||
should_filter &&= !list.show_followed?
|
should_filter &&= !list.show_followed?
|
||||||
should_filter &&= !(list.show_list? && ListAccount.where(list_id: list.id, account_id: status.in_reply_to_account_id).exists?)
|
should_filter &&= !(list.show_list? && ListAccount.exists?(list_id: list.id, account_id: status.in_reply_to_account_id))
|
||||||
|
|
||||||
return !!should_filter
|
return !!should_filter
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,17 +5,46 @@ class PermalinkRedirector
|
||||||
|
|
||||||
def initialize(path)
|
def initialize(path)
|
||||||
@path = path
|
@path = path
|
||||||
|
@object = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def object
|
||||||
|
@object ||= begin
|
||||||
|
if at_username_status_request? || statuses_status_request?
|
||||||
|
status = Status.find_by(id: second_segment)
|
||||||
|
status if status&.distributable? && !status&.local?
|
||||||
|
elsif at_username_request?
|
||||||
|
username, domain = first_segment.delete_prefix('@').split('@')
|
||||||
|
domain = nil if TagManager.instance.local_domain?(domain)
|
||||||
|
account = Account.find_remote(username, domain)
|
||||||
|
account unless account&.local?
|
||||||
|
elsif accounts_request? && record_integer_id_request?
|
||||||
|
account = Account.find_by(id: second_segment)
|
||||||
|
account unless account&.local?
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def redirect_path
|
def redirect_path
|
||||||
if at_username_status_request? || statuses_status_request?
|
return ActivityPub::TagManager.instance.url_for(object) if object.present?
|
||||||
find_status_url_by_id(second_segment)
|
|
||||||
elsif at_username_request?
|
@path.delete_prefix('/deck') if @path.start_with?('/deck')
|
||||||
find_account_url_by_name(first_segment)
|
end
|
||||||
elsif accounts_request? && record_integer_id_request?
|
|
||||||
find_account_url_by_id(second_segment)
|
def redirect_uri
|
||||||
elsif @path.start_with?('/deck')
|
return ActivityPub::TagManager.instance.uri_for(object) if object.present?
|
||||||
@path.delete_prefix('/deck')
|
|
||||||
|
@path.delete_prefix('/deck') if @path.start_with?('/deck')
|
||||||
|
end
|
||||||
|
|
||||||
|
def redirect_confirmation_path
|
||||||
|
case object.class.name
|
||||||
|
when 'Account'
|
||||||
|
redirect_account_path(object.id)
|
||||||
|
when 'Status'
|
||||||
|
redirect_status_path(object.id)
|
||||||
|
else
|
||||||
|
@path.delete_prefix('/deck') if @path.start_with?('/deck')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -56,22 +85,4 @@ class PermalinkRedirector
|
||||||
def path_segments
|
def path_segments
|
||||||
@path_segments ||= @path.delete_prefix('/deck').delete_prefix('/').split('/')
|
@path_segments ||= @path.delete_prefix('/deck').delete_prefix('/').split('/')
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_status_url_by_id(id)
|
|
||||||
status = Status.find_by(id: id)
|
|
||||||
ActivityPub::TagManager.instance.url_for(status) if status&.distributable? && !status.account.local?
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_account_url_by_id(id)
|
|
||||||
account = Account.find_by(id: id)
|
|
||||||
ActivityPub::TagManager.instance.url_for(account) if account.present? && !account.local?
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_account_url_by_name(name)
|
|
||||||
username, domain = name.gsub(/\A@/, '').split('@')
|
|
||||||
domain = nil if TagManager.instance.local_domain?(domain)
|
|
||||||
account = Account.find_remote(username, domain)
|
|
||||||
|
|
||||||
ActivityPub::TagManager.instance.url_for(account) if account.present? && !account.local?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,7 @@ class SuspiciousSignInDetector
|
||||||
end
|
end
|
||||||
|
|
||||||
def previously_seen_ip?(request)
|
def previously_seen_ip?(request)
|
||||||
@user.ips.where('ip <<= ?', masked_ip(request)).exists?
|
@user.ips.exists?(['ip <<= ?', masked_ip(request)])
|
||||||
end
|
end
|
||||||
|
|
||||||
def freshly_signed_up?
|
def freshly_signed_up?
|
||||||
|
|
|
@ -27,11 +27,17 @@ class Vacuum::MediaAttachmentsVacuum
|
||||||
end
|
end
|
||||||
|
|
||||||
def media_attachments_past_retention_period
|
def media_attachments_past_retention_period
|
||||||
MediaAttachment.remote.cached.where(MediaAttachment.arel_table[:created_at].lt(@retention_period.ago)).where(MediaAttachment.arel_table[:updated_at].lt(@retention_period.ago))
|
MediaAttachment
|
||||||
|
.remote
|
||||||
|
.cached
|
||||||
|
.created_before(@retention_period.ago)
|
||||||
|
.updated_before(@retention_period.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
def orphaned_media_attachments
|
def orphaned_media_attachments
|
||||||
MediaAttachment.unattached.where(MediaAttachment.arel_table[:created_at].lt(TTL.ago))
|
MediaAttachment
|
||||||
|
.unattached
|
||||||
|
.created_before(TTL.ago)
|
||||||
end
|
end
|
||||||
|
|
||||||
def retention_period?
|
def retention_period?
|
||||||
|
|
|
@ -12,9 +12,11 @@
|
||||||
class AccountSummary < ApplicationRecord
|
class AccountSummary < ApplicationRecord
|
||||||
self.primary_key = :account_id
|
self.primary_key = :account_id
|
||||||
|
|
||||||
|
has_many :follow_recommendation_suppressions, primary_key: :account_id, foreign_key: :account_id, inverse_of: false
|
||||||
|
|
||||||
scope :safe, -> { where(sensitive: false) }
|
scope :safe, -> { where(sensitive: false) }
|
||||||
scope :localized, ->(locale) { where(language: locale) }
|
scope :localized, ->(locale) { where(language: locale) }
|
||||||
scope :filtered, -> { joins(arel_table.join(FollowRecommendationSuppression.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:account_id].eq(FollowRecommendationSuppression.arel_table[:account_id])).join_sources).where(FollowRecommendationSuppression.arel_table[:id].eq(nil)) }
|
scope :filtered, -> { where.missing(:follow_recommendation_suppressions) }
|
||||||
|
|
||||||
def self.refresh
|
def self.refresh
|
||||||
Scenic.database.refresh_materialized_view(table_name, concurrently: false, cascade: false)
|
Scenic.database.refresh_materialized_view(table_name, concurrently: false, cascade: false)
|
||||||
|
|
37
app/models/generated_annual_report.rb
Normal file
37
app/models/generated_annual_report.rb
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: generated_annual_reports
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# account_id :bigint(8) not null
|
||||||
|
# year :integer not null
|
||||||
|
# data :jsonb not null
|
||||||
|
# schema_version :integer not null
|
||||||
|
# viewed_at :datetime
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
|
||||||
|
class GeneratedAnnualReport < ApplicationRecord
|
||||||
|
belongs_to :account
|
||||||
|
|
||||||
|
scope :pending, -> { where(viewed_at: nil) }
|
||||||
|
|
||||||
|
def viewed?
|
||||||
|
viewed_at.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def view!
|
||||||
|
update!(viewed_at: Time.now.utc)
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_ids
|
||||||
|
data['most_reblogged_accounts'].pluck('account_id') + data['commonly_interacted_with_accounts'].pluck('account_id')
|
||||||
|
end
|
||||||
|
|
||||||
|
def status_ids
|
||||||
|
data['top_statuses'].values
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,14 +13,14 @@ class Instance < ApplicationRecord
|
||||||
|
|
||||||
attr_accessor :failure_days
|
attr_accessor :failure_days
|
||||||
|
|
||||||
has_many :accounts, foreign_key: :domain, primary_key: :domain, inverse_of: false
|
|
||||||
|
|
||||||
with_options foreign_key: :domain, primary_key: :domain, inverse_of: false do
|
with_options foreign_key: :domain, primary_key: :domain, inverse_of: false do
|
||||||
belongs_to :domain_block
|
belongs_to :domain_block
|
||||||
belongs_to :domain_allow
|
belongs_to :domain_allow
|
||||||
belongs_to :unavailable_domain # skipcq: RB-RL1031
|
belongs_to :unavailable_domain
|
||||||
belongs_to :instance_info
|
belongs_to :instance_info
|
||||||
belongs_to :friend_domain
|
belongs_to :friend_domain
|
||||||
|
|
||||||
|
has_many :accounts, dependent: nil
|
||||||
end
|
end
|
||||||
|
|
||||||
scope :searchable, -> { where.not(domain: DomainBlock.select(:domain)) }
|
scope :searchable, -> { where.not(domain: DomainBlock.select(:domain)) }
|
||||||
|
|
|
@ -209,12 +209,14 @@ class MediaAttachment < ApplicationRecord
|
||||||
validates :file, presence: true, if: :local?
|
validates :file, presence: true, if: :local?
|
||||||
validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? }
|
validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? }
|
||||||
|
|
||||||
scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) }
|
scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) }
|
||||||
scope :cached, -> { remote.where.not(file_file_name: nil) }
|
scope :cached, -> { remote.where.not(file_file_name: nil) }
|
||||||
scope :local, -> { where(remote_url: '') }
|
scope :created_before, ->(value) { where(arel_table[:created_at].lt(value)) }
|
||||||
scope :ordered, -> { order(id: :asc) }
|
scope :local, -> { where(remote_url: '') }
|
||||||
scope :remote, -> { where.not(remote_url: '') }
|
scope :ordered, -> { order(id: :asc) }
|
||||||
|
scope :remote, -> { where.not(remote_url: '') }
|
||||||
scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) }
|
scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) }
|
||||||
|
scope :updated_before, ->(value) { where(arel_table[:updated_at].lt(value)) }
|
||||||
|
|
||||||
attr_accessor :skip_download
|
attr_accessor :skip_download
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ class Poll < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def voted?(account)
|
def voted?(account)
|
||||||
account.id == account_id || votes.where(account: account).exists?
|
account.id == account_id || votes.exists?(account: account)
|
||||||
end
|
end
|
||||||
|
|
||||||
def own_votes(account)
|
def own_votes(account)
|
||||||
|
|
|
@ -41,7 +41,7 @@ class SessionActivation < ApplicationRecord
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def active?(id)
|
def active?(id)
|
||||||
id && where(session_id: id).exists?
|
id && exists?(session_id: id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def activate(**options)
|
def activate(**options)
|
||||||
|
|
|
@ -331,11 +331,11 @@ class Status < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def reported?
|
def reported?
|
||||||
@reported ||= Report.where(target_account: account).unresolved.where('? = ANY(status_ids)', id).exists?
|
@reported ||= Report.where(target_account: account).unresolved.exists?(['? = ANY(status_ids)', id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def dtl?
|
def dtl?
|
||||||
(%w(public public_unlisted login).include?(visibility) || (unlisted_visibility? && public_searchability?)) && tags.where(name: dtl_tag_name).exists?
|
(%w(public public_unlisted login).include?(visibility) || (unlisted_visibility? && public_searchability?)) && tags.exists?(name: dtl_tag_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def emojis
|
def emojis
|
||||||
|
|
23
app/presenters/annual_reports_presenter.rb
Normal file
23
app/presenters/annual_reports_presenter.rb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AnnualReportsPresenter
|
||||||
|
alias read_attribute_for_serialization send
|
||||||
|
|
||||||
|
attr_reader :annual_reports
|
||||||
|
|
||||||
|
def initialize(annual_reports)
|
||||||
|
@annual_reports = annual_reports
|
||||||
|
end
|
||||||
|
|
||||||
|
def accounts
|
||||||
|
@accounts ||= Account.where(id: @annual_reports.flat_map(&:account_ids)).includes(:account_stat, :moved_to_account, user: :role)
|
||||||
|
end
|
||||||
|
|
||||||
|
def statuses
|
||||||
|
@statuses ||= Status.where(id: @annual_reports.flat_map(&:status_ids)).with_includes
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.model_name
|
||||||
|
@model_name ||= ActiveModel::Name.new(self)
|
||||||
|
end
|
||||||
|
end
|
5
app/serializers/rest/annual_report_serializer.rb
Normal file
5
app/serializers/rest/annual_report_serializer.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class REST::AnnualReportSerializer < ActiveModel::Serializer
|
||||||
|
attributes :year, :data, :schema_version
|
||||||
|
end
|
7
app/serializers/rest/annual_reports_serializer.rb
Normal file
7
app/serializers/rest/annual_reports_serializer.rb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class REST::AnnualReportsSerializer < ActiveModel::Serializer
|
||||||
|
has_many :annual_reports, serializer: REST::AnnualReportSerializer
|
||||||
|
has_many :accounts, serializer: REST::AccountSerializer
|
||||||
|
has_many :statuses, serializer: REST::StatusSerializer
|
||||||
|
end
|
8
app/views/redirects/show.html.haml
Normal file
8
app/views/redirects/show.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
.redirect
|
||||||
|
.redirect__logo
|
||||||
|
= link_to render_logo, root_path
|
||||||
|
|
||||||
|
.redirect__message
|
||||||
|
%h1= t('redirects.title', instance: site_hostname)
|
||||||
|
%p= t('redirects.prompt')
|
||||||
|
%p= link_to @redirect_path, @redirect_path, rel: 'noreferrer noopener'
|
11
app/workers/generate_annual_report_worker.rb
Normal file
11
app/workers/generate_annual_report_worker.rb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class GenerateAnnualReportWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
def perform(account_id, year)
|
||||||
|
AnnualReport.new(Account.find(account_id), year).generate
|
||||||
|
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordNotUnique
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
|
@ -24,6 +24,8 @@ class Scheduler::IndexingScheduler
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
def indexes
|
def indexes
|
||||||
[AccountsIndex, TagsIndex, PublicStatusesIndex, StatusesIndex]
|
[AccountsIndex, TagsIndex, PublicStatusesIndex, StatusesIndex]
|
||||||
end
|
end
|
||||||
|
|
|
@ -1934,6 +1934,7 @@ ar:
|
||||||
go_to_sso_account_settings: انتقل إلى إعدادات حساب مزود الهوية الخاص بك
|
go_to_sso_account_settings: انتقل إلى إعدادات حساب مزود الهوية الخاص بك
|
||||||
invalid_otp_token: رمز المصادقة بخطوتين غير صالح
|
invalid_otp_token: رمز المصادقة بخطوتين غير صالح
|
||||||
otp_lost_help_html: إن فقدتَهُما ، يمكنك الاتصال بـ %{email}
|
otp_lost_help_html: إن فقدتَهُما ، يمكنك الاتصال بـ %{email}
|
||||||
|
rate_limited: عدد محاولات التحقق كثير جدًا، يرجى المحاولة مرة أخرى لاحقًا.
|
||||||
seamless_external_login: لقد قمت بتسجيل الدخول عبر خدمة خارجية، إنّ إعدادات الكلمة السرية و البريد الإلكتروني غير متوفرة.
|
seamless_external_login: لقد قمت بتسجيل الدخول عبر خدمة خارجية، إنّ إعدادات الكلمة السرية و البريد الإلكتروني غير متوفرة.
|
||||||
signed_in_as: 'تم تسجيل دخولك بصفة:'
|
signed_in_as: 'تم تسجيل دخولك بصفة:'
|
||||||
verification:
|
verification:
|
||||||
|
|
|
@ -1793,6 +1793,7 @@ bg:
|
||||||
failed_2fa:
|
failed_2fa:
|
||||||
details: 'Ето подробности на опита за влизане:'
|
details: 'Ето подробности на опита за влизане:'
|
||||||
explanation: Някой се опита да влезе в акаунта ви, но предостави невалиден втори фактор за удостоверяване.
|
explanation: Някой се опита да влезе в акаунта ви, но предостави невалиден втори фактор за удостоверяване.
|
||||||
|
further_actions_html: Ако не бяхте вие, то препоръчваме да направите %{action} незабавно, тъй като може да се злепостави.
|
||||||
subject: Неуспешен втори фактор за удостоверяване
|
subject: Неуспешен втори фактор за удостоверяване
|
||||||
title: Провал на втория фактор за удостоверяване
|
title: Провал на втория фактор за удостоверяване
|
||||||
suspicious_sign_in:
|
suspicious_sign_in:
|
||||||
|
|
|
@ -1790,6 +1790,12 @@ da:
|
||||||
extra: Sikkerhedskopien kan nu downloades!
|
extra: Sikkerhedskopien kan nu downloades!
|
||||||
subject: Dit arkiv er klar til download
|
subject: Dit arkiv er klar til download
|
||||||
title: Arkiv download
|
title: Arkiv download
|
||||||
|
failed_2fa:
|
||||||
|
details: 'Her er detaljerne om login-forsøget:'
|
||||||
|
explanation: Nogen har forsøgt at logge ind på kontoen, men har angivet en ugyldig anden godkendelsesfaktor.
|
||||||
|
further_actions_html: Var dette ikke dig, anbefales det straks at %{action}, da den kan være kompromitteret.
|
||||||
|
subject: Anden faktor godkendelsesfejl
|
||||||
|
title: Fejlede på anden faktor godkendelse
|
||||||
suspicious_sign_in:
|
suspicious_sign_in:
|
||||||
change_password: ændrer din adgangskode
|
change_password: ændrer din adgangskode
|
||||||
details: 'Her er nogle detaljer om login-forsøget:'
|
details: 'Her er nogle detaljer om login-forsøget:'
|
||||||
|
|
|
@ -47,14 +47,19 @@ ru:
|
||||||
subject: 'Mastodon: Инструкция по сбросу пароля'
|
subject: 'Mastodon: Инструкция по сбросу пароля'
|
||||||
title: Сброс пароля
|
title: Сброс пароля
|
||||||
two_factor_disabled:
|
two_factor_disabled:
|
||||||
|
explanation: Вход в систему теперь возможен только с использованием адреса электронной почты и пароля.
|
||||||
subject: 'Mastodon: Двухфакторная авторизация отключена'
|
subject: 'Mastodon: Двухфакторная авторизация отключена'
|
||||||
|
subtitle: Двухфакторная аутентификация для вашей учетной записи была отключена.
|
||||||
title: 2ФА отключена
|
title: 2ФА отключена
|
||||||
two_factor_enabled:
|
two_factor_enabled:
|
||||||
|
explanation: Для входа в систему потребуется токен, сгенерированный сопряженным приложением TOTP.
|
||||||
subject: 'Mastodon: Настроена двухфакторная авторизация'
|
subject: 'Mastodon: Настроена двухфакторная авторизация'
|
||||||
|
subtitle: Для вашей учетной записи была включена двухфакторная аутентификация.
|
||||||
title: 2ФА включена
|
title: 2ФА включена
|
||||||
two_factor_recovery_codes_changed:
|
two_factor_recovery_codes_changed:
|
||||||
explanation: Предыдущие резервные коды были аннулированы и созданы новые.
|
explanation: Предыдущие резервные коды были аннулированы и созданы новые.
|
||||||
subject: 'Mastodon: Резервные коды двуфакторной авторизации обновлены'
|
subject: 'Mastodon: Резервные коды двуфакторной авторизации обновлены'
|
||||||
|
subtitle: Предыдущие коды восстановления были аннулированы и сгенерированы новые.
|
||||||
title: Коды восстановления 2FA изменены
|
title: Коды восстановления 2FA изменены
|
||||||
unlock_instructions:
|
unlock_instructions:
|
||||||
subject: 'Mastodon: Инструкция по разблокировке'
|
subject: 'Mastodon: Инструкция по разблокировке'
|
||||||
|
@ -68,9 +73,13 @@ ru:
|
||||||
subject: 'Мастодон: Ключ Безопасности удален'
|
subject: 'Мастодон: Ключ Безопасности удален'
|
||||||
title: Один из ваших защитных ключей был удален
|
title: Один из ваших защитных ключей был удален
|
||||||
webauthn_disabled:
|
webauthn_disabled:
|
||||||
|
explanation: Аутентификация с помощью ключей безопасности была отключена для вашей учетной записи.
|
||||||
|
extra: Теперь вход в систему возможен только с использованием токена, сгенерированного сопряженным приложением TOTP.
|
||||||
subject: 'Мастодон: Аутентификация с ключами безопасности отключена'
|
subject: 'Мастодон: Аутентификация с ключами безопасности отключена'
|
||||||
title: Ключи безопасности отключены
|
title: Ключи безопасности отключены
|
||||||
webauthn_enabled:
|
webauthn_enabled:
|
||||||
|
explanation: Для вашей учетной записи включена аутентификация по ключу безопасности.
|
||||||
|
extra: Теперь ваш ключ безопасности можно использовать для входа в систему.
|
||||||
subject: 'Мастодон: Включена аутентификация по ключу безопасности'
|
subject: 'Мастодон: Включена аутентификация по ключу безопасности'
|
||||||
title: Ключи безопасности включены
|
title: Ключи безопасности включены
|
||||||
omniauth_callbacks:
|
omniauth_callbacks:
|
||||||
|
|
|
@ -47,14 +47,19 @@ sq:
|
||||||
subject: 'Mastodon: Udhëzime ricaktimi fjalëkalimi'
|
subject: 'Mastodon: Udhëzime ricaktimi fjalëkalimi'
|
||||||
title: Ricaktim fjalëkalimi
|
title: Ricaktim fjalëkalimi
|
||||||
two_factor_disabled:
|
two_factor_disabled:
|
||||||
|
explanation: Hyrja tanimë është e mundshme duke përdorur vetëm adresë email dhe fjalëkalim.
|
||||||
subject: 'Mastodon: U çaktivizua mirëfilltësimi dyfaktorësh'
|
subject: 'Mastodon: U çaktivizua mirëfilltësimi dyfaktorësh'
|
||||||
|
subtitle: Mirëfilltësimi dyfaktorësh për llogarinë tuaj është çaktivizuar.
|
||||||
title: 2FA u çaktivizua
|
title: 2FA u çaktivizua
|
||||||
two_factor_enabled:
|
two_factor_enabled:
|
||||||
|
explanation: Për të kryer hyrjen do të kërkohet doemos një token i prodhuar nga aplikacioni TOTP i çiftuar.
|
||||||
subject: 'Mastodon: U aktivizua mirëfilltësimi dyfaktorësh'
|
subject: 'Mastodon: U aktivizua mirëfilltësimi dyfaktorësh'
|
||||||
|
subtitle: Për llogarinë tuaj është aktivizuar mirëfilltësmi dyfaktorësh.
|
||||||
title: 2FA u aktivizua
|
title: 2FA u aktivizua
|
||||||
two_factor_recovery_codes_changed:
|
two_factor_recovery_codes_changed:
|
||||||
explanation: Kodet e dikurshëm të rikthimit janë bërë të pavlefshëm dhe janë prodhuar të rinj.
|
explanation: Kodet e dikurshëm të rikthimit janë bërë të pavlefshëm dhe janë prodhuar të rinj.
|
||||||
subject: 'Mastodon: U riprodhuan kode rikthimi dyfaktorësh'
|
subject: 'Mastodon: U riprodhuan kode rikthimi dyfaktorësh'
|
||||||
|
subtitle: Kodet e dikurshëm të rikthimit janë bërë të pavlefshëm dhe janë prodhuar të rinj.
|
||||||
title: Kodet e rikthimit 2FA u ndryshuan
|
title: Kodet e rikthimit 2FA u ndryshuan
|
||||||
unlock_instructions:
|
unlock_instructions:
|
||||||
subject: 'Mastodon: Udhëzime shkyçjeje'
|
subject: 'Mastodon: Udhëzime shkyçjeje'
|
||||||
|
@ -68,9 +73,13 @@ sq:
|
||||||
subject: 'Mastodon: Fshirje kyçi sigurie'
|
subject: 'Mastodon: Fshirje kyçi sigurie'
|
||||||
title: Një nga kyçet tuaj të sigurisë është fshirë
|
title: Një nga kyçet tuaj të sigurisë është fshirë
|
||||||
webauthn_disabled:
|
webauthn_disabled:
|
||||||
|
explanation: Mirëfilltësimi me kyçe sigurie është çaktivizuar për llogarinë tuaj.
|
||||||
|
extra: Hyrjet tani janë të mundshme vetëm duke përdorur token-in e prodhuar nga aplikacioni TOTP i çiftuar.
|
||||||
subject: 'Mastodon: U çaktivizua mirëfilltësimi me kyçe sigurie'
|
subject: 'Mastodon: U çaktivizua mirëfilltësimi me kyçe sigurie'
|
||||||
title: U çaktivizuan kyçe sigurie
|
title: U çaktivizuan kyçe sigurie
|
||||||
webauthn_enabled:
|
webauthn_enabled:
|
||||||
|
explanation: Mirëfilltësimi me kyçe sigurie është aktivizuar për këtë llogari.
|
||||||
|
extra: Kyçi juaj i sigurisë tanimë mund të përdoret për hyrje.
|
||||||
subject: 'Mastodon: U aktivizua mirëfilltësim me kyçe sigurie'
|
subject: 'Mastodon: U aktivizua mirëfilltësim me kyçe sigurie'
|
||||||
title: U aktivizuan kyçe sigurie
|
title: U aktivizuan kyçe sigurie
|
||||||
omniauth_callbacks:
|
omniauth_callbacks:
|
||||||
|
|
|
@ -1761,6 +1761,9 @@ en:
|
||||||
duplication: Cannot react same things
|
duplication: Cannot react same things
|
||||||
limit_reached: Limit of different reactions reached
|
limit_reached: Limit of different reactions reached
|
||||||
unrecognized_emoji: is not a recognized emoji
|
unrecognized_emoji: is not a recognized emoji
|
||||||
|
redirects:
|
||||||
|
prompt: If you trust this link, click it to continue.
|
||||||
|
title: You are leaving %{instance}.
|
||||||
relationships:
|
relationships:
|
||||||
activity: Account activity
|
activity: Account activity
|
||||||
confirm_follow_selected_followers: Are you sure you want to follow selected followers?
|
confirm_follow_selected_followers: Are you sure you want to follow selected followers?
|
||||||
|
|
|
@ -1792,6 +1792,10 @@ es-MX:
|
||||||
title: Descargar archivo
|
title: Descargar archivo
|
||||||
failed_2fa:
|
failed_2fa:
|
||||||
details: 'Estos son los detalles del intento de inicio de sesión:'
|
details: 'Estos son los detalles del intento de inicio de sesión:'
|
||||||
|
explanation: Alguien ha intentado iniciar sesión en tu cuenta pero proporcionó un segundo factor de autenticación inválido.
|
||||||
|
further_actions_html: Si no fuiste tú, se recomienda %{action} inmediatamente ya que puede estar comprometido.
|
||||||
|
subject: Fallo de autenticación de segundo factor
|
||||||
|
title: Falló la autenticación de segundo factor
|
||||||
suspicious_sign_in:
|
suspicious_sign_in:
|
||||||
change_password: cambies tu contraseña
|
change_password: cambies tu contraseña
|
||||||
details: 'Aquí están los detalles del inicio de sesión:'
|
details: 'Aquí están los detalles del inicio de sesión:'
|
||||||
|
|
|
@ -1792,6 +1792,10 @@ es:
|
||||||
title: Descargar archivo
|
title: Descargar archivo
|
||||||
failed_2fa:
|
failed_2fa:
|
||||||
details: 'Estos son los detalles del intento de inicio de sesión:'
|
details: 'Estos son los detalles del intento de inicio de sesión:'
|
||||||
|
explanation: Alguien ha intentado iniciar sesión en tu cuenta pero proporcionó un segundo factor de autenticación inválido.
|
||||||
|
further_actions_html: Si no fuiste tú, se recomienda %{action} inmediatamente ya que puede estar comprometida.
|
||||||
|
subject: Fallo de autenticación del segundo factor
|
||||||
|
title: Fallo en la autenticación del segundo factor
|
||||||
suspicious_sign_in:
|
suspicious_sign_in:
|
||||||
change_password: cambies tu contraseña
|
change_password: cambies tu contraseña
|
||||||
details: 'Aquí están los detalles del inicio de sesión:'
|
details: 'Aquí están los detalles del inicio de sesión:'
|
||||||
|
|
|
@ -1790,6 +1790,12 @@ fy:
|
||||||
extra: It stiet no klear om download te wurden!
|
extra: It stiet no klear om download te wurden!
|
||||||
subject: Jo argyf stiet klear om download te wurden
|
subject: Jo argyf stiet klear om download te wurden
|
||||||
title: Argyf ophelje
|
title: Argyf ophelje
|
||||||
|
failed_2fa:
|
||||||
|
details: 'Hjir binne de details fan de oanmeldbesykjen:'
|
||||||
|
explanation: Ien hat probearre om oan te melden op jo account, mar hat in ûnjildige twaddeferifikaasjefaktor opjûn.
|
||||||
|
further_actions_html: As jo dit net wiene, rekommandearje wy jo oan daliks %{action}, omdat it kompromitearre wêze kin.
|
||||||
|
subject: Twaddefaktorautentikaasjeflater
|
||||||
|
title: Twastapsferifikaasje mislearre
|
||||||
suspicious_sign_in:
|
suspicious_sign_in:
|
||||||
change_password: wizigje jo wachtwurd
|
change_password: wizigje jo wachtwurd
|
||||||
details: 'Hjir binne de details fan oanmeldbesykjen:'
|
details: 'Hjir binne de details fan oanmeldbesykjen:'
|
||||||
|
|
|
@ -1790,6 +1790,12 @@ gl:
|
||||||
extra: Está preparada para descargala!
|
extra: Está preparada para descargala!
|
||||||
subject: O teu ficheiro xa está preparado para descargar
|
subject: O teu ficheiro xa está preparado para descargar
|
||||||
title: Leve o ficheiro
|
title: Leve o ficheiro
|
||||||
|
failed_2fa:
|
||||||
|
details: 'Detalles do intento de acceso:'
|
||||||
|
explanation: Alguén intentou acceder á túa conta mais fíxoo cun segundo factor de autenticación non válido.
|
||||||
|
further_actions_html: Se non foches ti, recomendámosche %{action} inmediatamente xa que a conta podería estar en risco.
|
||||||
|
subject: Fallo co segundo factor de autenticación
|
||||||
|
title: Fallou o segundo factor de autenticación
|
||||||
suspicious_sign_in:
|
suspicious_sign_in:
|
||||||
change_password: cambia o teu contrasinal
|
change_password: cambia o teu contrasinal
|
||||||
details: 'Estos son os detalles do acceso:'
|
details: 'Estos son os detalles do acceso:'
|
||||||
|
|
|
@ -439,6 +439,7 @@ ru:
|
||||||
view: Посмотреть доменные блокировки
|
view: Посмотреть доменные блокировки
|
||||||
email_domain_blocks:
|
email_domain_blocks:
|
||||||
add_new: Добавить новую
|
add_new: Добавить новую
|
||||||
|
allow_registrations_with_approval: Разрешить регистрацию с одобрением
|
||||||
attempts_over_week:
|
attempts_over_week:
|
||||||
few: "%{count} попытки за последнюю неделю"
|
few: "%{count} попытки за последнюю неделю"
|
||||||
many: "%{count} попыток за последнюю неделю"
|
many: "%{count} попыток за последнюю неделю"
|
||||||
|
@ -1659,6 +1660,7 @@ ru:
|
||||||
unknown_browser: Неизвестный браузер
|
unknown_browser: Неизвестный браузер
|
||||||
weibo: Weibo
|
weibo: Weibo
|
||||||
current_session: Текущая сессия
|
current_session: Текущая сессия
|
||||||
|
date: Дата
|
||||||
description: "%{browser} на %{platform}"
|
description: "%{browser} на %{platform}"
|
||||||
explanation: Здесь отображаются все браузеры, с которых выполнен вход в вашу учётную запись. Авторизованные приложения находятся в секции «Приложения».
|
explanation: Здесь отображаются все браузеры, с которых выполнен вход в вашу учётную запись. Авторизованные приложения находятся в секции «Приложения».
|
||||||
ip: IP
|
ip: IP
|
||||||
|
@ -1837,16 +1839,27 @@ ru:
|
||||||
webauthn: Ключи безопасности
|
webauthn: Ключи безопасности
|
||||||
user_mailer:
|
user_mailer:
|
||||||
appeal_approved:
|
appeal_approved:
|
||||||
|
action: Настройки аккаунта
|
||||||
explanation: Апелляция на разблокировку против вашей учетной записи %{strike_date}, которую вы подали на %{appeal_date}, была одобрена. Ваша учетная запись снова на хорошем счету.
|
explanation: Апелляция на разблокировку против вашей учетной записи %{strike_date}, которую вы подали на %{appeal_date}, была одобрена. Ваша учетная запись снова на хорошем счету.
|
||||||
subject: Ваше обжалование от %{date} была одобрено
|
subject: Ваше обжалование от %{date} была одобрено
|
||||||
|
subtitle: Ваш аккаунт снова с хорошей репутацией.
|
||||||
title: Обжалование одобрено
|
title: Обжалование одобрено
|
||||||
appeal_rejected:
|
appeal_rejected:
|
||||||
explanation: Апелляция на разблокировку против вашей учетной записи %{strike_date}, которую вы подали на %{appeal_date}, была одобрена. Ваша учетная запись восстановлена.
|
explanation: Апелляция на разблокировку против вашей учетной записи %{strike_date}, которую вы подали на %{appeal_date}, была одобрена. Ваша учетная запись восстановлена.
|
||||||
subject: Ваше обжалование от %{date} отклонено
|
subject: Ваше обжалование от %{date} отклонено
|
||||||
|
subtitle: Ваша апелляция отклонена.
|
||||||
title: Обжалование отклонено
|
title: Обжалование отклонено
|
||||||
backup_ready:
|
backup_ready:
|
||||||
|
explanation: Вы запросили полное резервное копирование вашей учетной записи Mastodon.
|
||||||
|
extra: Теперь он готов к загрузке!
|
||||||
subject: Ваш архив готов к загрузке
|
subject: Ваш архив готов к загрузке
|
||||||
title: Архив ваших данных готов
|
title: Архив ваших данных готов
|
||||||
|
failed_2fa:
|
||||||
|
details: 'Вот подробности попытки регистрации:'
|
||||||
|
explanation: Кто-то пытался войти в вашу учетную запись, но указал неверный второй фактор аутентификации.
|
||||||
|
further_actions_html: Если это не вы, мы рекомендуем %{action} немедленно принять меры, так как он может быть скомпрометирован.
|
||||||
|
subject: Сбой двухфакторной аутентификации
|
||||||
|
title: Сбой двухфакторной аутентификации
|
||||||
suspicious_sign_in:
|
suspicious_sign_in:
|
||||||
change_password: сменить пароль
|
change_password: сменить пароль
|
||||||
details: 'Подробности о новом входе:'
|
details: 'Подробности о новом входе:'
|
||||||
|
@ -1900,6 +1913,7 @@ ru:
|
||||||
go_to_sso_account_settings: Перейти к настройкам сторонних аккаунтов учетной записи
|
go_to_sso_account_settings: Перейти к настройкам сторонних аккаунтов учетной записи
|
||||||
invalid_otp_token: Введен неверный код двухфакторной аутентификации
|
invalid_otp_token: Введен неверный код двухфакторной аутентификации
|
||||||
otp_lost_help_html: Если Вы потеряли доступ к обоим, свяжитесь с %{email}
|
otp_lost_help_html: Если Вы потеряли доступ к обоим, свяжитесь с %{email}
|
||||||
|
rate_limited: Слишком много попыток аутентификации, повторите попытку позже.
|
||||||
seamless_external_login: Вы залогинены через сторонний сервис, поэтому настройки e-mail и пароля недоступны.
|
seamless_external_login: Вы залогинены через сторонний сервис, поэтому настройки e-mail и пароля недоступны.
|
||||||
signed_in_as: 'Выполнен вход под именем:'
|
signed_in_as: 'Выполнен вход под именем:'
|
||||||
verification:
|
verification:
|
||||||
|
|
|
@ -60,6 +60,7 @@ sk:
|
||||||
fields:
|
fields:
|
||||||
name: Označenie
|
name: Označenie
|
||||||
value: Obsah
|
value: Obsah
|
||||||
|
unlocked: Automaticky prijímaj nových nasledovateľov
|
||||||
account_alias:
|
account_alias:
|
||||||
acct: Adresa starého účtu
|
acct: Adresa starého účtu
|
||||||
account_migration:
|
account_migration:
|
||||||
|
|
|
@ -430,6 +430,7 @@ sk:
|
||||||
dashboard:
|
dashboard:
|
||||||
instance_accounts_dimension: Najsledovanejšie účty
|
instance_accounts_dimension: Najsledovanejšie účty
|
||||||
instance_accounts_measure: uložené účty
|
instance_accounts_measure: uložené účty
|
||||||
|
instance_followers_measure: naši nasledovatelia tam
|
||||||
instance_follows_measure: ich sledovatelia tu
|
instance_follows_measure: ich sledovatelia tu
|
||||||
instance_languages_dimension: Najpopulárnejšie jazyky
|
instance_languages_dimension: Najpopulárnejšie jazyky
|
||||||
instance_media_attachments_measure: uložené mediálne prílohy
|
instance_media_attachments_measure: uložené mediálne prílohy
|
||||||
|
@ -1257,6 +1258,8 @@ sk:
|
||||||
extra: Teraz je pripravená na stiahnutie!
|
extra: Teraz je pripravená na stiahnutie!
|
||||||
subject: Tvoj archív je pripravený na stiahnutie
|
subject: Tvoj archív je pripravený na stiahnutie
|
||||||
title: Odber archívu
|
title: Odber archívu
|
||||||
|
failed_2fa:
|
||||||
|
details: 'Tu sú podrobnosti o pokuse o prihlásenie:'
|
||||||
warning:
|
warning:
|
||||||
subject:
|
subject:
|
||||||
disable: Tvoj účet %{acct} bol zamrazený
|
disable: Tvoj účet %{acct} bol zamrazený
|
||||||
|
|
|
@ -1604,6 +1604,7 @@ sq:
|
||||||
unknown_browser: Shfletues i Panjohur
|
unknown_browser: Shfletues i Panjohur
|
||||||
weibo: Weibo
|
weibo: Weibo
|
||||||
current_session: Sesioni i tanishëm
|
current_session: Sesioni i tanishëm
|
||||||
|
date: Datë
|
||||||
description: "%{browser} në %{platform}"
|
description: "%{browser} në %{platform}"
|
||||||
explanation: Këta janë shfletuesit e përdorur tani për hyrje te llogaria juaj Mastodon.
|
explanation: Këta janë shfletuesit e përdorur tani për hyrje te llogaria juaj Mastodon.
|
||||||
ip: IP
|
ip: IP
|
||||||
|
@ -1770,16 +1771,27 @@ sq:
|
||||||
webauthn: Kyçe sigurie
|
webauthn: Kyçe sigurie
|
||||||
user_mailer:
|
user_mailer:
|
||||||
appeal_approved:
|
appeal_approved:
|
||||||
|
action: Rregullime Llogarie
|
||||||
explanation: Apelimi i paralajmërimit kundër llogarisë tuaj më %{strike_date}, të cilin e parashtruar më %{appeal_date} është miratuar. Llogaria juaj është sërish në pozita të mira.
|
explanation: Apelimi i paralajmërimit kundër llogarisë tuaj më %{strike_date}, të cilin e parashtruar më %{appeal_date} është miratuar. Llogaria juaj është sërish në pozita të mira.
|
||||||
subject: Apelimi juaj i datës %{date} u miratua
|
subject: Apelimi juaj i datës %{date} u miratua
|
||||||
|
subtitle: Llogaria juaj edhe një herë është e shëndetshme.
|
||||||
title: Apelimi u miratua
|
title: Apelimi u miratua
|
||||||
appeal_rejected:
|
appeal_rejected:
|
||||||
explanation: Apelimi i paralajmërimit kundër llogarisë tuaj më %{strike_date}, të cilin e parashtruar më %{appeal_date}, u hodh poshtë.
|
explanation: Apelimi i paralajmërimit kundër llogarisë tuaj më %{strike_date}, të cilin e parashtruar më %{appeal_date}, u hodh poshtë.
|
||||||
subject: Apelimi juaj prej %{date} është hedhur poshtë
|
subject: Apelimi juaj prej %{date} është hedhur poshtë
|
||||||
|
subtitle: Apelimi juaj është hedhur poshtë.
|
||||||
title: Apelimi u hodh poshtë
|
title: Apelimi u hodh poshtë
|
||||||
backup_ready:
|
backup_ready:
|
||||||
|
explanation: Kërkuat një kopjeruajtje të plotë të llogarisë tuaj Mastodon.
|
||||||
|
extra: Tani është gati për shkarkim!
|
||||||
subject: Arkivi juaj është gati për shkarkim
|
subject: Arkivi juaj është gati për shkarkim
|
||||||
title: Marrje arkivi me vete
|
title: Marrje arkivi me vete
|
||||||
|
failed_2fa:
|
||||||
|
details: 'Ja hollësitë e përpjekjes për hyrje:'
|
||||||
|
explanation: Dikush ka provuar të hyjë në llogarinë tuaj, por dha faktor të dytë mirëfilltësimi.
|
||||||
|
further_actions_html: Nëse s’qetë ju, rekomandojmë të %{action} menjëherë, ngaqë mund të jetë komprometua.
|
||||||
|
subject: Dështim faktori të dytë mirëfilltësimesh
|
||||||
|
title: Dështoi mirëfilltësimi me faktor të dytë
|
||||||
suspicious_sign_in:
|
suspicious_sign_in:
|
||||||
change_password: ndryshoni fjalëkalimin tuaj
|
change_password: ndryshoni fjalëkalimin tuaj
|
||||||
details: 'Ja hollësitë për hyrjen:'
|
details: 'Ja hollësitë për hyrjen:'
|
||||||
|
@ -1833,6 +1845,7 @@ sq:
|
||||||
go_to_sso_account_settings: Kaloni te rregullime llogarie te shërbimi juaj i identitetit
|
go_to_sso_account_settings: Kaloni te rregullime llogarie te shërbimi juaj i identitetit
|
||||||
invalid_otp_token: Kod dyfaktorësh i pavlefshëm
|
invalid_otp_token: Kod dyfaktorësh i pavlefshëm
|
||||||
otp_lost_help_html: Nëse humbët hyrjen te të dy, mund të lidheni me %{email}
|
otp_lost_help_html: Nëse humbët hyrjen te të dy, mund të lidheni me %{email}
|
||||||
|
rate_limited: Shumë përpjekje mirëfilltësimi, riprovoni më vonë.
|
||||||
seamless_external_login: Jeni futur përmes një shërbimi të jashtëm, ndaj s’ka rregullime fjalëkalimi dhe email.
|
seamless_external_login: Jeni futur përmes një shërbimi të jashtëm, ndaj s’ka rregullime fjalëkalimi dhe email.
|
||||||
signed_in_as: 'I futur si:'
|
signed_in_as: 'I futur si:'
|
||||||
verification:
|
verification:
|
||||||
|
|
|
@ -1791,7 +1791,7 @@ tr:
|
||||||
subject: Arşiviniz indirilmeye hazır
|
subject: Arşiviniz indirilmeye hazır
|
||||||
title: Arşiv paketlemesi
|
title: Arşiv paketlemesi
|
||||||
failed_2fa:
|
failed_2fa:
|
||||||
details: 'Oturum açma denemesinin ayrıntıları şöyledir:'
|
details: 'İşte oturum açma girişiminin ayrıntıları:'
|
||||||
explanation: Birisi hesabınızda oturum açmaya çalıştı ancak hatalı bir iki aşamalı doğrulama kodu kullandı.
|
explanation: Birisi hesabınızda oturum açmaya çalıştı ancak hatalı bir iki aşamalı doğrulama kodu kullandı.
|
||||||
further_actions_html: Eğer bu kişi siz değilseniz, hemen %{action} yapmanızı öneriyoruz çünkü hesabınız ifşa olmuş olabilir.
|
further_actions_html: Eğer bu kişi siz değilseniz, hemen %{action} yapmanızı öneriyoruz çünkü hesabınız ifşa olmuş olabilir.
|
||||||
subject: İki aşamalı doğrulama başarısızlığı
|
subject: İki aşamalı doğrulama başarısızlığı
|
||||||
|
|
|
@ -1758,6 +1758,12 @@ vi:
|
||||||
extra: Hiện nó đã sẵn sàng tải xuống!
|
extra: Hiện nó đã sẵn sàng tải xuống!
|
||||||
subject: Dữ liệu cá nhân của bạn đã sẵn sàng để tải về
|
subject: Dữ liệu cá nhân của bạn đã sẵn sàng để tải về
|
||||||
title: Nhận dữ liệu cá nhân
|
title: Nhận dữ liệu cá nhân
|
||||||
|
failed_2fa:
|
||||||
|
details: 'Chi tiết thông tin đăng nhập:'
|
||||||
|
explanation: Ai đó đã cố đăng nhập vào tài khoản của bạn nhưng cung cấp yếu tố xác thực thứ hai không hợp lệ.
|
||||||
|
further_actions_html: Nếu không phải bạn, hãy lập tức %{action} vì có thể có rủi ro.
|
||||||
|
subject: Xác minh hai bước thất bại
|
||||||
|
title: Xác minh hai bước thất bại
|
||||||
suspicious_sign_in:
|
suspicious_sign_in:
|
||||||
change_password: đổi mật khẩu của bạn
|
change_password: đổi mật khẩu của bạn
|
||||||
details: 'Chi tiết thông tin đăng nhập:'
|
details: 'Chi tiết thông tin đăng nhập:'
|
||||||
|
|
|
@ -171,6 +171,11 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
namespace :redirect do
|
||||||
|
resources :accounts, only: :show
|
||||||
|
resources :statuses, only: :show
|
||||||
|
end
|
||||||
|
|
||||||
resources :media, only: [:show] do
|
resources :media, only: [:show] do
|
||||||
get :player
|
get :player
|
||||||
end
|
end
|
||||||
|
|
|
@ -62,6 +62,12 @@ namespace :api, format: false do
|
||||||
resources :scheduled_statuses, only: [:index, :show, :update, :destroy]
|
resources :scheduled_statuses, only: [:index, :show, :update, :destroy]
|
||||||
resources :preferences, only: [:index]
|
resources :preferences, only: [:index]
|
||||||
|
|
||||||
|
resources :annual_reports, only: [:index] do
|
||||||
|
member do
|
||||||
|
post :read
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
resources :announcements, only: [:index] do
|
resources :announcements, only: [:index] do
|
||||||
scope module: :announcements do
|
scope module: :announcements do
|
||||||
resources :reactions, only: [:update, :destroy]
|
resources :reactions, only: [:update, :destroy]
|
||||||
|
|
17
db/migrate/20240111033014_create_generated_annual_reports.rb
Normal file
17
db/migrate/20240111033014_create_generated_annual_reports.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateGeneratedAnnualReports < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
create_table :generated_annual_reports do |t|
|
||||||
|
t.belongs_to :account, null: false, foreign_key: { on_cascade: :delete }, index: false
|
||||||
|
t.integer :year, null: false
|
||||||
|
t.jsonb :data, null: false
|
||||||
|
t.integer :schema_version, null: false
|
||||||
|
t.datetime :viewed_at
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :generated_annual_reports, [:account_id, :year], unique: true
|
||||||
|
end
|
||||||
|
end
|
12
db/schema.rb
12
db/schema.rb
|
@ -694,6 +694,17 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_21_231131) do
|
||||||
t.index ["inbox_url"], name: "index_friend_domains_on_inbox_url", unique: true
|
t.index ["inbox_url"], name: "index_friend_domains_on_inbox_url", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "generated_annual_reports", force: :cascade do |t|
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.integer "year", null: false
|
||||||
|
t.jsonb "data", null: false
|
||||||
|
t.integer "schema_version", null: false
|
||||||
|
t.datetime "viewed_at"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id", "year"], name: "index_generated_annual_reports_on_account_id_and_year", unique: true
|
||||||
|
end
|
||||||
|
|
||||||
create_table "identities", force: :cascade do |t|
|
create_table "identities", force: :cascade do |t|
|
||||||
t.string "provider", default: "", null: false
|
t.string "provider", default: "", null: false
|
||||||
t.string "uid", default: "", null: false
|
t.string "uid", default: "", null: false
|
||||||
|
@ -1487,6 +1498,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_21_231131) do
|
||||||
add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade
|
add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade
|
||||||
add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade
|
add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade
|
||||||
add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade
|
add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade
|
||||||
|
add_foreign_key "generated_annual_reports", "accounts"
|
||||||
add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade
|
add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade
|
||||||
add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade
|
add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade
|
||||||
add_foreign_key "invites", "users", on_delete: :cascade
|
add_foreign_key "invites", "users", on_delete: :cascade
|
||||||
|
|
|
@ -120,7 +120,7 @@ module Mastodon::CLI
|
||||||
|
|
||||||
say('Beginning removal of now-orphaned media attachments to free up disk space...')
|
say('Beginning removal of now-orphaned media attachments to free up disk space...')
|
||||||
|
|
||||||
scope = MediaAttachment.unattached.where('created_at < ?', options[:days].pred.days.ago)
|
scope = MediaAttachment.unattached.created_before(options[:days].pred.days.ago)
|
||||||
processed = 0
|
processed = 0
|
||||||
removed = 0
|
removed = 0
|
||||||
progress = create_progress_bar(scope.count)
|
progress = create_progress_bar(scope.count)
|
||||||
|
|
|
@ -12,7 +12,7 @@ describe Api::BaseController do
|
||||||
head 200
|
head 200
|
||||||
end
|
end
|
||||||
|
|
||||||
def error
|
def failure
|
||||||
FakeService.new
|
FakeService.new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -30,7 +30,7 @@ describe Api::BaseController do
|
||||||
|
|
||||||
it 'does not protect from forgery' do
|
it 'does not protect from forgery' do
|
||||||
ActionController::Base.allow_forgery_protection = true
|
ActionController::Base.allow_forgery_protection = true
|
||||||
post 'success'
|
post :success
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -50,47 +50,55 @@ describe Api::BaseController do
|
||||||
|
|
||||||
it 'returns http forbidden for unconfirmed accounts' do
|
it 'returns http forbidden for unconfirmed accounts' do
|
||||||
user.update(confirmed_at: nil)
|
user.update(confirmed_at: nil)
|
||||||
post 'success'
|
post :success
|
||||||
expect(response).to have_http_status(403)
|
expect(response).to have_http_status(403)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns http forbidden for pending accounts' do
|
it 'returns http forbidden for pending accounts' do
|
||||||
user.update(approved: false)
|
user.update(approved: false)
|
||||||
post 'success'
|
post :success
|
||||||
expect(response).to have_http_status(403)
|
expect(response).to have_http_status(403)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns http forbidden for disabled accounts' do
|
it 'returns http forbidden for disabled accounts' do
|
||||||
user.update(disabled: true)
|
user.update(disabled: true)
|
||||||
post 'success'
|
post :success
|
||||||
expect(response).to have_http_status(403)
|
expect(response).to have_http_status(403)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns http forbidden for suspended accounts' do
|
it 'returns http forbidden for suspended accounts' do
|
||||||
user.account.suspend!
|
user.account.suspend!
|
||||||
post 'success'
|
post :success
|
||||||
expect(response).to have_http_status(403)
|
expect(response).to have_http_status(403)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'error handling' do
|
describe 'error handling' do
|
||||||
before do
|
before do
|
||||||
routes.draw { get 'error' => 'api/base#error' }
|
routes.draw { get 'failure' => 'api/base#failure' }
|
||||||
end
|
end
|
||||||
|
|
||||||
{
|
{
|
||||||
ActiveRecord::RecordInvalid => 422,
|
ActiveRecord::RecordInvalid => 422,
|
||||||
Mastodon::ValidationError => 422,
|
|
||||||
ActiveRecord::RecordNotFound => 404,
|
ActiveRecord::RecordNotFound => 404,
|
||||||
Mastodon::UnexpectedResponseError => 503,
|
ActiveRecord::RecordNotUnique => 422,
|
||||||
|
Date::Error => 422,
|
||||||
HTTP::Error => 503,
|
HTTP::Error => 503,
|
||||||
OpenSSL::SSL::SSLError => 503,
|
Mastodon::InvalidParameterError => 400,
|
||||||
Mastodon::NotPermittedError => 403,
|
Mastodon::NotPermittedError => 403,
|
||||||
|
Mastodon::RaceConditionError => 503,
|
||||||
|
Mastodon::RateLimitExceededError => 429,
|
||||||
|
Mastodon::UnexpectedResponseError => 503,
|
||||||
|
Mastodon::ValidationError => 422,
|
||||||
|
OpenSSL::SSL::SSLError => 503,
|
||||||
|
Seahorse::Client::NetworkingError => 503,
|
||||||
|
Stoplight::Error::RedLight => 503,
|
||||||
}.each do |error, code|
|
}.each do |error, code|
|
||||||
it "Handles error class of #{error}" do
|
it "Handles error class of #{error}" do
|
||||||
allow(FakeService).to receive(:new).and_raise(error)
|
allow(FakeService).to receive(:new).and_raise(error)
|
||||||
|
|
||||||
get 'error'
|
get :failure
|
||||||
|
|
||||||
expect(response).to have_http_status(code)
|
expect(response).to have_http_status(code)
|
||||||
expect(FakeService).to have_received(:new)
|
expect(FakeService).to have_received(:new)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue