diff options
author | Ondřej Hruška <ondra@ondrovo.com> | 2017-07-17 20:03:57 +0200 |
---|---|---|
committer | Ondřej Hruška <ondra@ondrovo.com> | 2017-07-17 20:03:57 +0200 |
commit | c727eae4412ac9e4f1bafdc68fe89dcd46d602ca (patch) | |
tree | ee7ce8662f6a91c87cf46bf6de70dae66556c0d0 | |
parent | d0aad1ac854eaa53f9b7d38cc8dd90e289790629 (diff) | |
parent | 681c33d1f4c395742918eb66f2db979b0d628118 (diff) |
Updated from tootsuite
34 files changed, 221 insertions, 29 deletions
diff --git a/Dockerfile b/Dockerfile index 97a691393..8cd374481 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,7 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit && apk -U upgrade \ && apk add -t build-dependencies \ build-base \ + libidn-dev \ libxml2-dev \ libxslt-dev \ postgresql-dev \ @@ -27,6 +28,7 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit git \ icu-dev \ imagemagick@edge \ + libidn \ libpq \ libxml2 \ libxslt \ diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index 6a58ccf24..30b91f370 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -20,7 +20,6 @@ class ActivityPub::OutboxesController < Api::BaseController ActivityPub::CollectionPresenter.new( id: account_outbox_url(@account), type: :ordered, - current: account_outbox_url(@account), size: @account.statuses_count, items: @statuses ) diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb index e58c5ad46..5edb4d67c 100644 --- a/app/controllers/follower_accounts_controller.rb +++ b/app/controllers/follower_accounts_controller.rb @@ -21,7 +21,6 @@ class FollowerAccountsController < ApplicationController ActivityPub::CollectionPresenter.new( id: account_followers_url(@account), type: :ordered, - current: account_followers_url(@account), size: @account.followers_count, items: @follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) } ) diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb index 69f29cd70..7cafe5fda 100644 --- a/app/controllers/following_accounts_controller.rb +++ b/app/controllers/following_accounts_controller.rb @@ -21,7 +21,6 @@ class FollowingAccountsController < ApplicationController ActivityPub::CollectionPresenter.new( id: account_following_index_url(@account), type: :ordered, - current: account_following_index_url(@account), size: @account.following_count, items: @follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) } ) diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 8bcce9e13..2cd85e185 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -23,7 +23,6 @@ class TagsController < ApplicationController ActivityPub::CollectionPresenter.new( id: tag_url(@tag), type: :ordered, - current: tag_url(@tag), size: @tag.statuses.count, items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) } ) diff --git a/app/javascript/mastodon/components/extended_video_player.js b/app/javascript/mastodon/components/extended_video_player.js index b38a4b8ff..5ab5e9e58 100644 --- a/app/javascript/mastodon/components/extended_video_player.js +++ b/app/javascript/mastodon/components/extended_video_player.js @@ -32,7 +32,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent { render () { return ( - <div className='extended-video-player' style={{ width: this.props.width, height: this.props.height }}> + <div className='extended-video-player'> <video ref={this.setRef} src={this.props.src} diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 7273edf48..58064fac2 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -140,7 +140,7 @@ export default class ComposeForm extends ImmutablePureComponent { handleEmojiPick = (data) => { const position = this.autosuggestTextarea.textarea.selectionStart; - const emojiChar = String.fromCodePoint(parseInt(data.unicode, 16)); + const emojiChar = data.unicode.split('-').map(code => String.fromCodePoint(parseInt(code, 16))).join(''); this._restoreCaret = position + emojiChar.length + 1; this.props.onPickEmoji(position, data); } diff --git a/app/javascript/mastodon/main.js b/app/javascript/mastodon/main.js index b237e9aee..80a169f51 100644 --- a/app/javascript/mastodon/main.js +++ b/app/javascript/mastodon/main.js @@ -8,8 +8,6 @@ function main() { const React = require('react'); const ReactDOM = require('react-dom'); - require.context('../images/', true); - if (window.history && history.replaceState) { const { pathname, search, hash } = window.location; const path = pathname + search + hash; diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 0c5dbccab..4dce634a4 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -126,7 +126,7 @@ const insertSuggestion = (state, position, token, completion) => { }; const insertEmoji = (state, position, emojiData) => { - const emoji = String.fromCodePoint(parseInt(emojiData.unicode, 16)); + const emoji = emojiData.unicode.split('-').map(code => String.fromCodePoint(parseInt(code, 16))).join(''); return state.withMutations(map => { map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`); diff --git a/app/javascript/packs/common.js b/app/javascript/packs/common.js index a0cb91ae4..ba7053f1f 100644 --- a/app/javascript/packs/common.js +++ b/app/javascript/packs/common.js @@ -4,4 +4,6 @@ import { start } from 'rails-ujs'; require('font-awesome/css/font-awesome.css'); require('mastodon-application-style'); +require.context('../images/', true); + start(); diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 4865f3ec0..39daef761 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -7,8 +7,6 @@ import loadPolyfills from '../mastodon/load_polyfills'; import { processBio } from '../glitch/util/bio_metadata'; import ready from '../mastodon/ready'; -require.context('../images/', true); - const { localeData } = getLocale(); localeData.forEach(IntlRelativeFormat.__addLocaleData); diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss index f12c8fbd1..a09a33e00 100644 --- a/app/javascript/styles/components.scss +++ b/app/javascript/styles/components.scss @@ -1384,8 +1384,8 @@ top: 0; left: 0; right: 0; - width: 100%; - height: 100%; + max-width: 100%; + max-height: 100%; background-image: none; } @@ -3313,8 +3313,9 @@ button.icon-button.active i.fa-retweet { video { max-width: 80vw; max-height: 80vh; - width: 100%; + width: auto; height: auto; + margin: auto; } .extended-video-player, @@ -3330,6 +3331,10 @@ button.icon-button.active i.fa-retweet { background: url('../images/void.png') repeat; object-fit: contain; } + + .react-swipeable-view-container { + max-width: 80vw; + } } .media-modal__close { diff --git a/app/presenters/activitypub/collection_presenter.rb b/app/presenters/activitypub/collection_presenter.rb index 6bae2955e..631d87cd0 100644 --- a/app/presenters/activitypub/collection_presenter.rb +++ b/app/presenters/activitypub/collection_presenter.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ActivityPub::CollectionPresenter < ActiveModelSerializers::Model - attributes :id, :type, :current, :size, :items + attributes :id, :type, :size, :items end diff --git a/app/serializers/activitypub/accept_follow_serializer.rb b/app/serializers/activitypub/accept_follow_serializer.rb new file mode 100644 index 000000000..ce900bc78 --- /dev/null +++ b/app/serializers/activitypub/accept_follow_serializer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class ActivityPub::AcceptFollowSerializer < ActiveModel::Serializer + attributes :type, :actor + + has_one :object, serializer: ActivityPub::FollowSerializer + + def type + 'Accept' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.target_account) + end +end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index 56806152e..f5e626d73 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -7,6 +7,8 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer :inbox, :outbox, :preferred_username, :name, :summary, :icon, :image + has_one :public_key, serializer: ActivityPub::PublicKeySerializer + def id account_url(object) end @@ -50,4 +52,8 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer def image full_asset_url(object.header.url(:original)) end + + def public_key + object + end end diff --git a/app/serializers/activitypub/block_serializer.rb b/app/serializers/activitypub/block_serializer.rb new file mode 100644 index 000000000..a001b213b --- /dev/null +++ b/app/serializers/activitypub/block_serializer.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ActivityPub::BlockSerializer < ActiveModel::Serializer + attributes :type, :actor + attribute :virtual_object, key: :object + + def type + 'Block' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def virtual_object + ActivityPub::TagManager.instance.uri_for(object.target_account) + end +end diff --git a/app/serializers/activitypub/collection_serializer.rb b/app/serializers/activitypub/collection_serializer.rb index baaba7654..d01dead28 100644 --- a/app/serializers/activitypub/collection_serializer.rb +++ b/app/serializers/activitypub/collection_serializer.rb @@ -6,8 +6,7 @@ class ActivityPub::CollectionSerializer < ActiveModel::Serializer super end - attributes :id, :type, :total_items, - :current + attributes :id, :type, :total_items has_many :items, key: :ordered_items diff --git a/app/serializers/activitypub/delete_serializer.rb b/app/serializers/activitypub/delete_serializer.rb new file mode 100644 index 000000000..77098b1b0 --- /dev/null +++ b/app/serializers/activitypub/delete_serializer.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ActivityPub::DeleteSerializer < ActiveModel::Serializer + attributes :type, :actor + attribute :virtual_object, key: :object + + def type + 'Delete' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def virtual_object + ActivityPub::TagManager.instance.uri_for(object) + end +end diff --git a/app/serializers/activitypub/follow_serializer.rb b/app/serializers/activitypub/follow_serializer.rb new file mode 100644 index 000000000..1953a2d7b --- /dev/null +++ b/app/serializers/activitypub/follow_serializer.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ActivityPub::FollowSerializer < ActiveModel::Serializer + attributes :type, :actor + attribute :virtual_object, key: :object + + def type + 'Follow' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def virtual_object + ActivityPub::TagManager.instance.uri_for(object.target_account) + end +end diff --git a/app/serializers/activitypub/like_serializer.rb b/app/serializers/activitypub/like_serializer.rb new file mode 100644 index 000000000..4226913f5 --- /dev/null +++ b/app/serializers/activitypub/like_serializer.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ActivityPub::LikeSerializer < ActiveModel::Serializer + attributes :type, :actor + attribute :virtual_object, key: :object + + def type + 'Like' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def virtual_object + ActivityPub::TagManager.instance.uri_for(object.status) + end +end diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index ffdc6175d..4c13f8e59 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -3,7 +3,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer attributes :id, :type, :summary, :content, :in_reply_to, :published, :url, - :actor, :to, :cc, :sensitive + :attributed_to, :to, :cc, :sensitive has_many :media_attachments, key: :attachment has_many :virtual_tags, key: :tag @@ -36,7 +36,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer ActivityPub::TagManager.instance.url_for(object) end - def actor + def attributed_to ActivityPub::TagManager.instance.uri_for(object.account) end diff --git a/app/serializers/activitypub/public_key_serializer.rb b/app/serializers/activitypub/public_key_serializer.rb new file mode 100644 index 000000000..38e9e93ba --- /dev/null +++ b/app/serializers/activitypub/public_key_serializer.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class ActivityPub::PublicKeySerializer < ActiveModel::Serializer + attributes :id, :owner, :public_key_pem + + def id + [ActivityPub::TagManager.instance.uri_for(object), '#main-key'].join + end + + def owner + ActivityPub::TagManager.instance.uri_for(object) + end + + def public_key_pem + object.public_key + end +end diff --git a/app/serializers/activitypub/reject_follow_serializer.rb b/app/serializers/activitypub/reject_follow_serializer.rb new file mode 100644 index 000000000..28584d627 --- /dev/null +++ b/app/serializers/activitypub/reject_follow_serializer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class ActivityPub::RejectFollowSerializer < ActiveModel::Serializer + attributes :type, :actor + + has_one :object, serializer: ActivityPub::FollowSerializer + + def type + 'Reject' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.target_account) + end +end diff --git a/app/serializers/activitypub/undo_block_serializer.rb b/app/serializers/activitypub/undo_block_serializer.rb new file mode 100644 index 000000000..f71faa729 --- /dev/null +++ b/app/serializers/activitypub/undo_block_serializer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class ActivityPub::UndoBlockSerializer < ActiveModel::Serializer + attributes :type, :actor + + has_one :object, serializer: ActivityPub::BlockSerializer + + def type + 'Undo' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end +end diff --git a/app/serializers/activitypub/undo_follow_serializer.rb b/app/serializers/activitypub/undo_follow_serializer.rb new file mode 100644 index 000000000..fe91f5f1c --- /dev/null +++ b/app/serializers/activitypub/undo_follow_serializer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class ActivityPub::UndoFollowSerializer < ActiveModel::Serializer + attributes :type, :actor + + has_one :object, serializer: ActivityPub::FollowSerializer + + def type + 'Undo' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end +end diff --git a/app/serializers/activitypub/undo_like_serializer.rb b/app/serializers/activitypub/undo_like_serializer.rb new file mode 100644 index 000000000..db9cd1d0d --- /dev/null +++ b/app/serializers/activitypub/undo_like_serializer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class ActivityPub::UndoLikeSerializer < ActiveModel::Serializer + attributes :type, :actor + + has_one :object, serializer: ActivityPub::LikeSerializer + + def type + 'Undo' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end +end diff --git a/app/serializers/activitypub/update_serializer.rb b/app/serializers/activitypub/update_serializer.rb new file mode 100644 index 000000000..322305da8 --- /dev/null +++ b/app/serializers/activitypub/update_serializer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class ActivityPub::UpdateSerializer < ActiveModel::Serializer + attributes :type, :actor + + has_one :object, serializer: ActivityPub::ActorSerializer + + def type + 'Update' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object) + end +end diff --git a/app/workers/web_push_notification_worker.rb b/app/workers/web_push_notification_worker.rb index 0568a3e02..e8f1d72bd 100644 --- a/app/workers/web_push_notification_worker.rb +++ b/app/workers/web_push_notification_worker.rb @@ -9,7 +9,7 @@ class WebPushNotificationWorker recipient = Account.find(recipient_id) notification = Notification.find(notification_id) - sessions_with_subscriptions = recipient.user.session_activations.reject { |session| session.web_push_subscription.nil? } + sessions_with_subscriptions = recipient.user.session_activations.where.not(web_push_subscription: nil) sessions_with_subscriptions.each do |session| begin @@ -17,8 +17,7 @@ class WebPushNotificationWorker rescue Webpush::InvalidSubscription, Webpush::ExpiredSubscription # Subscription expiration is not currently implemented in any browser session.web_push_subscription.destroy! - session.web_push_subscription = nil - session.save! + session.update!(web_push_subscription: nil) rescue Webpush::PayloadTooLarge => e Rails.logger.error(e) end diff --git a/config/environments/production.rb b/config/environments/production.rb index d71e410bf..928fd13e5 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -85,6 +85,7 @@ Rails.application.configure do :ca_file => ENV['SMTP_CA_FILE'].presence, :openssl_verify_mode => ENV['SMTP_OPENSSL_VERIFY_MODE'], :enable_starttls_auto => ENV['SMTP_ENABLE_STARTTLS_AUTO'] || true, + :tls => ENV['SMTP_TLS'].presence, } config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 6f2831670..98cfe1b77 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -10,7 +10,7 @@ pl: domain_count_after: instancji domain_count_before: Serwer połączony z features: - humane_approach_body: Nauczeni na błędach innych sieci społecznościowych, Mastodon został zaprojektowany tak, aby uniknąć częstych nadużyć. + humane_approach_body: Nauczeni na błędach innych sieci społecznościowych, zaprojektowaliśmy Mastodona tak, aby uniknąć częstych nadużyć. humane_approach_title: Bardziej ludzkie podejście not_a_product_body: Mastodon nie jest komercyjną siecią. Nie doświadczysz tu reklam, zbierania danych, ani centralnego ośrodka, tak jak w przypadku wielu rozwiązań. not_a_product_title: Jesteś człowiekiem, nie produktem diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index fbaf0ff68..476ccc773 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -8,7 +8,7 @@ en: one: <span class="name-counter">1</span> character left other: <span class="name-counter">%{count}</span> characters left header: PNG, GIF or JPG. At most 2MB. Will be downscaled to 700x335px - locked: Requires you to manually approve followers and defaults post privacy to followers-only + locked: Requires you to manually approve followers note: one: <span class="note-counter">1</span> character left other: <span class="note-counter">%{count}</span> characters left diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 9342398a8..74cf91de4 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -6,7 +6,7 @@ ja: avatar: 2MBまでのPNGやGIF、JPGが利用可能です。120x120pxまで縮小されます。 display_name: あと<span class="name-counter">%{count}</span>文字入力できます。 header: 2MBまでのPNGやGIF、JPGが利用可能です。 700x335pxまで縮小されます。 - locked: フォロワーを手動で承認する必要があります。デフォルトではトゥートの公開範囲はフォロワーのみです。 + locked: フォロワーを手動で承認する必要があります。 note: あと<span class="note-counter">%{count}</span>文字入力できます。 imports: data: 他の Mastodon インスタンスからエクスポートしたCSVファイルを選択して下さい diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index e4c4d7c8c..5553c75e3 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -10,7 +10,7 @@ pl: one: Pozostał <span class="name-counter">1</span> znak. other: Pozostało <span class="name-counter">%{count}</span> znaków header: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 700x335px - locked: Musisz akceptować obserwacje; Twoje wpisy są domyślnie widoczne tylko dla Twoich obserwujących + locked: Musisz akceptować prośby o śledzenie note: few: Pozostały <span class="name-counter">%{count}</span> znaki. many: Pozostało <span class="name-counter">%{count}</span> znaków diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index 010139e91..bceeeaf01 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -232,9 +232,16 @@ namespace :mastodon do task prepare_for_foreign_keys: :environment do # All the deletes: ActiveRecord::Base.connection.execute('DELETE FROM statuses USING statuses s LEFT JOIN accounts a ON a.id = s.account_id WHERE statuses.id = s.id AND a.id IS NULL') - ActiveRecord::Base.connection.execute('DELETE FROM account_domain_blocks USING account_domain_blocks adb LEFT JOIN accounts a ON a.id = adb.account_id WHERE account_domain_blocks.id = adb.id AND a.id IS NULL') - ActiveRecord::Base.connection.execute('DELETE FROM conversation_mutes USING conversation_mutes cm LEFT JOIN accounts a ON a.id = cm.account_id WHERE conversation_mutes.id = cm.id AND a.id IS NULL') - ActiveRecord::Base.connection.execute('DELETE FROM conversation_mutes USING conversation_mutes cm LEFT JOIN conversations c ON c.id = cm.conversation_id WHERE conversation_mutes.id = cm.id AND c.id IS NULL') + + if ActiveRecord::Base.connection.table_exists? :account_domain_blocks + ActiveRecord::Base.connection.execute('DELETE FROM account_domain_blocks USING account_domain_blocks adb LEFT JOIN accounts a ON a.id = adb.account_id WHERE account_domain_blocks.id = adb.id AND a.id IS NULL') + end + + if ActiveRecord::Base.connection.table_exists? :conversation_mutes + ActiveRecord::Base.connection.execute('DELETE FROM conversation_mutes USING conversation_mutes cm LEFT JOIN accounts a ON a.id = cm.account_id WHERE conversation_mutes.id = cm.id AND a.id IS NULL') + ActiveRecord::Base.connection.execute('DELETE FROM conversation_mutes USING conversation_mutes cm LEFT JOIN conversations c ON c.id = cm.conversation_id WHERE conversation_mutes.id = cm.id AND c.id IS NULL') + end + ActiveRecord::Base.connection.execute('DELETE FROM favourites USING favourites f LEFT JOIN accounts a ON a.id = f.account_id WHERE favourites.id = f.id AND a.id IS NULL') ActiveRecord::Base.connection.execute('DELETE FROM favourites USING favourites f LEFT JOIN statuses s ON s.id = f.status_id WHERE favourites.id = f.id AND s.id IS NULL') ActiveRecord::Base.connection.execute('DELETE FROM blocks USING blocks b LEFT JOIN accounts a ON a.id = b.account_id WHERE blocks.id = b.id AND a.id IS NULL') |