From 1c6b02f936b984a17343b85a5cfb07ed93dd1cfb Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 28 Mar 2017 11:23:33 +0200 Subject: Fix #690 - Webfinger should handle new shortform profile URLs now (nice) --- app/controllers/xrd_controller.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'app/controllers') diff --git a/app/controllers/xrd_controller.rb b/app/controllers/xrd_controller.rb index 9e0277860..6db87cefc 100644 --- a/app/controllers/xrd_controller.rb +++ b/app/controllers/xrd_controller.rb @@ -36,11 +36,14 @@ class XrdController < ApplicationController end def username_from_resource - if resource_param.start_with?('acct:') || resource_param.include?('@') - resource_param.split('@').first.gsub('acct:', '') + if resource_param =~ /\Ahttps?:\/\// + path_params = Rails.application.routes.recognize_path(resource_param) + raise ActiveRecord::RecordNotFound unless path_params[:controller] == 'users' && path_params[:action] == 'show' + path_params[:username] else - url = Addressable::URI.parse(resource_param) - url.path.gsub('/users/', '') + username, domain = resource_param.gsub(/\Aacct:/, '').split('@') + raise ActiveRecord::RecordNotFound unless TagManager.instance.local_domain?(domain) + username end end -- cgit From de22c202f5e103f433926c763fc3ae179d5976ad Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 30 Mar 2017 15:06:59 +0200 Subject: Add counter caches for a large performance increase on API requests --- app/controllers/api/v1/accounts_controller.rb | 10 +++++----- app/controllers/api/v1/blocks_controller.rb | 2 +- app/controllers/api/v1/favourites_controller.rb | 2 +- app/controllers/api/v1/follow_requests_controller.rb | 2 +- app/controllers/api/v1/mutes_controller.rb | 2 +- app/controllers/api/v1/notifications_controller.rb | 4 ++-- app/controllers/api/v1/statuses_controller.rb | 6 +++--- app/models/favourite.rb | 2 +- app/models/follow.rb | 4 ++-- app/models/status.rb | 4 ++-- app/views/api/v1/statuses/_show.rabl | 4 ++-- db/migrate/20170330021336_add_counter_caches.rb | 14 ++++++++++++++ db/schema.rb | 7 ++++++- 13 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 db/migrate/20170330021336_add_counter_caches.rb (limited to 'app/controllers') diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index f07450eb0..da18474cb 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -20,7 +20,7 @@ class Api::V1::AccountsController < ApiController accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h @accounts = results.map { |f| accounts[f.target_account_id] } - set_account_counters_maps(@accounts) + # set_account_counters_maps(@accounts) next_path = following_api_v1_account_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) prev_path = following_api_v1_account_url(since_id: results.first.id) unless results.empty? @@ -35,7 +35,7 @@ class Api::V1::AccountsController < ApiController accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h @accounts = results.map { |f| accounts[f.account_id] } - set_account_counters_maps(@accounts) + # set_account_counters_maps(@accounts) next_path = followers_api_v1_account_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) prev_path = followers_api_v1_account_url(since_id: results.first.id) unless results.empty? @@ -52,8 +52,8 @@ class Api::V1::AccountsController < ApiController @statuses = cache_collection(@statuses, Status) 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 = statuses_api_v1_account_url(max_id: @statuses.last.id) unless @statuses.empty? prev_path = statuses_api_v1_account_url(since_id: @statuses.first.id) unless @statuses.empty? @@ -117,7 +117,7 @@ class Api::V1::AccountsController < ApiController def search @accounts = AccountSearchService.new.call(params[:q], limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:resolve] == 'true', current_account) - set_account_counters_maps(@accounts) unless @accounts.nil? + # set_account_counters_maps(@accounts) unless @accounts.nil? render action: :index end diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb index 08aefc175..dadf21265 100644 --- a/app/controllers/api/v1/blocks_controller.rb +++ b/app/controllers/api/v1/blocks_controller.rb @@ -11,7 +11,7 @@ class Api::V1::BlocksController < ApiController accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h @accounts = results.map { |f| accounts[f.target_account_id] }.compact - set_account_counters_maps(@accounts) + # set_account_counters_maps(@accounts) next_path = api_v1_blocks_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) prev_path = api_v1_blocks_url(since_id: results.first.id) unless results.empty? diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb index ef0a4854a..8a5b81e63 100644 --- a/app/controllers/api/v1/favourites_controller.rb +++ b/app/controllers/api/v1/favourites_controller.rb @@ -11,7 +11,7 @@ class Api::V1::FavouritesController < ApiController @statuses = cache_collection(Status.where(id: results.map(&:status_id)), Status) set_maps(@statuses) - set_counters_maps(@statuses) + # set_counters_maps(@statuses) next_path = api_v1_favourites_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_STATUSES_LIMIT) prev_path = api_v1_favourites_url(since_id: results.first.id) unless results.empty? diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb index 740083735..3b8e8c078 100644 --- a/app/controllers/api/v1/follow_requests_controller.rb +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -9,7 +9,7 @@ class Api::V1::FollowRequestsController < ApiController accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h @accounts = results.map { |f| accounts[f.account_id] } - set_account_counters_maps(@accounts) + # set_account_counters_maps(@accounts) next_path = api_v1_follow_requests_url(max_id: results.last.id) if results.size == DEFAULT_ACCOUNTS_LIMIT prev_path = api_v1_follow_requests_url(since_id: results.first.id) unless results.empty? diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb index 42a9ed412..6f48de040 100644 --- a/app/controllers/api/v1/mutes_controller.rb +++ b/app/controllers/api/v1/mutes_controller.rb @@ -11,7 +11,7 @@ class Api::V1::MutesController < ApiController accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h @accounts = results.map { |f| accounts[f.target_account_id] } - set_account_counters_maps(@accounts) + # set_account_counters_maps(@accounts) next_path = api_v1_mutes_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) prev_path = api_v1_mutes_url(since_id: results.first.id) unless results.empty? diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 544ba2442..7bbc5419c 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -14,8 +14,8 @@ class Api::V1::NotificationsController < ApiController statuses = @notifications.select { |n| !n.target_status.nil? }.map(&:target_status) set_maps(statuses) - set_counters_maps(statuses) - set_account_counters_maps(@notifications.map(&:from_account)) + # set_counters_maps(statuses) + # set_account_counters_maps(@notifications.map(&:from_account)) next_path = api_v1_notifications_url(max_id: @notifications.last.id) unless @notifications.empty? prev_path = api_v1_notifications_url(since_id: @notifications.first.id) unless @notifications.empty? diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 552f1b1b3..024258c0e 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -23,7 +23,7 @@ class Api::V1::StatusesController < ApiController statuses = [@status] + @context[:ancestors] + @context[:descendants] set_maps(statuses) - set_counters_maps(statuses) + # set_counters_maps(statuses) end def card @@ -36,7 +36,7 @@ class Api::V1::StatusesController < ApiController accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h @accounts = results.map { |r| accounts[r.account_id] } - set_account_counters_maps(@accounts) + # set_account_counters_maps(@accounts) next_path = reblogged_by_api_v1_status_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) prev_path = reblogged_by_api_v1_status_url(since_id: results.first.id) unless results.empty? @@ -51,7 +51,7 @@ class Api::V1::StatusesController < ApiController accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h @accounts = results.map { |f| accounts[f.account_id] } - set_account_counters_maps(@accounts) + # set_account_counters_maps(@accounts) next_path = favourited_by_api_v1_status_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) prev_path = favourited_by_api_v1_status_url(since_id: results.first.id) unless results.empty? diff --git a/app/models/favourite.rb b/app/models/favourite.rb index 67a293888..41d06e734 100644 --- a/app/models/favourite.rb +++ b/app/models/favourite.rb @@ -4,7 +4,7 @@ class Favourite < ApplicationRecord include Paginable belongs_to :account, inverse_of: :favourites - belongs_to :status, inverse_of: :favourites + belongs_to :status, inverse_of: :favourites, counter_cache: true has_one :notification, as: :activity, dependent: :destroy diff --git a/app/models/follow.rb b/app/models/follow.rb index 57db8c462..8bfe8b2f6 100644 --- a/app/models/follow.rb +++ b/app/models/follow.rb @@ -3,8 +3,8 @@ class Follow < ApplicationRecord include Paginable - belongs_to :account - belongs_to :target_account, class_name: 'Account' + belongs_to :account, counter_cache: :following_count + belongs_to :target_account, class_name: 'Account', counter_cache: :followers_count has_one :notification, as: :activity, dependent: :destroy diff --git a/app/models/status.rb b/app/models/status.rb index d5bbf70fb..81b26fd14 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -10,11 +10,11 @@ class Status < ApplicationRecord belongs_to :application, class_name: 'Doorkeeper::Application' - belongs_to :account, inverse_of: :statuses + belongs_to :account, inverse_of: :statuses, counter_cache: true belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account' belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies - belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs + belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, counter_cache: :reblogs_count has_many :favourites, inverse_of: :status, dependent: :destroy has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy diff --git a/app/views/api/v1/statuses/_show.rabl b/app/views/api/v1/statuses/_show.rabl index 059e0d13f..f384b6d14 100644 --- a/app/views/api/v1/statuses/_show.rabl +++ b/app/views/api/v1/statuses/_show.rabl @@ -3,8 +3,8 @@ attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id, :sensitiv node(:uri) { |status| TagManager.instance.uri_for(status) } node(:content) { |status| Formatter.instance.format(status) } node(:url) { |status| TagManager.instance.url_for(status) } -node(:reblogs_count) { |status| defined?(@reblogs_counts_map) ? (@reblogs_counts_map[status.id] || 0) : status.reblogs.count } -node(:favourites_count) { |status| defined?(@favourites_counts_map) ? (@favourites_counts_map[status.id] || 0) : status.favourites.count } +node(:reblogs_count) { |status| defined?(@reblogs_counts_map) ? (@reblogs_counts_map[status.id] || 0) : (status.try(:reblogs_count) || status.reblogs.count) } +node(:favourites_count) { |status| defined?(@favourites_counts_map) ? (@favourites_counts_map[status.id] || 0) : (status.try(:favourites_count) || status.favourites.count) } child :application do extends 'api/v1/apps/show' diff --git a/db/migrate/20170330021336_add_counter_caches.rb b/db/migrate/20170330021336_add_counter_caches.rb new file mode 100644 index 000000000..eb4e54d0a --- /dev/null +++ b/db/migrate/20170330021336_add_counter_caches.rb @@ -0,0 +1,14 @@ +class AddCounterCaches < ActiveRecord::Migration[5.0] + def change + add_column :statuses, :favourites_count, :integer + add_column :statuses, :reblogs_count, :integer + + execute('update statuses set favourites_count = (select count(*) from favourites where favourites.status_id = statuses.id), reblogs_count = (select count(*) from statuses as reblogs where reblogs.reblog_of_id = statuses.id)') + + add_column :accounts, :statuses_count, :integer + add_column :accounts, :followers_count, :integer + add_column :accounts, :following_count, :integer + + execute('update accounts set statuses_count = (select count(*) from statuses where account_id = accounts.id), followers_count = (select count(*) from follows where target_account_id = accounts.id), following_count = (select count(*) from follows where account_id = accounts.id)') + end +end diff --git a/db/schema.rb b/db/schema.rb index 2457b523d..52437ca57 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: 20170322162804) do +ActiveRecord::Schema.define(version: 20170330021336) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -44,6 +44,9 @@ ActiveRecord::Schema.define(version: 20170322162804) do t.boolean "suspended", default: false, null: false t.boolean "locked", default: false, null: false t.string "header_remote_url", default: "", null: false + t.integer "statuses_count" + t.integer "followers_count" + t.integer "following_count" t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", using: :btree t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree @@ -220,6 +223,8 @@ ActiveRecord::Schema.define(version: 20170322162804) do t.integer "application_id" t.text "spoiler_text", default: "", null: false t.boolean "reply", default: false + t.integer "favourites_count" + t.integer "reblogs_count" t.index ["account_id"], name: "index_statuses_on_account_id", using: :btree t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id", using: :btree t.index ["reblog_of_id"], name: "index_statuses_on_reblog_of_id", using: :btree -- cgit From e8875c6046615778c7ae6f1fc0c4a195fb5d3a03 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 30 Mar 2017 19:42:33 +0200 Subject: Import feature for following/blocking lists (addresses #62, #177, #201, #454) --- app/controllers/api/v1/timelines_controller.rb | 12 ++--- app/controllers/settings/imports_controller.rb | 34 ++++++++++++++ app/models/import.rb | 14 ++++++ app/views/layouts/admin.html.haml | 9 ++++ app/views/settings/imports/show.html.haml | 11 +++++ app/workers/import_worker.rb | 54 ++++++++++++++++++++++ config/locales/en.yml | 8 ++++ config/locales/simple_form.en.yml | 4 ++ config/navigation.rb | 1 + config/routes.rb | 1 + db/migrate/20170330163835_create_imports.rb | 11 +++++ ...0170330164118_add_attachment_data_to_imports.rb | 11 +++++ db/schema.rb | 14 +++++- spec/fabricators/import_fabricator.rb | 2 + spec/models/import_spec.rb | 5 ++ 15 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 app/controllers/settings/imports_controller.rb create mode 100644 app/models/import.rb create mode 100644 app/views/settings/imports/show.html.haml create mode 100644 app/workers/import_worker.rb create mode 100644 db/migrate/20170330163835_create_imports.rb create mode 100644 db/migrate/20170330164118_add_attachment_data_to_imports.rb create mode 100644 spec/fabricators/import_fabricator.rb create mode 100644 spec/models/import_spec.rb (limited to 'app/controllers') diff --git a/app/controllers/api/v1/timelines_controller.rb b/app/controllers/api/v1/timelines_controller.rb index af6e5b7df..0446b9e4d 100644 --- a/app/controllers/api/v1/timelines_controller.rb +++ b/app/controllers/api/v1/timelines_controller.rb @@ -11,8 +11,8 @@ class Api::V1::TimelinesController < ApiController @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? @@ -27,8 +27,8 @@ class Api::V1::TimelinesController < ApiController @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? @@ -44,8 +44,8 @@ class Api::V1::TimelinesController < ApiController @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? diff --git a/app/controllers/settings/imports_controller.rb b/app/controllers/settings/imports_controller.rb new file mode 100644 index 000000000..cbb5e65da --- /dev/null +++ b/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 diff --git a/app/models/import.rb b/app/models/import.rb new file mode 100644 index 000000000..255063c53 --- /dev/null +++ b/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 diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 750d6036f..59fe078df 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -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' } diff --git a/app/views/settings/imports/show.html.haml b/app/views/settings/imports/show.html.haml new file mode 100644 index 000000000..8502913dc --- /dev/null +++ b/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 diff --git a/app/workers/import_worker.rb b/app/workers/import_worker.rb new file mode 100644 index 000000000..a3ae2a85a --- /dev/null +++ b/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 diff --git a/config/locales/en.yml b/config/locales/en.yml index 3e130aaf8..965001e05 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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: %{name} is a user on %{domain}. You can follow them or interact with them if you have an account anywhere in the fediverse. If you don't, you can sign up here. notification_mailer: digest: @@ -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 diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index c4bd0ad96..df4f6ca00 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -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 @@ -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 diff --git a/config/navigation.rb b/config/navigation.rb index 607a0ff10..77556e5aa 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -9,6 +9,7 @@ SimpleNavigation::Configuration.run do |navigation| 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 diff --git a/config/routes.rb b/config/routes.rb index cf8364968..bfca5c734 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -51,6 +51,7 @@ Rails.application.routes.draw do 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 diff --git a/db/migrate/20170330163835_create_imports.rb b/db/migrate/20170330163835_create_imports.rb new file mode 100644 index 000000000..d6f74823d --- /dev/null +++ b/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 diff --git a/db/migrate/20170330164118_add_attachment_data_to_imports.rb b/db/migrate/20170330164118_add_attachment_data_to_imports.rb new file mode 100644 index 000000000..4850b0663 --- /dev/null +++ b/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 diff --git a/db/schema.rb b/db/schema.rb index 7675ed1a9..5a9ca1426 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: 20170330021336) do +ActiveRecord::Schema.define(version: 20170330164118) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -93,6 +93,18 @@ ActiveRecord::Schema.define(version: 20170330021336) do 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" diff --git a/spec/fabricators/import_fabricator.rb b/spec/fabricators/import_fabricator.rb new file mode 100644 index 000000000..e2eb1e0df --- /dev/null +++ b/spec/fabricators/import_fabricator.rb @@ -0,0 +1,2 @@ +Fabricator(:import) do +end diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb new file mode 100644 index 000000000..fa52077cd --- /dev/null +++ b/spec/models/import_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Import, type: :model do + +end -- cgit