federails 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +182 -7
- data/Rakefile +5 -5
- data/app/controllers/federails/application_controller.rb +23 -0
- data/app/controllers/federails/client/activities_controller.rb +21 -0
- data/app/controllers/federails/client/actors_controller.rb +37 -0
- data/app/controllers/federails/client/followings_controller.rb +101 -0
- data/app/controllers/federails/server/activities_controller.rb +65 -0
- data/app/controllers/federails/server/actors_controller.rb +34 -0
- data/app/controllers/federails/server/followings_controller.rb +19 -0
- data/app/controllers/federails/server/nodeinfo_controller.rb +22 -0
- data/app/controllers/federails/server/server_controller.rb +17 -0
- data/app/controllers/federails/server/web_finger_controller.rb +38 -0
- data/app/helpers/federails/application_helper.rb +8 -0
- data/app/jobs/federails/notify_inbox_job.rb +12 -0
- data/app/mailers/federails/application_mailer.rb +2 -2
- data/app/models/concerns/federails/entity.rb +57 -0
- data/app/models/concerns/federails/has_uuid.rb +35 -0
- data/app/models/federails/activity.rb +35 -0
- data/app/models/federails/actor.rb +189 -0
- data/app/models/federails/following.rb +52 -0
- data/app/policies/federails/client/activity_policy.rb +6 -0
- data/app/policies/federails/client/actor_policy.rb +15 -0
- data/app/policies/federails/client/following_policy.rb +35 -0
- data/app/policies/federails/federails_policy.rb +59 -0
- data/app/policies/federails/server/activity_policy.rb +6 -0
- data/app/policies/federails/server/actor_policy.rb +23 -0
- data/app/policies/federails/server/following_policy.rb +6 -0
- data/app/views/federails/client/activities/_activity.html.erb +5 -0
- data/app/views/federails/client/activities/_activity.json.jbuilder +1 -0
- data/app/views/federails/client/activities/_index.json.jbuilder +1 -0
- data/app/views/federails/client/activities/feed.html.erb +4 -0
- data/app/views/federails/client/activities/feed.json.jbuilder +1 -0
- data/app/views/federails/client/activities/index.html.erb +5 -0
- data/app/views/federails/client/activities/index.json.jbuilder +1 -0
- data/app/views/federails/client/actors/_actor.json.jbuilder +14 -0
- data/app/views/federails/client/actors/_lookup_form.html.erb +5 -0
- data/app/views/federails/client/actors/index.html.erb +24 -0
- data/app/views/federails/client/actors/index.json.jbuilder +1 -0
- data/app/views/federails/client/actors/show.html.erb +100 -0
- data/app/views/federails/client/actors/show.json.jbuilder +1 -0
- data/app/views/federails/client/followings/_follow.html.erb +4 -0
- data/app/views/federails/client/followings/_follower.html.erb +7 -0
- data/app/views/federails/client/followings/_following.json.jbuilder +1 -0
- data/app/views/federails/client/followings/_form.html.erb +21 -0
- data/app/views/federails/client/followings/index.html.erb +29 -0
- data/app/views/federails/client/followings/index.json.jbuilder +1 -0
- data/app/views/federails/client/followings/show.html.erb +21 -0
- data/app/views/federails/client/followings/show.json.jbuilder +1 -0
- data/app/views/federails/server/activities/_activity.activitypub.jbuilder +14 -0
- data/app/views/federails/server/activities/outbox.activitypub.jbuilder +18 -0
- data/app/views/federails/server/activities/show.activitypub.jbuilder +1 -0
- data/app/views/federails/server/actors/_actor.activitypub.jbuilder +21 -0
- data/app/views/federails/server/actors/followers.activitypub.jbuilder +18 -0
- data/app/views/federails/server/actors/following.activitypub.jbuilder +18 -0
- data/app/views/federails/server/actors/show.activitypub.jbuilder +1 -0
- data/app/views/federails/server/followings/_following.activitypub.jbuilder +7 -0
- data/app/views/federails/server/followings/show.activitypub.jbuilder +1 -0
- data/app/views/federails/server/nodeinfo/index.nodeinfo.jbuilder +6 -0
- data/app/views/federails/server/nodeinfo/show.nodeinfo.jbuilder +19 -0
- data/app/views/federails/server/web_finger/find.jrd.jbuilder +24 -0
- data/app/views/federails/server/web_finger/host_meta.xrd.erb +5 -0
- data/config/initializers/mime_types.rb +21 -0
- data/config/routes.rb +43 -0
- data/db/migrate/20200712133150_create_federails_actors.rb +24 -0
- data/db/migrate/20200712143127_create_federails_followings.rb +14 -0
- data/db/migrate/20200712174938_create_federails_activities.rb +11 -0
- data/db/migrate/20240731145400_change_actor_entity_rel_to_polymorphic.rb +11 -0
- data/db/migrate/20241002094500_add_uuids.rb +13 -0
- data/db/migrate/20241002094501_add_keypair_to_actors.rb +8 -0
- data/lib/federails/configuration.rb +92 -0
- data/lib/federails/engine.rb +6 -0
- data/lib/federails/utils/host.rb +54 -0
- data/lib/federails/version.rb +1 -1
- data/lib/federails.rb +34 -3
- data/lib/fediverse/inbox.rb +71 -0
- data/lib/fediverse/notifier.rb +60 -0
- data/lib/fediverse/request.rb +38 -0
- data/lib/fediverse/signature.rb +49 -0
- data/lib/fediverse/webfinger.rb +117 -0
- data/lib/generators/federails/install/USAGE +9 -0
- data/lib/generators/federails/install/install_generator.rb +10 -0
- data/lib/generators/federails/install/templates/federails.rb +1 -0
- data/lib/generators/federails/install/templates/federails.yml +23 -0
- data/lib/tasks/factory_bot.rake +15 -0
- metadata +170 -10
- data/app/views/layouts/federails/application.html.erb +0 -15
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Federails
|
|
2
|
+
module Utils
|
|
3
|
+
class Host
|
|
4
|
+
class << self
|
|
5
|
+
COMMON_PORTS = [80, 443].freeze
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# @return [String] Host and port of the current instance
|
|
9
|
+
def localhost
|
|
10
|
+
uri = URI.parse Federails.configuration.site_host
|
|
11
|
+
host_and_port (uri.host || 'localhost'), Federails.configuration.site_port
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Checks if the given URL points somewhere on current instance
|
|
16
|
+
#
|
|
17
|
+
# @param url [String] URL to check
|
|
18
|
+
#
|
|
19
|
+
# @return [true, false]
|
|
20
|
+
def local_url?(url)
|
|
21
|
+
uri = URI.parse url
|
|
22
|
+
host = host_and_port uri.host, uri.port
|
|
23
|
+
localhost == host
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Gets the route on the current instance, or nil
|
|
28
|
+
#
|
|
29
|
+
# @param url [String] URL to check
|
|
30
|
+
#
|
|
31
|
+
# @return [ActionDispatch::Routing::RouteSet, nil] nil when URL do not match a route
|
|
32
|
+
def local_route(url)
|
|
33
|
+
return nil unless local_url? url
|
|
34
|
+
|
|
35
|
+
Rails.application.routes.recognize_path(url)
|
|
36
|
+
rescue ActionController::RoutingError
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def host_and_port(host, port)
|
|
43
|
+
port_string = if port.present? && COMMON_PORTS.exclude?(port)
|
|
44
|
+
":#{port}"
|
|
45
|
+
else
|
|
46
|
+
''
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
"#{host}#{port_string}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/federails/version.rb
CHANGED
data/lib/federails.rb
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
1
|
+
require 'federails/version'
|
|
2
|
+
require 'federails/engine'
|
|
3
|
+
require 'federails/configuration'
|
|
3
4
|
|
|
5
|
+
# rubocop:disable Style/ClassVars
|
|
4
6
|
module Federails
|
|
5
|
-
|
|
7
|
+
mattr_reader :configuration
|
|
8
|
+
@@configuration = Configuration
|
|
9
|
+
|
|
10
|
+
# Make factories available
|
|
11
|
+
config.factory_bot.definition_file_paths += [File.expand_path('spec/factories', __dir__)] if defined?(FactoryBotRails)
|
|
12
|
+
|
|
13
|
+
def self.configure
|
|
14
|
+
yield @@configuration
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.config_from(name) # rubocop:disable Metrics/MethodLength
|
|
18
|
+
config = Rails.application.config_for name
|
|
19
|
+
[
|
|
20
|
+
:app_name,
|
|
21
|
+
:app_version,
|
|
22
|
+
:force_ssl,
|
|
23
|
+
:site_host,
|
|
24
|
+
:site_port,
|
|
25
|
+
:enable_discovery,
|
|
26
|
+
:app_layout,
|
|
27
|
+
:user_class, # @deprecated
|
|
28
|
+
:server_routes_path,
|
|
29
|
+
:client_routes_path,
|
|
30
|
+
:remote_follow_url_method,
|
|
31
|
+
:user_profile_url_method, # @deprecated
|
|
32
|
+
:user_name_field, # @deprecated
|
|
33
|
+
:user_username_field, # @deprecated
|
|
34
|
+
].each { |key| Configuration.send :"#{key}=", config[key] if config.key?(key) }
|
|
35
|
+
end
|
|
6
36
|
end
|
|
37
|
+
# rubocop:enable Style/ClassVars
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'fediverse/request'
|
|
2
|
+
|
|
3
|
+
module Fediverse
|
|
4
|
+
class Inbox
|
|
5
|
+
class << self
|
|
6
|
+
def dispatch_request(payload)
|
|
7
|
+
case payload['type']
|
|
8
|
+
when 'Create'
|
|
9
|
+
handle_create_request payload
|
|
10
|
+
when 'Follow'
|
|
11
|
+
handle_create_follow_request payload
|
|
12
|
+
when 'Accept'
|
|
13
|
+
handle_accept_request payload
|
|
14
|
+
when 'Undo'
|
|
15
|
+
handle_undo_request payload
|
|
16
|
+
else
|
|
17
|
+
# FIXME: Fails silently
|
|
18
|
+
# raise NotImplementedError
|
|
19
|
+
Rails.logger.debug { "Unhandled activity type: #{payload['type']}" }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def handle_create_request(payload)
|
|
26
|
+
activity = Request.get(payload['object'])
|
|
27
|
+
case activity['type']
|
|
28
|
+
when 'Follow'
|
|
29
|
+
handle_create_follow_request activity
|
|
30
|
+
when 'Note'
|
|
31
|
+
handle_create_note activity
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def handle_create_follow_request(activity)
|
|
36
|
+
actor = Federails::Actor.find_or_create_by_object activity['actor']
|
|
37
|
+
target_actor = Federails::Actor.find_or_create_by_object activity['object']
|
|
38
|
+
|
|
39
|
+
Federails::Following.create! actor: actor, target_actor: target_actor, federated_url: activity['id']
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def handle_create_note(activity)
|
|
43
|
+
actor = Federails::Actor.find_or_create_by_object activity['attributedTo']
|
|
44
|
+
Note.create! actor: actor, content: activity['content'], federated_url: activity['id']
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def handle_accept_request(payload)
|
|
48
|
+
activity = Request.get(payload['object'])
|
|
49
|
+
raise "Can't accept things that are not Follow" unless activity['type'] == 'Follow'
|
|
50
|
+
|
|
51
|
+
actor = Federails::Actor.find_or_create_by_object activity['actor']
|
|
52
|
+
target_actor = Federails::Actor.find_or_create_by_object activity['object']
|
|
53
|
+
raise 'Follow not accepted by target actor but by someone else' if payload['actor'] != target_actor.federated_url
|
|
54
|
+
|
|
55
|
+
follow = Federails::Following.find_by actor: actor, target_actor: target_actor
|
|
56
|
+
follow.accept!
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def handle_undo_request(payload)
|
|
60
|
+
activity = payload['object']
|
|
61
|
+
raise "Can't undo things that are not Follow" unless activity['type'] == 'Follow'
|
|
62
|
+
|
|
63
|
+
actor = Federails::Actor.find_or_create_by_object activity['actor']
|
|
64
|
+
target_actor = Federails::Actor.find_or_create_by_object activity['object']
|
|
65
|
+
|
|
66
|
+
follow = Federails::Following.find_by actor: actor, target_actor: target_actor
|
|
67
|
+
follow&.destroy
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'fediverse/signature'
|
|
2
|
+
|
|
3
|
+
module Fediverse
|
|
4
|
+
class Notifier
|
|
5
|
+
class << self
|
|
6
|
+
def post_to_inboxes(activity)
|
|
7
|
+
actors = activity.recipients
|
|
8
|
+
Rails.logger.debug('Nobody to notice') && return if actors.count.zero?
|
|
9
|
+
|
|
10
|
+
message = payload(activity)
|
|
11
|
+
actors.each do |recipient|
|
|
12
|
+
Rails.logger.debug { "Sending activity ##{activity.id} to #{recipient.inbox_url}" }
|
|
13
|
+
post_to_inbox(to: recipient, message: message, from: activity.actor)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def payload(activity)
|
|
20
|
+
Federails::ApplicationController.renderer.new.render(
|
|
21
|
+
template: 'federails/server/activities/show',
|
|
22
|
+
assigns: { activity: activity },
|
|
23
|
+
format: :json
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def post_to_inbox(to:, message:, from: nil)
|
|
28
|
+
conn = Faraday.default_connection
|
|
29
|
+
conn.builder.build_response(
|
|
30
|
+
conn,
|
|
31
|
+
signed_request(to: to, message: message, from: from)
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def signed_request(to:, message:, from:)
|
|
36
|
+
req = request(to: to, message: message)
|
|
37
|
+
req.headers['Signature'] = Fediverse::Signature.sign(sender: from, request: req) if from
|
|
38
|
+
req
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def request(to:, message:) # rubocop:todo Metrics/AbcSize
|
|
42
|
+
Faraday.default_connection.build_request(:post) do |req|
|
|
43
|
+
req.url to.inbox_url
|
|
44
|
+
req.body = message
|
|
45
|
+
req.headers['Content-Type'] = Mime[:activitypub].to_s
|
|
46
|
+
req.headers['Accept'] = Mime[:activitypub].to_s
|
|
47
|
+
req.headers['Host'] = URI.parse(to.inbox_url).host
|
|
48
|
+
req.headers['Date'] = Time.now.utc.httpdate
|
|
49
|
+
req.headers['Digest'] = digest(message)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def digest(message)
|
|
54
|
+
"SHA-256=#{Base64.strict_encode64(
|
|
55
|
+
OpenSSL::Digest.new('SHA256').digest(message)
|
|
56
|
+
)}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'json/ld'
|
|
2
|
+
|
|
3
|
+
module Fediverse
|
|
4
|
+
class Request
|
|
5
|
+
BASE_HEADERS = {
|
|
6
|
+
'Content-Type' => 'application/json',
|
|
7
|
+
'Accept' => 'application/json',
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
10
|
+
def initialize(id)
|
|
11
|
+
@id = id
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get
|
|
15
|
+
Rails.logger.debug { "GET #{@id}" }
|
|
16
|
+
@response = Faraday.get(@id, nil, BASE_HEADERS)
|
|
17
|
+
response_to_json
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def get(id)
|
|
22
|
+
new(id).get
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def response_to_json
|
|
29
|
+
begin
|
|
30
|
+
body = JSON.parse @response.body
|
|
31
|
+
rescue JSON::ParserError
|
|
32
|
+
return
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
JSON::LD::API.compact body, body['@context']
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Fediverse
|
|
2
|
+
class Signature
|
|
3
|
+
class << self
|
|
4
|
+
def sign(sender:, request:)
|
|
5
|
+
private_key = OpenSSL::PKey::RSA.new sender.private_key, Rails.application.credentials.secret_key_base
|
|
6
|
+
headers = '(request-target) host date digest'
|
|
7
|
+
sig = Base64.strict_encode64(
|
|
8
|
+
private_key.sign(
|
|
9
|
+
OpenSSL::Digest.new('SHA256'), signature_payload(request: request, headers: headers)
|
|
10
|
+
)
|
|
11
|
+
)
|
|
12
|
+
{
|
|
13
|
+
keyId: sender.key_id,
|
|
14
|
+
headers: headers,
|
|
15
|
+
signature: sig,
|
|
16
|
+
}.map { |k, v| "#{k}=\"#{v}\"" }.join(',')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def verify(sender:, request:)
|
|
20
|
+
raise 'Unsigned headers' unless request.headers['Signature']
|
|
21
|
+
|
|
22
|
+
signature_header = request.headers['Signature'].split(',').to_h do |pair|
|
|
23
|
+
/\A(?<key>[\w]+)="(?<value>.*)"\z/ =~ pair
|
|
24
|
+
[key, value]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
headers = signature_header['headers']
|
|
28
|
+
signature = Base64.decode64(signature_header['signature'])
|
|
29
|
+
key = OpenSSL::PKey::RSA.new(sender.public_key)
|
|
30
|
+
|
|
31
|
+
comparison_string = signature_payload(request: request, headers: headers)
|
|
32
|
+
|
|
33
|
+
key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison_string)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def signature_payload(request:, headers:)
|
|
39
|
+
headers.split.map do |signed_header_name|
|
|
40
|
+
if signed_header_name == '(request-target)'
|
|
41
|
+
"(request-target): #{request.http_method} #{URI.parse(request.path).path}"
|
|
42
|
+
else
|
|
43
|
+
"#{signed_header_name}: #{request.headers[signed_header_name.capitalize]}"
|
|
44
|
+
end
|
|
45
|
+
end.join("\n")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
require 'faraday/follow_redirects'
|
|
3
|
+
|
|
4
|
+
require 'federails/utils/host'
|
|
5
|
+
|
|
6
|
+
module Fediverse
|
|
7
|
+
class Webfinger
|
|
8
|
+
class << self
|
|
9
|
+
ACCOUNT_REGEX = /(?<username>[a-z0-9\-_.]+)(?:@(?<domain>.*))?/
|
|
10
|
+
|
|
11
|
+
def split_resource_account(account)
|
|
12
|
+
/\Aacct:#{ACCOUNT_REGEX}\z/io.match account
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def split_account(account)
|
|
16
|
+
/\A#{ACCOUNT_REGEX}\z/io.match account
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def local_user?(account)
|
|
20
|
+
account[:username] && (account[:domain].nil? || (account[:domain] == Federails::Utils::Host.localhost))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fetch_actor(username, domain)
|
|
24
|
+
fetch_actor_url webfinger(username, domain)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def fetch_actor_url(url)
|
|
28
|
+
webfinger_to_actor get_json url
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns actor id
|
|
32
|
+
def webfinger(username, domain)
|
|
33
|
+
json = webfinger_response(username, domain)
|
|
34
|
+
link = json['links'].find { |l| l['type'] == 'application/activity+json' }
|
|
35
|
+
|
|
36
|
+
link['href'] if link
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns remote follow link template, or complete link if actor_url is provided
|
|
40
|
+
def remote_follow_url(username, domain, actor_url: nil)
|
|
41
|
+
json = webfinger_response(username, domain)
|
|
42
|
+
link = json['links'].find { |l| l['rel'] == 'http://ostatus.org/schema/1.0/subscribe' }
|
|
43
|
+
return nil if link&.dig('template').nil?
|
|
44
|
+
|
|
45
|
+
if actor_url
|
|
46
|
+
link['template'].gsub('{uri}', CGI.escape(actor_url))
|
|
47
|
+
else
|
|
48
|
+
link['template']
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def webfinger_response(username, domain)
|
|
55
|
+
scheme = Federails.configuration.force_ssl ? 'https' : 'http'
|
|
56
|
+
get_json "#{scheme}://#{domain}/.well-known/webfinger", resource: "acct:#{username}@#{domain}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def server_and_port(id)
|
|
60
|
+
uri = URI.parse id
|
|
61
|
+
if uri.port && [80, 443].exclude?(uri.port)
|
|
62
|
+
"#{uri.host}:#{uri.port}"
|
|
63
|
+
else
|
|
64
|
+
uri.host
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def webfinger_to_actor(data)
|
|
69
|
+
Federails::Actor.new federated_url: data['id'],
|
|
70
|
+
username: data['preferredUsername'],
|
|
71
|
+
name: data['name'],
|
|
72
|
+
server: server_and_port(data['id']),
|
|
73
|
+
inbox_url: data['inbox'],
|
|
74
|
+
outbox_url: data['outbox'],
|
|
75
|
+
followers_url: data['followers'],
|
|
76
|
+
followings_url: data['following'],
|
|
77
|
+
profile_url: data['url'],
|
|
78
|
+
public_key: data.dig('publicKey', 'publicKeyPem')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def get_json(url, payload = {})
|
|
82
|
+
response = get(url, payload: payload, headers: { accept: 'application/json' })
|
|
83
|
+
|
|
84
|
+
if response.status != 200
|
|
85
|
+
Rails.logger.debug { "Unhandled status code #{response.status} for GET #{url}" }
|
|
86
|
+
raise ActiveRecord::RecordNotFound
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
JSON.parse(response.body)
|
|
90
|
+
rescue JSON::ParserError
|
|
91
|
+
Rails.logger.debug { "Invalid JSON response GET #{url}" }
|
|
92
|
+
|
|
93
|
+
raise ActiveRecord::RecordNotFound
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Only perform a GET request and throws an ActiveRecord::RecordNotFound
|
|
97
|
+
# on error.
|
|
98
|
+
# That's "ok-ish"; when an actor is unavailable, whatever the reason is, it's
|
|
99
|
+
# not found...
|
|
100
|
+
def get(url, payload: {}, headers: {})
|
|
101
|
+
connection = Faraday.new url: url, params: payload, headers: headers do |faraday|
|
|
102
|
+
faraday.response :follow_redirects # use Faraday::FollowRedirects::Middleware
|
|
103
|
+
faraday.adapter Faraday.default_adapter
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
begin
|
|
107
|
+
response = connection.get
|
|
108
|
+
rescue Faraday::ConnectionFailed
|
|
109
|
+
Rails.logger.debug { "Failed to reach server for GET #{url}" }
|
|
110
|
+
raise ActiveRecord::RecordNotFound
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
response
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Federails
|
|
2
|
+
class InstallGenerator < Rails::Generators::Base
|
|
3
|
+
source_root File.expand_path('templates', __dir__)
|
|
4
|
+
|
|
5
|
+
def copy_files
|
|
6
|
+
copy_file 'federails.yml', 'config/federails.yml'
|
|
7
|
+
copy_file 'federails.rb', 'config/initializers/federails.rb'
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Federails.config_from 'federails'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
defaults: &defaults
|
|
3
|
+
app_name:
|
|
4
|
+
app_version:
|
|
5
|
+
force_ssl: false
|
|
6
|
+
site_host: http://localhost
|
|
7
|
+
site_port: 3000
|
|
8
|
+
enable_discovery: true
|
|
9
|
+
app_layout: 'layouts/application'
|
|
10
|
+
server_routes_path: federation
|
|
11
|
+
client_routes_path: app
|
|
12
|
+
|
|
13
|
+
development:
|
|
14
|
+
<<: *defaults
|
|
15
|
+
|
|
16
|
+
test:
|
|
17
|
+
<<: *defaults
|
|
18
|
+
site_port: null
|
|
19
|
+
|
|
20
|
+
production:
|
|
21
|
+
<<: *defaults
|
|
22
|
+
force_ssl: true
|
|
23
|
+
site_port: 443
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
namespace :factory_bot do
|
|
2
|
+
desc 'Verify that all FactoryBot factories are valid'
|
|
3
|
+
task lint: :environment do
|
|
4
|
+
if Rails.env.test?
|
|
5
|
+
conn = ActiveRecord::Base.connection
|
|
6
|
+
conn.transaction do
|
|
7
|
+
FactoryBot.lint traits: true
|
|
8
|
+
raise ActiveRecord::Rollback
|
|
9
|
+
end
|
|
10
|
+
else
|
|
11
|
+
system("bundle exec rake app:factory_bot:lint RAILS_ENV='test'")
|
|
12
|
+
raise if $CHILD_STATUS.exitstatus.nonzero?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|