Add autofollow option to invites (#7805)
* Add autofollow option to invites * Trigger CodeClimate rebuild
This commit is contained in:
		@@ -3,6 +3,7 @@
 | 
				
			|||||||
class Auth::RegistrationsController < Devise::RegistrationsController
 | 
					class Auth::RegistrationsController < Devise::RegistrationsController
 | 
				
			||||||
  layout :determine_layout
 | 
					  layout :determine_layout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before_action :set_invite, only: [:new, :create]
 | 
				
			||||||
  before_action :check_enabled_registrations, only: [:new, :create]
 | 
					  before_action :check_enabled_registrations, only: [:new, :create]
 | 
				
			||||||
  before_action :configure_sign_up_params, only: [:create]
 | 
					  before_action :configure_sign_up_params, only: [:create]
 | 
				
			||||||
  before_action :set_sessions, only: [:edit, :update]
 | 
					  before_action :set_sessions, only: [:edit, :update]
 | 
				
			||||||
@@ -51,7 +52,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
 | 
				
			|||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def allowed_registrations?
 | 
					  def allowed_registrations?
 | 
				
			||||||
    Setting.open_registrations || (invite_code.present? && Invite.find_by(code: invite_code)&.valid_for_use?)
 | 
					    Setting.open_registrations || @invite&.valid_for_use?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def invite_code
 | 
					  def invite_code
 | 
				
			||||||
@@ -68,6 +69,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
 | 
				
			|||||||
    @instance_presenter = InstancePresenter.new
 | 
					    @instance_presenter = InstancePresenter.new
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def set_invite
 | 
				
			||||||
 | 
					    @invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def determine_layout
 | 
					  def determine_layout
 | 
				
			||||||
    %w(edit update).include?(action_name) ? 'admin' : 'auth'
 | 
					    %w(edit update).include?(action_name) ? 'admin' : 'auth'
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ class InvitesController < ApplicationController
 | 
				
			|||||||
    authorize :invite, :create?
 | 
					    authorize :invite, :create?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @invites = invites
 | 
					    @invites = invites
 | 
				
			||||||
    @invite  = Invite.new(expires_in: 1.day.to_i)
 | 
					    @invite  = Invite.new
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def create
 | 
					  def create
 | 
				
			||||||
@@ -42,6 +42,6 @@ class InvitesController < ApplicationController
 | 
				
			|||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def resource_params
 | 
					  def resource_params
 | 
				
			||||||
    params.require(:invite).permit(:max_uses, :expires_in)
 | 
					    params.require(:invite).permit(:max_uses, :expires_in, :autofollow)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -458,23 +458,31 @@
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.account-card {
 | 
					.account-card {
 | 
				
			||||||
  padding: 14px 10px;
 | 
					 | 
				
			||||||
  background: $simple-background-color;
 | 
					 | 
				
			||||||
  border-radius: 4px;
 | 
					  border-radius: 4px;
 | 
				
			||||||
  text-align: left;
 | 
					  text-align: left;
 | 
				
			||||||
  box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
 | 
					  box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
 | 
				
			||||||
 | 
					  background: $simple-background-color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .detailed-status__display-name {
 | 
					  &__header {
 | 
				
			||||||
 | 
					    background-size: cover;
 | 
				
			||||||
 | 
					    background-position: center center;
 | 
				
			||||||
 | 
					    height: 90px;
 | 
				
			||||||
 | 
					    border-radius: 4px 4px 0 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  & > .detailed-status__display-name {
 | 
				
			||||||
    display: block;
 | 
					    display: block;
 | 
				
			||||||
    overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
    margin-bottom: 15px;
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    padding: 10px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    &:last-child {
 | 
					    &:last-child {
 | 
				
			||||||
      margin-bottom: 0;
 | 
					      margin-bottom: 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    & > div {
 | 
					    & > div:first-child {
 | 
				
			||||||
      float: left;
 | 
					      flex: 0 0 auto;
 | 
				
			||||||
      margin-right: 10px;
 | 
					      margin-right: 10px;
 | 
				
			||||||
      width: 48px;
 | 
					      width: 48px;
 | 
				
			||||||
      height: 48px;
 | 
					      height: 48px;
 | 
				
			||||||
@@ -483,9 +491,11 @@
 | 
				
			|||||||
    .avatar {
 | 
					    .avatar {
 | 
				
			||||||
      display: block;
 | 
					      display: block;
 | 
				
			||||||
      border-radius: 4px;
 | 
					      border-radius: 4px;
 | 
				
			||||||
 | 
					      margin: 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .display-name {
 | 
					    .display-name {
 | 
				
			||||||
 | 
					      flex: 1 0 auto;
 | 
				
			||||||
      display: block;
 | 
					      display: block;
 | 
				
			||||||
      max-width: 100%;
 | 
					      max-width: 100%;
 | 
				
			||||||
      overflow: hidden;
 | 
					      overflow: hidden;
 | 
				
			||||||
@@ -493,6 +503,10 @@
 | 
				
			|||||||
      text-overflow: ellipsis;
 | 
					      text-overflow: ellipsis;
 | 
				
			||||||
      cursor: default;
 | 
					      cursor: default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      & > .detailed-status__display-name {
 | 
				
			||||||
 | 
					        margin-bottom: 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      strong {
 | 
					      strong {
 | 
				
			||||||
        font-weight: 500;
 | 
					        font-weight: 500;
 | 
				
			||||||
        color: $ui-base-color;
 | 
					        color: $ui-base-color;
 | 
				
			||||||
@@ -519,9 +533,28 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .account__header__content {
 | 
					  .counter {
 | 
				
			||||||
    font-size: 14px;
 | 
					    box-sizing: border-box;
 | 
				
			||||||
    color: $inverted-text-color;
 | 
					    flex: 0 0 auto;
 | 
				
			||||||
 | 
					    color: $light-text-color;
 | 
				
			||||||
 | 
					    padding: 0 10px;
 | 
				
			||||||
 | 
					    cursor: default;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    line-height: 24px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .counter-label {
 | 
				
			||||||
 | 
					      font-size: 12px;
 | 
				
			||||||
 | 
					      display: block;
 | 
				
			||||||
 | 
					      text-transform: uppercase;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .counter-number {
 | 
				
			||||||
 | 
					      font-weight: 500;
 | 
				
			||||||
 | 
					      font-size: 16px;
 | 
				
			||||||
 | 
					      color: $inverted-text-color;
 | 
				
			||||||
 | 
					      font-family: 'mastodon-font-display', sans-serif;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@
 | 
				
			|||||||
#  uses       :integer          default(0), not null
 | 
					#  uses       :integer          default(0), not null
 | 
				
			||||||
#  created_at :datetime         not null
 | 
					#  created_at :datetime         not null
 | 
				
			||||||
#  updated_at :datetime         not null
 | 
					#  updated_at :datetime         not null
 | 
				
			||||||
 | 
					#  autofollow :boolean          default(FALSE), not null
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Invite < ApplicationRecord
 | 
					class Invite < ApplicationRecord
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,13 +2,25 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class BootstrapTimelineService < BaseService
 | 
					class BootstrapTimelineService < BaseService
 | 
				
			||||||
  def call(source_account)
 | 
					  def call(source_account)
 | 
				
			||||||
    bootstrap_timeline_accounts.each do |target_account|
 | 
					    @source_account = source_account
 | 
				
			||||||
      FollowService.new.call(source_account, target_account)
 | 
					
 | 
				
			||||||
    end
 | 
					    autofollow_inviter!
 | 
				
			||||||
 | 
					    autofollow_bootstrap_timeline_accounts!
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def autofollow_inviter!
 | 
				
			||||||
 | 
					    return unless @source_account&.user&.invite&.autofollow?
 | 
				
			||||||
 | 
					    FollowService.new.call(@source_account, @source_account.user.invite.user.account)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def autofollow_bootstrap_timeline_accounts!
 | 
				
			||||||
 | 
					    bootstrap_timeline_accounts.each do |target_account|
 | 
				
			||||||
 | 
					      FollowService.new.call(@source_account, target_account)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def bootstrap_timeline_accounts
 | 
					  def bootstrap_timeline_accounts
 | 
				
			||||||
    return @bootstrap_timeline_accounts if defined?(@bootstrap_timeline_accounts)
 | 
					    return @bootstrap_timeline_accounts if defined?(@bootstrap_timeline_accounts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,11 @@
 | 
				
			|||||||
= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
 | 
					= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
 | 
				
			||||||
  = render 'shared/error_messages', object: resource
 | 
					  = render 'shared/error_messages', object: resource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - if @invite.present? && @invite.autofollow?
 | 
				
			||||||
 | 
					    .fields-group{ style: 'margin-bottom: 30px' }
 | 
				
			||||||
 | 
					      %p.hint{ style: 'text-align: center' }= t('invites.invited_by')
 | 
				
			||||||
 | 
					      = render 'authorize_follows/card', account: @invite.user.account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  = f.simple_fields_for :account do |ff|
 | 
					  = f.simple_fields_for :account do |ff|
 | 
				
			||||||
    .input-with-append
 | 
					    .input-with-append
 | 
				
			||||||
      = ff.input :username, autofocus: true, placeholder: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off' }
 | 
					      = ff.input :username, autofocus: true, placeholder: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off' }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
.account-card
 | 
					.account-card
 | 
				
			||||||
 | 
					  .account-card__header{ style: "background-image: url(#{account.header.url(:original)})" }
 | 
				
			||||||
  .detailed-status__display-name
 | 
					  .detailed-status__display-name
 | 
				
			||||||
    %div
 | 
					    %div
 | 
				
			||||||
      = image_tag account.avatar.url(:original), alt: '', width: 48, height: 48, class: 'avatar'
 | 
					      = image_tag account.avatar.url(:original), alt: '', width: 48, height: 48, class: 'avatar'
 | 
				
			||||||
@@ -9,5 +10,14 @@
 | 
				
			|||||||
        %strong.emojify= display_name(account, custom_emojify: true)
 | 
					        %strong.emojify= display_name(account, custom_emojify: true)
 | 
				
			||||||
        %span @#{account.acct}
 | 
					        %span @#{account.acct}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - if account.note?
 | 
					    .counter
 | 
				
			||||||
    .account__header__content.emojify= Formatter.instance.simplified_format(account)
 | 
					      %span.counter-number= number_to_human account.statuses_count, strip_insignificant_zeros: true
 | 
				
			||||||
 | 
					      %span.counter-label= t('accounts.posts')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .counter
 | 
				
			||||||
 | 
					      %span.counter-number= number_to_human account.following_count, strip_insignificant_zeros: true
 | 
				
			||||||
 | 
					      %span.counter-label= t('accounts.following')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .counter
 | 
				
			||||||
 | 
					      %span.counter-number= number_to_human account.followers_count, strip_insignificant_zeros: true
 | 
				
			||||||
 | 
					      %span.counter-label= t('accounts.followers')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,5 +5,8 @@
 | 
				
			|||||||
    = f.input :max_uses, wrapper: :with_label, collection: [1, 5, 10, 25, 50, 100], label_method: lambda { |num| I18n.t('invites.max_uses', count: num) }, prompt: I18n.t('invites.max_uses_prompt')
 | 
					    = f.input :max_uses, wrapper: :with_label, collection: [1, 5, 10, 25, 50, 100], label_method: lambda { |num| I18n.t('invites.max_uses', count: num) }, prompt: I18n.t('invites.max_uses_prompt')
 | 
				
			||||||
    = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt')
 | 
					    = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .fields-group
 | 
				
			||||||
 | 
					    = f.input :autofollow, wrapper: :with_label
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .actions
 | 
					  .actions
 | 
				
			||||||
    = f.button :button, t('invites.generate'), type: :submit
 | 
					    = f.button :button, t('invites.generate'), type: :submit
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -514,6 +514,7 @@ en:
 | 
				
			|||||||
      '86400': 1 day
 | 
					      '86400': 1 day
 | 
				
			||||||
    expires_in_prompt: Never
 | 
					    expires_in_prompt: Never
 | 
				
			||||||
    generate: Generate
 | 
					    generate: Generate
 | 
				
			||||||
 | 
					    invited_by: 'You were invited by:'
 | 
				
			||||||
    max_uses:
 | 
					    max_uses:
 | 
				
			||||||
      one: 1 use
 | 
					      one: 1 use
 | 
				
			||||||
      other: "%{count} uses"
 | 
					      other: "%{count} uses"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ en:
 | 
				
			|||||||
  simple_form:
 | 
					  simple_form:
 | 
				
			||||||
    hints:
 | 
					    hints:
 | 
				
			||||||
      defaults:
 | 
					      defaults:
 | 
				
			||||||
 | 
					        autofollow: People who sign up through the invite will automatically follow you
 | 
				
			||||||
        avatar: PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px
 | 
					        avatar: PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px
 | 
				
			||||||
        bot: This account mainly performs automated actions and might not be monitored
 | 
					        bot: This account mainly performs automated actions and might not be monitored
 | 
				
			||||||
        digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
 | 
					        digest: Only sent after a long period of inactivity and only if you have received any personal messages in your absence
 | 
				
			||||||
@@ -30,6 +31,7 @@ en:
 | 
				
			|||||||
          name: Label
 | 
					          name: Label
 | 
				
			||||||
          value: Content
 | 
					          value: Content
 | 
				
			||||||
      defaults:
 | 
					      defaults:
 | 
				
			||||||
 | 
					        autofollow: Invite to follow your account
 | 
				
			||||||
        avatar: Avatar
 | 
					        avatar: Avatar
 | 
				
			||||||
        bot: This is a bot account
 | 
					        bot: This is a bot account
 | 
				
			||||||
        confirm_new_password: Confirm new password
 | 
					        confirm_new_password: Confirm new password
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								db/migrate/20180615122121_add_autofollow_to_invites.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								db/migrate/20180615122121_add_autofollow_to_invites.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					require Rails.root.join('lib', 'mastodon', 'migration_helpers')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AddAutofollowToInvites < ActiveRecord::Migration[5.2]
 | 
				
			||||||
 | 
					  include Mastodon::MigrationHelpers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  disable_ddl_transaction!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def change
 | 
				
			||||||
 | 
					    safety_assured do
 | 
				
			||||||
 | 
					      add_column_with_default :invites, :autofollow, :bool, default: false, allow_null: false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def down
 | 
				
			||||||
 | 
					    remove_column :invites, :autofollow
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
@@ -10,7 +10,7 @@
 | 
				
			|||||||
#
 | 
					#
 | 
				
			||||||
# It's strongly recommended that you check this file into your version control system.
 | 
					# It's strongly recommended that you check this file into your version control system.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ActiveRecord::Schema.define(version: 2018_06_09_104432) do
 | 
					ActiveRecord::Schema.define(version: 2018_06_15_122121) do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # These are extensions that must be enabled in order to support this database
 | 
					  # These are extensions that must be enabled in order to support this database
 | 
				
			||||||
  enable_extension "plpgsql"
 | 
					  enable_extension "plpgsql"
 | 
				
			||||||
@@ -219,6 +219,7 @@ ActiveRecord::Schema.define(version: 2018_06_09_104432) do
 | 
				
			|||||||
    t.integer "uses", default: 0, null: false
 | 
					    t.integer "uses", default: 0, null: false
 | 
				
			||||||
    t.datetime "created_at", null: false
 | 
					    t.datetime "created_at", null: false
 | 
				
			||||||
    t.datetime "updated_at", null: false
 | 
					    t.datetime "updated_at", null: false
 | 
				
			||||||
 | 
					    t.boolean "autofollow", default: false, null: false
 | 
				
			||||||
    t.index ["code"], name: "index_invites_on_code", unique: true
 | 
					    t.index ["code"], name: "index_invites_on_code", unique: true
 | 
				
			||||||
    t.index ["user_id"], name: "index_invites_on_user_id"
 | 
					    t.index ["user_id"], name: "index_invites_on_user_id"
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user