From 8331fdf7e0ea85ecc6d7dbff00b784bb6aa1f7d4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 21 Feb 2021 19:50:12 +0100 Subject: [PATCH] Add server rules (#15769) --- app/controllers/about_controller.rb | 1 + app/controllers/admin/rules_controller.rb | 59 +++++++++++++++++++ .../api/v1/instances/rules_controller.rb | 17 ++++++ app/javascript/styles/mastodon/about.scss | 21 +++++++ app/models/rule.rb | 22 +++++++ app/policies/rule_policy.rb | 19 ++++++ app/presenters/instance_presenter.rb | 4 ++ app/serializers/rest/instance_serializer.rb | 4 +- app/serializers/rest/rule_serializer.rb | 9 +++ app/views/about/more.html.haml | 13 ++++ app/views/admin/rules/_rule.html.haml | 11 ++++ app/views/admin/rules/edit.html.haml | 11 ++++ app/views/admin/rules/index.html.haml | 24 ++++++++ config/locales/en.yml | 7 +++ config/locales/simple_form.en.yml | 4 ++ config/navigation.rb | 1 + config/routes.rb | 2 + db/migrate/20210221045109_create_rules.rb | 11 ++++ db/schema.rb | 10 +++- spec/fabricators/rule_fabricator.rb | 5 ++ spec/models/rule_spec.rb | 5 ++ 21 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 app/controllers/admin/rules_controller.rb create mode 100644 app/controllers/api/v1/instances/rules_controller.rb create mode 100644 app/models/rule.rb create mode 100644 app/policies/rule_policy.rb create mode 100644 app/serializers/rest/rule_serializer.rb create mode 100644 app/views/admin/rules/_rule.html.haml create mode 100644 app/views/admin/rules/edit.html.haml create mode 100644 app/views/admin/rules/index.html.haml create mode 100644 db/migrate/20210221045109_create_rules.rb create mode 100644 spec/fabricators/rule_fabricator.rb create mode 100644 spec/models/rule_spec.rb diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index dcad5d3b44..d7e78d6b91 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -20,6 +20,7 @@ class AboutController < ApplicationController toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description) + @rules = Rule.ordered @contents = toc_generator.html @table_of_contents = toc_generator.toc @blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks? diff --git a/app/controllers/admin/rules_controller.rb b/app/controllers/admin/rules_controller.rb new file mode 100644 index 0000000000..f3bed3ad8e --- /dev/null +++ b/app/controllers/admin/rules_controller.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Admin + class RulesController < BaseController + before_action :set_rule, except: [:index, :create] + + def index + authorize :rule, :index? + + @rules = Rule.ordered + @rule = Rule.new + end + + def create + authorize :rule, :create? + + @rule = Rule.new(resource_params) + + if @rule.save + redirect_to admin_rules_path + else + @rules = Rule.ordered + render :index + end + end + + def edit + authorize @rule, :update? + end + + def update + authorize @rule, :update? + + if @rule.update(resource_params) + redirect_to admin_rules_path + else + render :edit + end + end + + def destroy + authorize @rule, :destroy? + + @rule.discard + + redirect_to admin_rules_path + end + + private + + def set_rule + @rule = Rule.find(params[:id]) + end + + def resource_params + params.require(:rule).permit(:text, :priority) + end + end +end diff --git a/app/controllers/api/v1/instances/rules_controller.rb b/app/controllers/api/v1/instances/rules_controller.rb new file mode 100644 index 0000000000..93cf3c7594 --- /dev/null +++ b/app/controllers/api/v1/instances/rules_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Api::V1::Instances::RulesController < Api::BaseController + skip_before_action :require_authenticated_user!, unless: :whitelist_mode? + + before_action :set_rules + + def index + render json: @rules, each_serializer: REST::RuleSerializer + end + + private + + def set_rules + @rules = Rule.ordered + end +end diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index d6bd9e3c60..281f5b2bfe 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -884,3 +884,24 @@ $small-breakpoint: 960px; } } +.rules-list { + background: darken($ui-base-color, 2%); + border: 1px solid darken($ui-base-color, 8%); + border-radius: 4px; + padding: 0.5em 2.5em !important; + margin-top: 1.85em !important; + + li { + border-bottom: 1px solid lighten($ui-base-color, 4%); + color: $dark-text-color; + padding: 1em; + + &:last-child { + border-bottom: 0; + } + } + + &__text { + color: $primary-text-color; + } +} diff --git a/app/models/rule.rb b/app/models/rule.rb new file mode 100644 index 0000000000..7b62f2b354 --- /dev/null +++ b/app/models/rule.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: rules +# +# id :bigint(8) not null, primary key +# priority :integer default(0), not null +# deleted_at :datetime +# text :text default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# +class Rule < ApplicationRecord + include Discard::Model + + self.discard_column = :deleted_at + + validates :text, presence: true, length: { maximum: 300 } + + scope :ordered, -> { kept.order(priority: :asc) } +end diff --git a/app/policies/rule_policy.rb b/app/policies/rule_policy.rb new file mode 100644 index 0000000000..6a4def0090 --- /dev/null +++ b/app/policies/rule_policy.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class RulePolicy < ApplicationPolicy + def index? + staff? + end + + def create? + admin? + end + + def update? + admin? + end + + def destroy? + admin? + end +end diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb index 1bfdd40ac9..8cd774abe8 100644 --- a/app/presenters/instance_presenter.rb +++ b/app/presenters/instance_presenter.rb @@ -16,6 +16,10 @@ class InstancePresenter Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')) end + def rules + Rule.ordered + end + def user_count Rails.cache.fetch('user_count') { User.confirmed.joins(:account).merge(Account.without_suspended).count } end diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index b388e448e3..d39092b56d 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -9,7 +9,9 @@ class REST::InstanceSerializer < ActiveModel::Serializer has_one :contact_account, serializer: REST::AccountSerializer - delegate :contact_account, to: :instance_presenter + has_many :rules, serializer: REST::RuleSerializer + + delegate :contact_account, :rules, to: :instance_presenter def uri Rails.configuration.x.local_domain diff --git a/app/serializers/rest/rule_serializer.rb b/app/serializers/rest/rule_serializer.rb new file mode 100644 index 0000000000..fc925925a9 --- /dev/null +++ b/app/serializers/rest/rule_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class REST::RuleSerializer < ActiveModel::Serializer + attributes :id, :text + + def id + object.id.to_s + end +end diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml index 109b5fa863..3c5f4f6f1c 100644 --- a/app/views/about/more.html.haml +++ b/app/views/about/more.html.haml @@ -48,6 +48,16 @@ - else .box-widget .rich-formatting + - unless @rules.empty? + %h2#rules= t('about.rules') + + %p= t('about.rules_html') + + %ol.rules-list + - @rules.each do |rule| + %li + .rules-list__text= rule.text + = @contents.html_safe - if display_blocks? && !@blocks.empty? @@ -70,6 +80,9 @@ .column-4 %ul.table-of-contents + - unless @rules.empty? + %li= link_to t('about.rules'), '#rules' + - @table_of_contents.each do |item| %li = link_to item.title, "##{item.anchor}" diff --git a/app/views/admin/rules/_rule.html.haml b/app/views/admin/rules/_rule.html.haml new file mode 100644 index 0000000000..f8a9ac7868 --- /dev/null +++ b/app/views/admin/rules/_rule.html.haml @@ -0,0 +1,11 @@ +.announcements-list__item + = link_to edit_admin_rule_path(rule), class: 'announcements-list__item__title' do + = "#{rule_counter + 1}." + = truncate(rule.text) + + .announcements-list__item__action-bar + .announcements-list__item__meta + = rule.text + + %div + = table_link_to 'trash', t('admin.rules.delete'), admin_rule_path(rule), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, rule) diff --git a/app/views/admin/rules/edit.html.haml b/app/views/admin/rules/edit.html.haml new file mode 100644 index 0000000000..ba7e6451a1 --- /dev/null +++ b/app/views/admin/rules/edit.html.haml @@ -0,0 +1,11 @@ +- content_for :page_title do + = t('admin.rules.edit') + += simple_form_for @rule, url: admin_rule_path(@rule) do |f| + = render 'shared/error_messages', object: @rule + + .fields-group + = f.input :text, wrapper: :with_block_label + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/rules/index.html.haml b/app/views/admin/rules/index.html.haml new file mode 100644 index 0000000000..3b069d083f --- /dev/null +++ b/app/views/admin/rules/index.html.haml @@ -0,0 +1,24 @@ +- content_for :page_title do + = t('admin.rules.title') + +.simple_form + %p.hint= t('admin.rules.description') + +- if can? :create, :rule + = simple_form_for @rule, url: admin_rules_path do |f| + = render 'shared/error_messages', object: @rule + + .fields-group + = f.input :text, wrapper: :with_block_label + + .actions + = f.button :button, t('admin.rules.add_new'), type: :submit + + %hr.spacer/ + +- if @rules.empty? + %div.muted-hint.center-text + = t 'admin.rules.empty' +- else + .announcements-list + = render partial: 'rule', collection: @rules diff --git a/config/locales/en.yml b/config/locales/en.yml index 8245397d7c..0c38c5ae11 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -26,6 +26,8 @@ en: It is used for federation purposes and should not be blocked unless you want to block the whole instance, in which case you should use a domain block. learn_more: Learn more privacy_policy: Privacy policy + rules: Server rules + rules_html: 'Below is a summary of rules you need to follow if you want to have an account on this server of Mastodon:' see_whats_happening: See what's happening server_stats: 'Server stats:' source_code: Source code @@ -542,6 +544,11 @@ en: unassign: Unassign unresolved: Unresolved updated_at: Updated + rules: + add_new: Add rule + description: While most claim to have read and agree to the terms of service, usually people do not read through until after a problem arises. Make it easier to see your server's rules at a glance by providing them in a flat bullet point list. Try to keep individual rules short and simple, but try not to split them up into many separate items either. + edit: Edit rule + title: Server rules settings: activity_api_enabled: desc_html: Counts of locally posted statuses, active users, and new registrations in weekly buckets diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 20c9165602..4b5ff7ae8e 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -73,6 +73,8 @@ en: no_access: Block access to all resources sign_up_requires_approval: New sign-ups will require your approval severity: Choose what will happen with requests from this IP + rule: + text: Describe a rule or requirement for users on this server. Try to keep it short and simple sessions: otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:' webauthn: If it's an USB key be sure to insert it and, if necessary, tap it. @@ -197,6 +199,8 @@ en: reblog: Someone boosted your status report: New report is submitted trending_tag: An unreviewed hashtag is trending + rule: + text: Rule tag: listable: Allow this hashtag to appear in searches and on the profile directory name: Hashtag diff --git a/config/navigation.rb b/config/navigation.rb index 4a56abe18c..3a82c7971b 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -47,6 +47,7 @@ SimpleNavigation::Configuration.run do |navigation| n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |s| s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/settings} + s.item :rules, safe_join([fa_icon('gavel fw'), t('admin.rules.title')]), admin_rules_path, highlights_on: %r{/admin/rules} 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} diff --git a/config/routes.rb b/config/routes.rb index fd118f1d61..0ff48cf482 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -220,6 +220,7 @@ Rails.application.routes.draw do end resources :instances, only: [:index, :show], constraints: { id: /[^\/]+/ } + resources :rules resources :reports, only: [:index, :show] do member do @@ -405,6 +406,7 @@ Rails.application.routes.draw do resource :instance, only: [:show] do resources :peers, only: [:index], controller: 'instances/peers' resource :activity, only: [:show], controller: 'instances/activity' + resources :rules, only: [:index], controller: 'instances/rules' end resource :domain_blocks, only: [:show, :create, :destroy] diff --git a/db/migrate/20210221045109_create_rules.rb b/db/migrate/20210221045109_create_rules.rb new file mode 100644 index 0000000000..abe2fd42a0 --- /dev/null +++ b/db/migrate/20210221045109_create_rules.rb @@ -0,0 +1,11 @@ +class CreateRules < ActiveRecord::Migration[5.2] + def change + create_table :rules do |t| + t.integer :priority, null: false, default: 0 + t.datetime :deleted_at + t.text :text, null: false, default: '' + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 18bf1d4ca8..4b85fce8dc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_12_18_054746) do +ActiveRecord::Schema.define(version: 2021_02_21_045109) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -723,6 +723,14 @@ ActiveRecord::Schema.define(version: 2020_12_18_054746) do t.index ["target_account_id"], name: "index_reports_on_target_account_id" end + create_table "rules", force: :cascade do |t| + t.integer "priority", default: 0, null: false + t.datetime "deleted_at" + t.text "text", default: "", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "scheduled_statuses", force: :cascade do |t| t.bigint "account_id" t.datetime "scheduled_at" diff --git a/spec/fabricators/rule_fabricator.rb b/spec/fabricators/rule_fabricator.rb new file mode 100644 index 0000000000..4bdfd05e0e --- /dev/null +++ b/spec/fabricators/rule_fabricator.rb @@ -0,0 +1,5 @@ +Fabricator(:rule) do + priority "" + deleted_at "2021-02-21 05:51:09" + text "MyText" +end \ No newline at end of file diff --git a/spec/models/rule_spec.rb b/spec/models/rule_spec.rb new file mode 100644 index 0000000000..8666bda713 --- /dev/null +++ b/spec/models/rule_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Rule, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end