Skip to content

Commit

Permalink
Import feature for following/blocking lists (addresses #62, #177, #201,
Browse files Browse the repository at this point in the history
  • Loading branch information
Gargron committed Mar 30, 2017
1 parent 03fb6c1 commit e8875c6
Show file tree
Hide file tree
Showing 15 changed files with 184 additions and 7 deletions.
12 changes: 6 additions & 6 deletions app/controllers/api/v1/timelines_controller.rb
Expand Up @@ -11,8 +11,8 @@ def home
@statuses = cache_collection(@statuses)

set_maps(@statuses)
set_counters_maps(@statuses)
set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
# set_counters_maps(@statuses)
# set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)

next_path = api_v1_home_timeline_url(max_id: @statuses.last.id) unless @statuses.empty?
prev_path = api_v1_home_timeline_url(since_id: @statuses.first.id) unless @statuses.empty?
Expand All @@ -27,8 +27,8 @@ def public
@statuses = cache_collection(@statuses)

set_maps(@statuses)
set_counters_maps(@statuses)
set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
# set_counters_maps(@statuses)
# set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)

next_path = api_v1_public_timeline_url(max_id: @statuses.last.id) unless @statuses.empty?
prev_path = api_v1_public_timeline_url(since_id: @statuses.first.id) unless @statuses.empty?
Expand All @@ -44,8 +44,8 @@ def tag
@statuses = cache_collection(@statuses)

set_maps(@statuses)
set_counters_maps(@statuses)
set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
# set_counters_maps(@statuses)
# set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)

next_path = api_v1_hashtag_timeline_url(params[:id], max_id: @statuses.last.id) unless @statuses.empty?
prev_path = api_v1_hashtag_timeline_url(params[:id], since_id: @statuses.first.id) unless @statuses.empty?
Expand Down
34 changes: 34 additions & 0 deletions app/controllers/settings/imports_controller.rb
@@ -0,0 +1,34 @@
# frozen_string_literal: true

class Settings::ImportsController < ApplicationController
layout 'admin'

before_action :authenticate_user!
before_action :set_account

def show
@import = Import.new
end

def create
@import = Import.new(import_params)
@import.account = @account

if @import.save
ImportWorker.perform_async(@import.id)
redirect_to settings_import_path, notice: I18n.t('imports.success')
else
render action: :show
end
end

private

def set_account
@account = current_user.account
end

def import_params
params.require(:import).permit(:data, :type)
end
end
14 changes: 14 additions & 0 deletions app/models/import.rb
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class Import < ApplicationRecord
self.inheritance_column = false

enum type: [:following, :blocking]

belongs_to :account

FILE_TYPES = ['text/plain', 'text/csv'].freeze

has_attached_file :data, url: '/system/:hash.:extension', hash_secret: ENV.fetch('PAPERCLIP_SECRET')
validates_attachment_content_type :data, content_type: FILE_TYPES
end
9 changes: 9 additions & 0 deletions app/views/layouts/admin.html.haml
Expand Up @@ -12,6 +12,15 @@
.content-wrapper
.content
%h2= yield :page_title

- if flash[:notice]
.flash-message.notice
%strong= flash[:notice]

- if flash[:alert]
.flash-message.alert
%strong= flash[:alert]

= yield

= render template: "layouts/application", locals: { body_classes: 'admin' }
11 changes: 11 additions & 0 deletions app/views/settings/imports/show.html.haml
@@ -0,0 +1,11 @@
- content_for :page_title do
= t('settings.import')

%p.hint= t('imports.preface')

= simple_form_for @import, url: settings_import_path do |f|
= f.input :type, collection: Import.types.keys, wrapper: :with_label, include_blank: false, label_method: lambda { |type| I18n.t("imports.types.#{type}") }
= f.input :data, wrapper: :with_label, hint: t('simple_form.hints.imports.data')

.actions
= f.button :button, t('imports.upload'), type: :submit
54 changes: 54 additions & 0 deletions app/workers/import_worker.rb
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require 'csv'

class ImportWorker
include Sidekiq::Worker

sidekiq_options retry: false

def perform(import_id)
import = Import.find(import_id)

case import.type
when 'blocking'
process_blocks(import)
when 'following'
process_follows(import)
end

import.destroy
end

private

def process_blocks(import)
from_account = import.account

CSV.foreach(import.data.path) do |row|
next if row.size != 1

begin
target_account = FollowRemoteAccountService.new.call(row[0])
next if target_account.nil?
BlockService.new.call(from_account, target_account)
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError
next
end
end
end

def process_follows(import)
from_account = import.account

CSV.foreach(import.data.path) do |row|
next if row.size != 1

begin
FollowService.new.call(from_account, row[0])
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError
next
end
end
end
end
8 changes: 8 additions & 0 deletions config/locales/en.yml
Expand Up @@ -85,6 +85,13 @@ en:
validation_errors:
one: Something isn't quite right yet! Please review the error below
other: Something isn't quite right yet! Please review %{count} errors below
imports:
preface: You can import certain data like all the people you are following or blocking into your account on this instance, from files created by an export on another instance.
success: Your data was successfully uploaded and will now be processed in due time
types:
blocking: Blocking list
following: Following list
upload: Upload
landing_strip_html: <strong>%{name}</strong> is a user on <strong>%{domain}</strong>. You can follow them or interact with them if you have an account anywhere in the fediverse. If you don't, you can <a href="%{sign_up_path}">sign up here</a>.
notification_mailer:
digest:
Expand Down Expand Up @@ -124,6 +131,7 @@ en:
back: Back to Mastodon
edit_profile: Edit profile
export: Data export
import: Import
preferences: Preferences
settings: Settings
two_factor_auth: Two-factor Authentication
Expand Down
4 changes: 4 additions & 0 deletions config/locales/simple_form.en.yml
Expand Up @@ -8,12 +8,15 @@ en:
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
note: At most 160 characters
imports:
data: CSV file exported from another Mastodon instance
labels:
defaults:
avatar: Avatar
confirm_new_password: Confirm new password
confirm_password: Confirm password
current_password: Current password
data: Data
display_name: Display name
email: E-mail address
header: Header
Expand All @@ -24,6 +27,7 @@ en:
otp_attempt: Two-factor code
password: Password
setting_default_privacy: Post privacy
type: Import type
username: Username
interactions:
must_be_follower: Block notifications from non-followers
Expand Down
1 change: 1 addition & 0 deletions config/navigation.rb
Expand Up @@ -9,6 +9,7 @@
settings.item :preferences, safe_join([fa_icon('sliders fw'), t('settings.preferences')]), settings_preferences_url
settings.item :password, safe_join([fa_icon('cog fw'), t('auth.change_password')]), edit_user_registration_url
settings.item :two_factor_auth, safe_join([fa_icon('mobile fw'), t('settings.two_factor_auth')]), settings_two_factor_auth_url
settings.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_url
settings.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url
settings.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url
end
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Expand Up @@ -51,6 +51,7 @@
namespace :settings do
resource :profile, only: [:show, :update]
resource :preferences, only: [:show, :update]
resource :import, only: [:show, :create]

resource :export, only: [:show] do
collection do
Expand Down
11 changes: 11 additions & 0 deletions db/migrate/20170330163835_create_imports.rb
@@ -0,0 +1,11 @@
class CreateImports < ActiveRecord::Migration[5.0]
def change
create_table :imports do |t|
t.integer :account_id, null: false
t.integer :type, null: false
t.boolean :approved

t.timestamps
end
end
end
11 changes: 11 additions & 0 deletions db/migrate/20170330164118_add_attachment_data_to_imports.rb
@@ -0,0 +1,11 @@
class AddAttachmentDataToImports < ActiveRecord::Migration
def self.up
change_table :imports do |t|
t.attachment :data
end
end

def self.down
remove_attachment :imports, :data
end
end
14 changes: 13 additions & 1 deletion db/schema.rb
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20170330021336) do
ActiveRecord::Schema.define(version: 20170330164118) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -93,6 +93,18 @@
t.index ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true, using: :btree
end

create_table "imports", force: :cascade do |t|
t.integer "account_id", null: false
t.integer "type", null: false
t.boolean "approved"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "data_file_name"
t.string "data_content_type"
t.integer "data_file_size"
t.datetime "data_updated_at"
end

create_table "media_attachments", force: :cascade do |t|
t.bigint "status_id"
t.string "file_file_name"
Expand Down
2 changes: 2 additions & 0 deletions spec/fabricators/import_fabricator.rb
@@ -0,0 +1,2 @@
Fabricator(:import) do
end
5 changes: 5 additions & 0 deletions spec/models/import_spec.rb
@@ -0,0 +1,5 @@
require 'rails_helper'

RSpec.describe Import, type: :model do

end

0 comments on commit e8875c6

Please sign in to comment.