Implement social relay functionality

* .well-known/social-relay - to serve subscription preferences to relays
* Workers.deferred_dispatch relay carbon copy functionality for outbound sending

See discussion here: https://www.loomio.org/d/9vpoe0UR/public-post-federation#comment-730911 and spec here: https://wiki.diasporafoundation.org/Relay_servers_for_public_posts
This commit is contained in:
Jason Robinson 2015-07-17 16:59:27 +03:00
parent 0e439f6c1c
commit bdf6c71772
9 changed files with 272 additions and 6 deletions

View file

@ -0,0 +1,7 @@
class SocialRelayController < ApplicationController
respond_to :json
def well_known
render json: SocialRelayPresenter.new
end
end

View file

@ -0,0 +1,24 @@
class SocialRelayPresenter
def as_json(*)
{
"subscribe" => AppConfig.relay.inbound.subscribe,
"scope" => AppConfig.relay.inbound.scope,
"tags" => tags
}
end
def tags
return [] unless AppConfig.relay.inbound.scope == "tags"
tags = AppConfig.relay.inbound.pod_tags.present? ? AppConfig.relay.inbound.pod_tags.split(",") : []
add_user_tags(tags)
tags.uniq
end
def add_user_tags(tags)
if AppConfig.relay.inbound.include_user_tags?
user_ids = User.halfyear_actives.pluck(:id)
tag_ids = TagFollowing.where(user: user_ids).select(:tag_id).distinct.pluck(:tag_id)
tags.concat ActsAsTaggableOn::Tag.where(id: tag_ids).pluck(:name)
end
end
end

View file

@ -10,14 +10,38 @@ module Workers
user = User.find(user_id)
object = object_class_name.constantize.find(object_id)
opts = HashWithIndifferentAccess.new(opts)
opts[:services] = user.services.where(:type => opts.delete(:service_types))
if opts[:additional_subscribers].present?
opts[:additional_subscribers] = Person.where(:id => opts[:additional_subscribers])
end
opts[:services] = user.services.where(type: opts.delete(:service_types))
add_additional_subscribers(object, object_class_name, opts)
Postzord::Dispatcher.build(user, object, opts).post
rescue ActiveRecord::RecordNotFound # The target got deleted before the job was run
end
def add_additional_subscribers(object, object_class_name, opts)
if AppConfig.relay.outbound.send? &&
object_class_name == "StatusMessage" &&
object.respond_to?(:public?) && object.public?
handle_relay(opts)
end
if opts[:additional_subscribers].present?
opts[:additional_subscribers] = Person.where(id: opts[:additional_subscribers])
end
end
def handle_relay(opts)
relay_person = Person.find_by diaspora_handle: AppConfig.relay.outbound.handle.to_s
if relay_person
add_person_to_subscribers(opts, relay_person)
else
# Skip this message for relay and just queue a webfinger fetch for the relay handle
Workers::FetchWebfinger.perform_async(AppConfig.relay.outbound.handle)
end
end
def add_person_to_subscribers(opts, person)
opts[:additional_subscribers] ||= []
opts[:additional_subscribers] << person.id
end
end
end

View file

@ -193,6 +193,15 @@ defaults:
admins:
account:
podmin_email:
relay:
outbound:
send: false
handle: 'relay@relay.iliketoast.net'
inbound:
subscribe: false
scope: tags
include_user_tags: false
pod_tags:
# List valid environment variables
redistogo_url:

View file

@ -679,6 +679,38 @@ configuration: ## Section
## E-mail address to contact the administrator.
#podmin_email: 'podmin@example.org'
## Settings related to relays
relay: ## Section
## Relays are applications that exist to push public posts around to
## pods which want to subscribe to them but would not otherwise
## receive them due to not having direct contact with the remote pods.
##
## See more regarding relays: https://wiki.diasporafoundation.org/Relay_servers_for_public_posts
outbound: ## Section
## Enable this setting to send out public posts from this pod to a relay
#send: false
## Change default remote relay handle used for sending out here
#handle: 'relay@relay.iliketoast.net'
inbound: ## Section
## Enable this to receive public posts from relays
#subscribe: false
## Scope is either 'all' or 'tags' (default).
## - 'all', means this pod wants to receive all public posts from a relay
## - 'tags', means this pod wants only posts tagged with certain tags
#scope: tags
## If scope is 'tags', should we include tags that users on this pod follow?
## These are added in addition to 'pod_tags', if set.
#include_user_tags: false
## If scope is 'tags', a comma separated list of tags here can be set.
## For example "linux,diaspora", to receive posts related to these tags
#pod_tags:
## Here you can override settings defined above if you need
## to have them different in different environments.
production: ## Section

View file

@ -244,6 +244,9 @@ Diaspora::Application.routes.draw do
get 'terms' => 'terms#index'
end
# Relay
get ".well-known/x-social-relay" => "social_relay#well_known"
# Startpage
root :to => 'home#show'
end

View file

@ -0,0 +1,16 @@
require "spec_helper"
describe SocialRelayController, type: :controller do
describe "#well_known" do
it "responds to format json" do
get :well_known, format: "json"
expect(response.code).to eq("200")
end
it "contains json" do
get :well_known, format: "json"
json = JSON.parse(response.body)
expect(json["scope"]).to be_present
end
end
end

View file

@ -0,0 +1,122 @@
require "spec_helper"
describe SocialRelayPresenter do
before do
@presenter = SocialRelayPresenter.new
end
describe "#as_json" do
it "works" do
expect(@presenter.as_json).to be_present
expect(@presenter.as_json).to be_a Hash
end
end
describe "#social relay well-known contents" do
describe "defaults" do
it "provides valid detault data" do
expect(@presenter.as_json).to eq(
"subscribe" => false,
"scope" => "tags",
"tags" => []
)
end
end
describe "pod tags" do
before do
AppConfig.relay.inbound.pod_tags = "foo,bar"
AppConfig.relay.inbound.include_user_tags = false
end
it "provides pod tags" do
expect(@presenter.as_json).to eq(
"subscribe" => false,
"scope" => "tags",
"tags" => ["foo", "bar"]
)
end
end
describe "user tags" do
before do
AppConfig.relay.inbound.pod_tags = ""
AppConfig.relay.inbound.include_user_tags = true
ceetag = FactoryGirl.create(:tag, name: "cee")
lootag = FactoryGirl.create(:tag, name: "loo")
FactoryGirl.create(:tag_following, user: alice, tag: ceetag)
FactoryGirl.create(:tag_following, user: alice, tag: lootag)
alice.last_seen = Time.now - 2.month
alice.save
end
it "provides user tags" do
expect(@presenter.as_json).to eq(
"subscribe" => false,
"scope" => "tags",
"tags" => ["cee", "loo"]
)
end
end
describe "pod tags combined with user tags" do
before do
AppConfig.relay.inbound.pod_tags = "foo,bar"
AppConfig.relay.inbound.include_user_tags = true
ceetag = FactoryGirl.create(:tag, name: "cee")
lootag = FactoryGirl.create(:tag, name: "loo")
FactoryGirl.create(:tag_following, user: alice, tag: ceetag)
FactoryGirl.create(:tag_following, user: alice, tag: lootag)
alice.last_seen = Time.now - 2.month
alice.save
end
it "provides combined pod and user tags" do
expect(@presenter.as_json).to eq(
"subscribe" => false,
"scope" => "tags",
"tags" => ["foo", "bar", "cee", "loo"]
)
end
end
describe "user tags for inactive user" do
before do
AppConfig.relay.inbound.pod_tags = ""
AppConfig.relay.inbound.include_user_tags = true
ceetag = FactoryGirl.create(:tag, name: "cee")
lootag = FactoryGirl.create(:tag, name: "loo")
FactoryGirl.create(:tag_following, user: alice, tag: ceetag)
FactoryGirl.create(:tag_following, user: alice, tag: lootag)
alice.last_seen = Time.now - 8.month
alice.save
end
it "ignores user tags" do
expect(@presenter.as_json).to eq(
"subscribe" => false,
"scope" => "tags",
"tags" => []
)
end
end
describe "when scope is all" do
before do
AppConfig.relay.inbound.scope = "all"
AppConfig.relay.inbound.pod_tags = "foo,bar"
AppConfig.relay.inbound.include_user_tags = true
ceetag = FactoryGirl.create(:tag, name: "cee")
FactoryGirl.create(:tag_following, user: alice, tag: ceetag)
end
it "provides empty tags list" do
expect(@presenter.as_json).to eq(
"subscribe" => false,
"scope" => "all",
"tags" => []
)
end
end
end
end

View file

@ -3,7 +3,36 @@ require 'spec_helper'
describe Workers::DeferredDispatch do
it "handles non existing records gracefully" do
expect {
described_class.new.perform(alice.id, 'Comment', 0, {})
described_class.new.perform(alice.id, "Comment", 0, {})
}.to_not raise_error
end
describe "#social relay functionality" do
let(:message) { FactoryGirl.create(:status_message, author: alice.person, public: true) }
before do
AppConfig.relay.outbound.send = true
end
it "triggers fetch of relay handle" do
allow(Person).to receive(:find_by).and_return(nil)
expect(Workers::FetchWebfinger).to receive(:perform_async)
described_class.new.perform(alice.id, "StatusMessage", message.id, {})
end
it "triggers post to relay" do
relay_person = FactoryGirl.create(:person, diaspora_handle: AppConfig.relay.outbound.handle)
opts = {"additional_subscribers" => [relay_person], "services" => []}
allow(Person).to receive(:find_by).and_return(relay_person)
postzord = double
allow(Postzord::Dispatcher).to receive(:build).with(any_args).and_return(postzord)
allow(postzord).to receive(:post)
allow(Person).to receive(:where).and_return([relay_person])
expect(Postzord::Dispatcher).to receive(:build).with(alice, message, opts)
described_class.new.perform(alice.id, "StatusMessage", message.id, {})
end
end
end