From 5817230fcd305349b214c625dfc08a31c749112c Mon Sep 17 00:00:00 2001 From: noellabo Date: Sun, 18 Dec 2022 14:34:18 +0900 Subject: [PATCH] Add push subscription block feature --- .../push_subscription_blocks_controller.rb | 72 +++++++++++++++++++ app/javascript/styles/mastodon/tables.scss | 5 ++ app/models/push_subscription_block.rb | 44 ++++++++++++ app/models/web/push_subscription.rb | 2 +- .../push_subscription_block_policy.rb | 7 ++ .../_push_subscription_block.html.haml | 24 +++++++ .../push_subscription_blocks/edit.html.haml | 16 +++++ .../push_subscription_blocks/index.html.haml | 21 ++++++ .../push_subscription_blocks/new.html.haml | 16 +++++ config/locales/en.yml | 16 +++++ config/locales/ja.yml | 16 +++++ config/locales/simple_form.en.yml | 3 + config/locales/simple_form.ja.yml | 3 + config/navigation.rb | 1 + config/routes.rb | 7 ++ ...5211405_create_push_subscription_blocks.rb | 11 +++ db/schema.rb | 8 +++ .../push_subscription_block_fabricator.rb | 5 ++ spec/models/push_subscription_block_spec.rb | 5 ++ 19 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 app/controllers/admin/push_subscription_blocks_controller.rb create mode 100644 app/models/push_subscription_block.rb create mode 100644 app/policies/push_subscription_block_policy.rb create mode 100644 app/views/admin/push_subscription_blocks/_push_subscription_block.html.haml create mode 100644 app/views/admin/push_subscription_blocks/edit.html.haml create mode 100644 app/views/admin/push_subscription_blocks/index.html.haml create mode 100644 app/views/admin/push_subscription_blocks/new.html.haml create mode 100644 db/migrate/20221215211405_create_push_subscription_blocks.rb create mode 100644 spec/fabricators/push_subscription_block_fabricator.rb create mode 100644 spec/models/push_subscription_block_spec.rb diff --git a/app/controllers/admin/push_subscription_blocks_controller.rb b/app/controllers/admin/push_subscription_blocks_controller.rb new file mode 100644 index 000000000..ecb7599ae --- /dev/null +++ b/app/controllers/admin/push_subscription_blocks_controller.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module Admin + class PushSubscriptionBlocksController < BaseController + before_action :set_push_subscription_block, except: [:index, :new, :create] + + def index + authorize :push_subscription_block, :update? + @push_subscription_blocks = PushSubscriptionBlock.all + end + + def new + authorize :push_subscription_block, :update? + @push_subscription_block = PushSubscriptionBlock.new + end + + def create + authorize :push_subscription_block, :update? + + @push_subscription_block = PushSubscriptionBlock.new(resource_params) + + if @push_subscription_block.save + @push_subscription_block.enable! + redirect_to admin_push_subscription_blocks_path + else + render action: :new + end + end + + def edit + authorize :push_subscription_block, :update? + end + + def update + authorize :push_subscription_block, :update? + + if @push_subscription_block.update(resource_params) + redirect_to admin_push_subscription_blocks_path + else + render action: :edit + end + end + + def destroy + authorize :push_subscription_block, :update? + @push_subscription_block.destroy + redirect_to admin_push_subscription_blocks_path + end + + def enable + authorize :push_subscription_block, :update? + @push_subscription_block.enable! + redirect_to admin_push_subscription_blocks_path + end + + def disable + authorize :push_subscription_block, :update? + @push_subscription_block.disable! + redirect_to admin_push_subscription_blocks_path + end + + private + + def set_push_subscription_block + @push_subscription_block = PushSubscriptionBlock.find(params[:id]) + end + + def resource_params + params.require(:push_subscription_block).permit(:name, :endpoint) + end + end +end diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index c2472fee3..f09425a7e 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -54,6 +54,11 @@ white-space: nowrap; } + th.wrap, + td.wrap { + overflow-wrap: anywhere; + } + th.symbol, td.symbol { text-align: center; diff --git a/app/models/push_subscription_block.rb b/app/models/push_subscription_block.rb new file mode 100644 index 000000000..10eac04c0 --- /dev/null +++ b/app/models/push_subscription_block.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: push_subscription_blocks +# +# id :bigint(8) not null, primary key +# name :string default(""), not null +# endpoint :string not null +# enable :boolean default(TRUE), not null +# created_at :datetime not null +# updated_at :datetime not null +# +class PushSubscriptionBlock < ApplicationRecord + CACHE_KEY = 'push_subscription_blocks' + + validates :endpoint, presence: true, uniqueness: true, url: true, if: :will_save_change_to_endpoint? + + after_commit :reset_cache + + def enable! + update!(enable: true) + end + + def disable! + update!(enable: false) + end + + class << self + def allow?(url) + !deny?(url) + end + + def deny?(url) + blocks = Rails.cache.fetch(CACHE_KEY) { Regexp.union(PushSubscriptionBlock.where(enable: true).pluck(:endpoint).map { |pattern| Regexp.new("^#{Regexp.escape(pattern)}", Regexp::IGNORECASE) }) } + blocks.match?(url) + end + end + + private + + def reset_cache + Rails.cache.delete(CACHE_KEY) + end +end diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb index 6e46573ae..0fc611093 100644 --- a/app/models/web/push_subscription.rb +++ b/app/models/web/push_subscription.rb @@ -47,7 +47,7 @@ class Web::PushSubscription < ApplicationRecord end def pushable?(notification) - policy_allows_notification?(notification) && alert_enabled_for_notification_type?(notification) + policy_allows_notification?(notification) && alert_enabled_for_notification_type?(notification) && PushSubscriptionBlock.allow?(endpoint) end def associated_user diff --git a/app/policies/push_subscription_block_policy.rb b/app/policies/push_subscription_block_policy.rb new file mode 100644 index 000000000..5565f5da8 --- /dev/null +++ b/app/policies/push_subscription_block_policy.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class PushSubscriptionBlockPolicy < ApplicationPolicy + def update? + admin? + end +end diff --git a/app/views/admin/push_subscription_blocks/_push_subscription_block.html.haml b/app/views/admin/push_subscription_blocks/_push_subscription_block.html.haml new file mode 100644 index 000000000..fb338dce9 --- /dev/null +++ b/app/views/admin/push_subscription_blocks/_push_subscription_block.html.haml @@ -0,0 +1,24 @@ +%tr + %td.nowrap + %samp= push_subscription_block.name + %td.wrap + %samp= push_subscription_block.endpoint + %td.nowrap + - if push_subscription_block.enable? + %span.positive-hint + = fa_icon('check') + = ' ' + = t 'admin.push_subscription_blocks.enabled' + - else + %span.negative-hint + = fa_icon('times') + = ' ' + = t 'admin.push_subscription_blocks.disabled' + %td.nowrap + - if push_subscription_block.enable? + = table_link_to 'power-off', t('admin.push_subscription_blocks.disable'), disable_admin_push_subscription_block_path(push_subscription_block), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } + - else + = table_link_to 'power-off', t('admin.push_subscription_blocks.enable'), enable_admin_push_subscription_block_path(push_subscription_block), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } + + = table_link_to 'pencil', t('admin.push_subscription_blocks.edit.title'), edit_admin_push_subscription_block_path(push_subscription_block) + = table_link_to 'times', t('admin.push_subscription_blocks.delete'), admin_push_subscription_block_path(push_subscription_block), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } diff --git a/app/views/admin/push_subscription_blocks/edit.html.haml b/app/views/admin/push_subscription_blocks/edit.html.haml new file mode 100644 index 000000000..718a9cdf1 --- /dev/null +++ b/app/views/admin/push_subscription_blocks/edit.html.haml @@ -0,0 +1,16 @@ +- content_for :page_title do + = t('admin.push_subscription_blocks.edit.title') + += simple_form_for @push_subscription_block, url: admin_push_subscription_block_path(@push_subscription_block), method: :put do |f| + = render 'shared/error_messages', object: @push_subscription_block + + %p.hint= t('admin.push_subscription_blocks.enable_hint') + + .field-group + = f.input :name, as: :string, wrapper: :with_block_label + + .field-group + = f.input :endpoint, as: :string, wrapper: :with_block_label + + .actions + = f.button :button, t('admin.push_subscription_blocks.save'), type: :submit diff --git a/app/views/admin/push_subscription_blocks/index.html.haml b/app/views/admin/push_subscription_blocks/index.html.haml new file mode 100644 index 000000000..641606b78 --- /dev/null +++ b/app/views/admin/push_subscription_blocks/index.html.haml @@ -0,0 +1,21 @@ +- content_for :page_title do + = t('admin.push_subscription_blocks.title') + +.simple_form + %p.hint= t('admin.push_subscription_blocks.description_html') + = link_to @push_subscription_blocks.empty? ? t('admin.push_subscription_blocks.setup') : t('admin.push_subscription_blocks.add_new'), new_admin_push_subscription_block_path, class: 'block-button' + +- unless @push_subscription_blocks.empty? + %hr.spacer + + .table-wrapper + %table.table + %thead + %tr + %th= t('admin.push_subscription_blocks.name') + %th= t('admin.push_subscription_blocks.endpoint') + %th= t('admin.push_subscription_blocks.enable') + %th + %tbody + = render @push_subscription_blocks + diff --git a/app/views/admin/push_subscription_blocks/new.html.haml b/app/views/admin/push_subscription_blocks/new.html.haml new file mode 100644 index 000000000..f09fe963a --- /dev/null +++ b/app/views/admin/push_subscription_blocks/new.html.haml @@ -0,0 +1,16 @@ +- content_for :page_title do + = t('admin.push_subscription_blocks.add_new') + += simple_form_for @push_subscription_block, url: admin_push_subscription_blocks_path do |f| + = render 'shared/error_messages', object: @push_subscription_block + + %p.hint= t('admin.push_subscription_blocks.enable_hint') + + .field-group + = f.input :name, as: :string, wrapper: :with_block_label + + .field-group + = f.input :endpoint, as: :string, wrapper: :with_block_label + + .actions + = f.button :button, t('admin.push_subscription_blocks.save'), type: :submit diff --git a/config/locales/en.yml b/config/locales/en.yml index 716252d58..e718c8c2c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -540,6 +540,22 @@ en: title: IP rules pending_accounts: title: Pending accounts (%{count}) + push_subscription_blocks: + add_new: Add new block + delete: Delete + disable: Disable + disabled: Disabled + description_html: Block dead/delayed push notification servers. + edit: + title: Edit + enable: Enable + enabled: Enabled + enable_hint: Refer to Sidekiq's dead job and specify the non-functioning endpoint. Since it matches with a prefix match, specify it without user-specific parts such as ID. A meaningful name is recommended, but not required. + endpoint: End point + name: Name + save: Save + setup: Setup a push subscription block + title: Push subscription block relationships: title: "%{acct}'s relationships" relays: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 2100a056b..b35b6ca85 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -522,6 +522,22 @@ ja: title: IPルール pending_accounts: title: 承認待ちアカウント (%{count}) + push_subscription_blocks: + add_new: プッシュ通知ブロックを追加 + delete: 削除 + disable: 無効化 + disabled: 無効 + description_html: 停止・遅延しているプッシュ通知サーバーの利用をブロックします。 + edit: + title: 編集 + enable: 有効化 + enabled: 有効 + enable_hint: Sidekiqのデッドジョブを参考に、機能していないエンドポイントを指定します。前方一致でマッチするので、IDなどのユーザー固有部分を除いて指定してください。わかりやすい名前をつけておくことをおすすめしますが、必須ではありません。 + endpoint: エンドポイント + name: 名前 + save: 保存 + setup: プッシュ通知ブロックを設定する + title: プッシュ通知ブロック relationships: title: "%{acct} さんのフォロー・フォロワー" relays: diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index e5959e7d1..fd4b0d268 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -390,6 +390,9 @@ en: report: New report is submitted status_reference: Someone referenced your post trending_tag: An unreviewed hashtag is trending + push_subscription_blocks: + endpoint: End point + name: Name rule: text: Rule tag: diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 00be1c648..04991f93f 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -390,6 +390,9 @@ ja: report: 通報を受けた時 status_reference: 投稿が参照された時 trending_tag: 未審査のハッシュタグが人気の時 + push_subscription_block: + endpoint: エンドポイント + name: 名前 rule: text: ルール tag: diff --git a/config/navigation.rb b/config/navigation.rb index 76175a24e..60c063489 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -63,6 +63,7 @@ SimpleNavigation::Configuration.run do |navigation| s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements} s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis} s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? && !whitelist_mode? }, highlights_on: %r{/admin/relays} + s.item :push_subscription_blocks, safe_join([fa_icon('ban fw'), t('admin.push_subscription_blocks.title')]), admin_push_subscription_blocks_url, if: -> { current_user.admin? && !whitelist_mode? }, highlights_on: %r{/admin/push_subscription_blocks} s.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? } s.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? } end diff --git a/config/routes.rb b/config/routes.rb index d18097ed4..1b01952b7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -322,6 +322,13 @@ Rails.application.routes.draw do post :batch end end + + resources :push_subscription_blocks, except: [:show] do + member do + post :enable + post :disable + end + end end get '/admin', to: redirect('/admin/dashboard', status: 302) diff --git a/db/migrate/20221215211405_create_push_subscription_blocks.rb b/db/migrate/20221215211405_create_push_subscription_blocks.rb new file mode 100644 index 000000000..1d3a41731 --- /dev/null +++ b/db/migrate/20221215211405_create_push_subscription_blocks.rb @@ -0,0 +1,11 @@ +class CreatePushSubscriptionBlocks < ActiveRecord::Migration[6.1] + def change + create_table :push_subscription_blocks do |t| + t.string :name, null: false, default: '' + t.string :endpoint, null: false + t.boolean :enable, null: false, default: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index a09ffcef9..fc7919403 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -838,6 +838,14 @@ ActiveRecord::Schema.define(version: 2023_01_29_193248) do t.index ["status_id", "preview_card_id"], name: "index_preview_cards_statuses_on_status_id_and_preview_card_id" end + create_table "push_subscription_blocks", force: :cascade do |t| + t.string "name", default: "", null: false + t.string "endpoint", null: false + t.boolean "enable", default: true, null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + create_table "relays", force: :cascade do |t| t.string "inbox_url", default: "", null: false t.string "follow_activity_id" diff --git a/spec/fabricators/push_subscription_block_fabricator.rb b/spec/fabricators/push_subscription_block_fabricator.rb new file mode 100644 index 000000000..76e0d6d8b --- /dev/null +++ b/spec/fabricators/push_subscription_block_fabricator.rb @@ -0,0 +1,5 @@ +Fabricator(:push_subscription_block) do + name 'tootle' + endpoint 'https://tootleformastodon.appspot.com/api/v1/notifications/callback/' + enable true +end diff --git a/spec/models/push_subscription_block_spec.rb b/spec/models/push_subscription_block_spec.rb new file mode 100644 index 000000000..0f874ffaa --- /dev/null +++ b/spec/models/push_subscription_block_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe PushSubscriptionBlock, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end