add migration for pods-table

* add port to pods
* remove url from person and link people with pod-table
This commit is contained in:
Benjamin Neff 2016-01-25 01:08:52 +01:00
parent e0d6da7ad7
commit b1a6516474
19 changed files with 158 additions and 183 deletions

View file

@ -50,15 +50,14 @@ class Person < ActiveRecord::Base
has_many :roles
belongs_to :owner, :class_name => 'User'
belongs_to :pod
has_many :notification_actors
has_many :notifications, :through => :notification_actors
has_many :mentions, :dependent => :destroy
before_validation :clean_url
validates :url, :presence => true
validate :owner_xor_pod
validates :profile, :presence => true
validates :serialized_public_key, :presence => true
validates :diaspora_handle, :uniqueness => true
@ -205,8 +204,6 @@ class Person < ActiveRecord::Base
def url
url_to "/"
rescue
self[:url]
end
def profile_url
@ -290,23 +287,6 @@ class Person < ActiveRecord::Base
json
end
# Update an array of people given a url, and set it as the new destination_url
# @param people [Array<People>]
# @param url [String]
def self.url_batch_update(people, url)
people.each do |person|
person.update_url(url)
end
end
# @param person [Person]
# @param url [String]
def update_url(url)
@uri = URI.parse(url)
@uri.path = "/"
update_attributes(:url => @uri.to_s)
end
def lock_access!
self.closed_account = true
self.save
@ -317,27 +297,12 @@ class Person < ActiveRecord::Base
self
end
protected
def clean_url
if self.url
self.url = 'http://' + self.url unless self.url.match(/https?:\/\//)
self.url = self.url + '/' if self.url[-1, 1] != '/'
end
end
private
# @return [URI]
def uri
@uri ||= URI.parse(self[:url])
@uri.dup
end
# @param path [String]
# @return [String]
def url_to(path)
uri.tap {|uri| uri.path = path }.to_s
local? ? AppConfig.url_to(path) : pod.url_to(path)
end
def fix_profile
@ -345,4 +310,8 @@ class Person < ActiveRecord::Base
DiasporaFederation::Discovery::Discovery.new(diaspora_handle).fetch_and_save
reload
end
def owner_xor_pod
errors.add(:base, "Specify an owner or a pod, not both") unless owner.blank? ^ pod.blank?
end
end

View file

@ -19,16 +19,21 @@ class Pod < ActiveRecord::Base
ConnectionTester::NodeInfoFailure => :version_failed
}
DEFAULT_PORTS = [URI::HTTP::DEFAULT_PORT, URI::HTTPS::DEFAULT_PORT]
has_many :people
scope :check_failed, lambda {
where(arel_table[:status].gt(Pod.statuses[:no_errors]))
}
class << self
def find_or_create_by(opts) # Rename this method to not override an AR method
u = URI.parse(opts.fetch(:url))
find_or_initialize_by(host: u.host).tap do |pod|
uri = URI.parse(opts.fetch(:url))
port = DEFAULT_PORTS.include?(uri.port) ? nil : uri.port
find_or_initialize_by(host: uri.host, port: port).tap do |pod|
unless pod.persisted?
pod.ssl = (u.scheme == "https")
pod.ssl = (uri.scheme == "https")
pod.save
end
end
@ -57,15 +62,20 @@ class Pod < ActiveRecord::Base
end
def test_connection!
url = "#{ssl ? 'https' : 'http'}://#{host}"
result = ConnectionTester.check url
logger.info "testing pod: '#{url}' - #{result.inspect}"
result = ConnectionTester.check uri.to_s
logger.info "testing pod: '#{uri}' - #{result.inspect}"
transaction do
update_from_result(result)
end
end
# @param path [String]
# @return [String]
def url_to(path)
uri.tap {|uri| uri.path = path }.to_s
end
private
def update_from_result(result)
@ -97,4 +107,10 @@ class Pod < ActiveRecord::Base
:no_errors
end
end
# @return [URI]
def uri
@uri ||= (ssl ? URI::HTTPS : URI::HTTP).build(host: host, port: port)
@uri.dup
end
end

View file

@ -39,7 +39,7 @@ class User < ActiveRecord::Base
serialize :hidden_shareables, Hash
has_one :person, :foreign_key => :owner_id
has_one :person, inverse_of: :owner, foreign_key: :owner_id
has_one :profile, through: :person
delegate :guid, :public_key, :posts, :photos, :owns?, :image_url,
@ -82,8 +82,7 @@ class User < ActiveRecord::Base
has_many :share_visibilities
before_save :guard_unconfirmed_email,
:save_person!
before_save :guard_unconfirmed_email
def self.all_sharing_with_person(person)
User.joins(:contacts).where(:contacts => {:person_id => person.id})
@ -460,7 +459,6 @@ class User < ActiveRecord::Base
end
def set_person(person)
person.url = AppConfig.pod_uri.to_s
person.diaspora_handle = "#{self.username}#{User.diaspora_id_host}"
self.person = person
end
@ -539,16 +537,6 @@ class User < ActiveRecord::Base
end
end
# Sometimes we access the person in a strange way and need to do this
# @note we should make this method depricated.
#
# @return [Person]
def save_person!
self.person.save if self.person && self.person.changed?
self.person
end
def no_person_with_same_username
diaspora_id = "#{self.username}#{User.diaspora_id_host}"
if self.username_changed? && Person.exists?(:diaspora_handle => diaspora_id)

View file

@ -47,7 +47,7 @@ DiasporaFederation.configure do |config|
# find existing person or create a new one
person_entity = Person.find_by(diaspora_handle: person.diaspora_id) ||
Person.new(diaspora_handle: person.diaspora_id, guid: person.guid,
serialized_public_key: person.exported_key, url: person.url)
serialized_public_key: person.exported_key, pod: Pod.find_or_create_by(url: person.url))
profile = person.profile
profile_entity = person_entity.profile ||= Profile.new

View file

@ -0,0 +1,77 @@
class ExtendPods < ActiveRecord::Migration
class Pod < ActiveRecord::Base
has_many :people
DEFAULT_PORTS = [URI::HTTP::DEFAULT_PORT, URI::HTTPS::DEFAULT_PORT]
def self.find_or_create_by(opts)
uri = URI.parse(opts.fetch(:url))
port = DEFAULT_PORTS.include?(uri.port) ? nil : uri.port
find_or_initialize_by(host: uri.host, port: port).tap do |pod|
pod.ssl ||= (uri.scheme == "https")
pod.save
end
end
def url
(ssl ? URI::HTTPS : URI::HTTP).build(host: host, port: port, path: "/")
end
end
class Person < ActiveRecord::Base
belongs_to :owner, class_name: "User"
belongs_to :pod
def url
owner_id.nil? ? pod.url.to_s : AppConfig.url_to("/")
end
end
class User < ActiveRecord::Base
has_one :person, inverse_of: :owner, foreign_key: :owner_id
end
def up
remove_index :pods, :host
# add port
add_column :pods, :port, :integer
add_index :pods, %i(host port), unique: true, length: {host: 190, port: nil}, using: :btree
add_column :pods, :blocked, :boolean, default: false
Pod.reset_column_information
# link people with pod
add_column :people, :pod_id, :integer
add_index :people, :url, length: 190
add_foreign_key :people, :pods, name: :people_pod_id_fk, on_delete: :cascade
Person.where(owner: nil).group_by {|person| person[:url] }.each do |url, _|
pod = Pod.find_or_create_by(url: url)
Person.where(url: url).update_all(pod_id: pod.id)
end
# cleanup unused pods
Pod.joins("LEFT OUTER JOIN people ON pods.id = people.pod_id").delete_all("people.id is NULL")
remove_column :people, :url
end
def down
# restore url
add_column :people, :url, :text
Person.all.group_by(&:pod_id).each do |pod_id, persons|
Person.where(pod_id: pod_id).update_all(url: persons.first.url)
end
change_column :people, :url, :text, null: false
remove_foreign_key :people, :pods
remove_column :people, :pod_id
# remove pods with port
Pod.where.not(port: nil).delete_all
remove_index :pods, column: %i(host port)
remove_columns :pods, :port, :blocked
add_index :pods, :host, unique: true, length: 190
end
end

View file

@ -327,7 +327,6 @@ ActiveRecord::Schema.define(version: 20160225232049) do
create_table "people", force: :cascade do |t|
t.string "guid", limit: 255, null: false
t.text "url", limit: 65535, null: false
t.string "diaspora_handle", limit: 255, null: false
t.text "serialized_public_key", limit: 65535, null: false
t.integer "owner_id", limit: 4
@ -335,11 +334,13 @@ ActiveRecord::Schema.define(version: 20160225232049) do
t.datetime "updated_at", null: false
t.boolean "closed_account", default: false
t.integer "fetch_status", limit: 4, default: 0
t.integer "pod_id", limit: 4
end
add_index "people", ["diaspora_handle"], name: "index_people_on_diaspora_handle", unique: true, length: {"diaspora_handle"=>191}, using: :btree
add_index "people", ["guid"], name: "index_people_on_guid", unique: true, length: {"guid"=>191}, using: :btree
add_index "people", ["owner_id"], name: "index_people_on_owner_id", unique: true, using: :btree
add_index "people", ["pod_id"], name: "people_pod_id_fk", using: :btree
create_table "photos", force: :cascade do |t|
t.integer "tmp_old_id", limit: 4
@ -375,10 +376,12 @@ ActiveRecord::Schema.define(version: 20160225232049) do
t.integer "response_time", limit: 4, default: -1
t.string "software", limit: 255
t.string "error", limit: 255
t.integer "port", limit: 4
t.boolean "blocked", default: false
end
add_index "pods", ["checked_at"], name: "index_pods_on_checked_at", using: :btree
add_index "pods", ["host"], name: "index_pods_on_host", unique: true, length: {"host"=>190}, using: :btree
add_index "pods", ["host", "port"], name: "index_pods_on_host_and_port", unique: true, length: {"host"=>190, "port"=>nil}, using: :btree
add_index "pods", ["offline_since"], name: "index_pods_on_offline_since", using: :btree
add_index "pods", ["status"], name: "index_pods_on_status", using: :btree
@ -672,6 +675,7 @@ ActiveRecord::Schema.define(version: 20160225232049) do
add_foreign_key "notification_actors", "notifications", name: "notification_actors_notification_id_fk", on_delete: :cascade
add_foreign_key "o_auth_access_tokens", "authorizations"
add_foreign_key "o_auth_applications", "users"
add_foreign_key "people", "pods", name: "people_pod_id_fk", on_delete: :cascade
add_foreign_key "posts", "people", column: "author_id", name: "posts_author_id_fk", on_delete: :cascade
add_foreign_key "ppid", "o_auth_applications"
add_foreign_key "ppid", "users"

View file

@ -104,11 +104,11 @@ class AccountDeleter
end
def normal_ar_person_associates_to_delete
[:posts, :photos, :mentions, :participations, :roles]
%i(posts photos mentions participations roles)
end
def ignored_or_special_ar_person_associations
[:comments, :contacts, :notification_actors, :notifications, :owner, :profile, :conversation_visibilities]
%i(comments contacts notification_actors notifications owner profile conversation_visibilities pod)
end
def mark_account_deletion_complete

View file

@ -17,11 +17,10 @@ class ConnectionTester
#
# @api This is the entry point you're supposed to use for testing
# connections to other diaspora-compatible servers.
# @param [String] server URL
# @param [String] url URL
# @return [Result] result object containing information about the
# server and to what point the connection was successful
def check(url)
url = "http://#{url}" unless url.include?("://")
result = Result.new
begin
@ -70,8 +69,6 @@ class ConnectionTester
@uri ||= URI.parse(@url)
raise AddressFailure,
"invalid protocol: '#{@uri.scheme.upcase}'" unless http_uri?(@uri)
result.hostname = @uri.host
rescue AddressFailure => e
raise e
rescue URI::InvalidURIError => e
@ -228,11 +225,8 @@ class ConnectionTester
end
Result = Struct.new(
:hostname, :ip, :reachable, :ssl_status, :status_code, :rt, :software_version, :error
:ip, :reachable, :ssl_status, :status_code, :rt, :software_version, :error
) do
# @!attribute hostname
# @return [String] hostname derived from the URL
# @!attribute ip
# @return [String] resolved IP address from DNS query

View file

@ -86,10 +86,6 @@ class HydraWrapper
# Save the reference to the pod to the database if not already present
Pod.find_or_create_by(url: response.effective_url)
if redirecting_to_https? response
Person.url_batch_update people_for_receive_url, response.headers_hash['Location']
end
unless response.success?
logger.warn "event=http_multi_fail sender_id=#{@user.id} url=#{response.effective_url} " \
"return_code=#{response.return_code} response_code=#{response.response_code}"
@ -101,10 +97,4 @@ class HydraWrapper
end
end
end
# @return [Boolean]
def redirecting_to_https? response
response.code >= 300 && response.code < 400 &&
response.headers_hash['Location'] == response.request.url.sub('http://', 'https://')
end
end

View file

@ -25,10 +25,11 @@ describe Admin::PodsController, type: :controller do
end
it "returns the json data" do
@pods = (0..2).map { FactoryGirl.create(:pod).reload } # normalize timestamps
pods = (0..2).map { FactoryGirl.create(:pod).reload } # normalize timestamps
pods.unshift remote_raphael.pod
get :index, format: :json
expect(response.body).to eql(PodPresenter.as_collection(@pods).to_json)
expect(response.body).to eql(PodPresenter.as_collection(pods).to_json)
end
end

View file

@ -31,7 +31,7 @@ FactoryGirl.define do
factory(:person, aliases: %i(author)) do
sequence(:diaspora_handle) {|n| "bob-person-#{n}#{r_str}@example.net" }
url AppConfig.pod_uri.to_s
pod { Pod.find_or_create_by(url: "http://example.net") }
serialized_public_key OpenSSL::PKey::RSA.generate(1024).public_key.export
after(:build) do |person|
unless person.profile.first_name.present?
@ -69,10 +69,11 @@ FactoryGirl.define do
password_confirmation { |u| u.password }
serialized_private_key OpenSSL::PKey::RSA.generate(1024).export
after(:build) do |u|
u.person = FactoryGirl.build(:person, :profile => FactoryGirl.build(:profile),
:owner_id => u.id,
:serialized_public_key => u.encryption_key.public_key.export,
:diaspora_handle => "#{u.username}#{User.diaspora_id_host}")
u.person = FactoryGirl.build(:person,
profile: FactoryGirl.build(:profile),
pod: nil,
serialized_public_key: u.encryption_key.public_key.export,
diaspora_handle: "#{u.username}#{User.diaspora_id_host}")
end
after(:create) do |u|
u.person.save

View file

@ -8,10 +8,13 @@ end
def create_remote_user(pod)
FactoryGirl.build(:user).tap do |user|
user.person = FactoryGirl.create(:person,
profile: FactoryGirl.build(:profile),
serialized_public_key: user.encryption_key.public_key.export,
diaspora_handle: "#{user.username}@#{pod}")
allow(user).to receive(:person).and_return(
FactoryGirl.create(:person,
profile: FactoryGirl.build(:profile),
serialized_public_key: user.encryption_key.public_key.export,
pod: Pod.find_or_create_by(url: "http://#{pod}"),
diaspora_handle: "#{user.username}@#{pod}")
)
allow(DiasporaFederation.callbacks).to receive(:trigger)
.with(:fetch_private_key_by_diaspora_id, user.diaspora_handle) {
user.encryption_key

View file

@ -13,9 +13,9 @@ describe Diaspora::Fetcher::Public do
# the guid of the person is "7445f9a0a6c28ebb"
@fixture = File.open(Rails.root.join('spec', 'fixtures', 'public_posts.json')).read
@fetcher = Diaspora::Fetcher::Public.new
@person = FactoryGirl.create(:person, {:guid => "7445f9a0a6c28ebb",
:url => "https://remote-testpod.net",
:diaspora_handle => "testuser@remote-testpod.net"})
@person = FactoryGirl.create(:person, guid: "7445f9a0a6c28ebb",
pod: Pod.find_or_create_by(url: "https://remote-testpod.net"),
diaspora_handle: "testuser@remote-testpod.net")
stub_request(:get, /remote-testpod.net\/people\/.*\/stream/)
.with(headers: {

View file

@ -74,37 +74,4 @@ describe HydraWrapper do
end
end
describe '#redirecting_to_https?!' do
it 'does not execute unless response has a 3xx code' do
resp = double code: 200
expect(@wrapper.send(:redirecting_to_https?, resp)).to be false
end
it "returns true if just the protocol is different" do
host = "the-same.com/"
resp = double(
request: double(url: "http://#{host}"),
code: 302,
headers_hash: {
'Location' => "https://#{host}"
}
)
expect(@wrapper.send(:redirecting_to_https?, resp)).to be true
end
it "returns false if not just the protocol is different" do
host = "the-same.com/"
resp = double(
request: double(url: "http://#{host}"),
code: 302,
headers_hash: {
'Location' => "https://not-the-same/"
}
)
expect(@wrapper.send(:redirecting_to_https?, resp)).to be false
end
end
end

View file

@ -19,6 +19,10 @@ describe Postzord::Receiver::Public do
comment.save
#bob signs his comment, and then sends it up
xml = Salmon::Slap.create_by_user_and_activity(bob, comment.to_diaspora_xml).xml_for(nil)
person = bob.person
person.owner = nil
person.pod = Pod.find_or_create_by(url: AppConfig.pod_uri)
person.save
bob.destroy
comment.destroy
expect{

View file

@ -109,7 +109,7 @@ describe Person, :type => :model do
describe "valid url" do
context "https urls" do
let(:person) { FactoryGirl.build(:person, url: "https://example.com") }
let(:person) { FactoryGirl.build(:person, pod: Pod.find_or_create_by(url: "https://example.com")) }
it "should add trailing slash" do
expect(person.url).to eq("https://example.com/")
@ -129,7 +129,9 @@ describe Person, :type => :model do
end
context "messed up urls" do
let(:person) { FactoryGirl.build(:person, url: "https://example.com/a/bit/messed/up") }
let(:person) {
FactoryGirl.build(:person, pod: Pod.find_or_create_by(url: "https://example.com/a/bit/messed/up"))
}
it "should return the correct url" do
expect(person.url).to eq("https://example.com/")
@ -149,12 +151,12 @@ describe Person, :type => :model do
end
it "should allow ports in the url" do
person = FactoryGirl.build(:person, url: "https://example.com:3000/")
person = FactoryGirl.build(:person, pod: Pod.find_or_create_by(url: "https://example.com:3000/"))
expect(person.url).to eq("https://example.com:3000/")
end
it "should remove https port in the url" do
person = FactoryGirl.build(:person, url: "https://example.com:443/")
person = FactoryGirl.build(:person, pod: Pod.find_or_create_by(url: "https://example.com:443/"))
expect(person.url).to eq("https://example.com/")
end
end
@ -538,32 +540,6 @@ describe Person, :type => :model do
end
end
context 'updating urls' do
before do
@url = "http://new-url.com/"
end
describe '.url_batch_update' do
it "calls #update_person_url given an array of users and a url" do
people = [double.as_null_object, double.as_null_object, double.as_null_object]
people.each do |person|
expect(person).to receive(:update_url).with(@url)
end
Person.url_batch_update(people, @url)
end
end
describe '#update_url' do
it "updates a given person's url" do
expect {
alice.person.update_url(@url)
}.to change {
alice.person.reload.url
}.from(anything).to(@url)
end
end
end
describe '#lock_access!' do
it 'sets the closed_account flag' do
@person.lock_access!

View file

@ -200,7 +200,9 @@ describe User::Querying, :type => :model do
connect_users(alice, @alices_aspect, remote_user, asp3)
local_person = remote_user.person
local_person.owner_id = nil
local_person.diaspora_handle = "#{remote_user.username}@example.net"
local_person.owner = nil
local_person.pod = Pod.find_or_create_by(url: "http://example.net")
local_person.save
local_person.reload

View file

@ -111,21 +111,6 @@ describe User, :type => :model do
end
end
context 'callbacks' do
describe '#save_person!' do
it 'saves the corresponding user if it has changed' do
alice.person.url = "http://stuff.com"
expect_any_instance_of(Person).to receive(:save)
alice.save
end
it 'does not save the corresponding user if it has not changed' do
expect_any_instance_of(Person).not_to receive(:save)
alice.save
end
end
end
describe 'hidden_shareables' do
before do
@sm = FactoryGirl.create(:status_message)
@ -1192,8 +1177,7 @@ describe User, :type => :model do
invited_user.save(validate: false)
closed_account = FactoryGirl.create(:user)
closed_account.person.closed_account = true
closed_account.save
closed_account.person.lock_access!
end
it "returns total_users excluding closed accounts & users without usernames" do

View file

@ -108,21 +108,20 @@ describe Workers::HttpMulti do
Workers::HttpMulti.new.perform bob.id, @post_xml, [person.id], "Postzord::Dispatcher::Private"
end
it 'updates http users who have moved to https' do
it "updates http users who have moved to https" do
person = @people.first
person.update_url("http://remote.net/")
response = Typhoeus::Response.new(
code: 301,
effective_url: 'https://foobar.com',
code: 301,
effective_url: "https://example.net",
response_headers: "Location: #{person.receive_url.sub('http://', 'https://')}",
body: "",
time: 0.2
body: "",
time: 0.2
)
Typhoeus.stub(person.receive_url).and_return response
Workers::HttpMulti.new.perform bob.id, @post_xml, [person.id], "Postzord::Dispatcher::Private"
expect(Person.find(person.id).url).to eq("https://remote.net/")
expect(Person.find(person.id).url).to eq("https://example.net/")
end
it 'only sends to users with valid RSA keys' do