federails 0.0.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|