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:
parent
0e439f6c1c
commit
bdf6c71772
9 changed files with 272 additions and 6 deletions
7
app/controllers/social_relay_controller.rb
Normal file
7
app/controllers/social_relay_controller.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
class SocialRelayController < ApplicationController
|
||||
respond_to :json
|
||||
|
||||
def well_known
|
||||
render json: SocialRelayPresenter.new
|
||||
end
|
||||
end
|
||||
24
app/presenters/social_relay_presenter.rb
Normal file
24
app/presenters/social_relay_presenter.rb
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
16
spec/controllers/social_relay_controller_spec.rb
Normal file
16
spec/controllers/social_relay_controller_spec.rb
Normal 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
|
||||
122
spec/presenters/social_relay_presenter_spec.rb
Normal file
122
spec/presenters/social_relay_presenter_spec.rb
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue