fedipub 0.0.1 → 0.10.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.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +76 -2
  3. data/Rakefile +5 -9
  4. data/app/assets/config/fedipub_manifest.js +1 -0
  5. data/app/assets/stylesheets/fedipub/application.css +15 -0
  6. data/app/controllers/concerns/fedipub/server/render_collections.rb +19 -0
  7. data/app/controllers/fedipub/client/activities_controller.rb +27 -0
  8. data/app/controllers/fedipub/client/actors_controller.rb +54 -0
  9. data/app/controllers/fedipub/client/followings_controller.rb +103 -0
  10. data/app/controllers/fedipub/client_controller.rb +9 -0
  11. data/app/controllers/fedipub/server/activities_controller.rb +78 -0
  12. data/app/controllers/fedipub/server/actors_controller.rb +50 -0
  13. data/app/controllers/fedipub/server/followings_controller.rb +19 -0
  14. data/app/controllers/fedipub/server/nodeinfo_controller.rb +28 -0
  15. data/app/controllers/fedipub/server/published_controller.rb +30 -0
  16. data/app/controllers/fedipub/server/web_finger_controller.rb +44 -0
  17. data/app/controllers/fedipub/server_controller.rb +48 -0
  18. data/app/helpers/fedipub/server_helper.rb +18 -0
  19. data/app/jobs/fedipub/application_job.rb +10 -0
  20. data/app/jobs/fedipub/fetch_nodeinfo_job.rb +10 -0
  21. data/app/jobs/fedipub/notify_inbox_job.rb +10 -0
  22. data/app/mailers/fedipub/application_mailer.rb +6 -0
  23. data/app/models/concerns/fedipub/actor_entity.rb +165 -0
  24. data/app/models/concerns/fedipub/announceable.rb +18 -0
  25. data/app/models/concerns/fedipub/application_actor.rb +25 -0
  26. data/app/models/concerns/fedipub/data_entity.rb +278 -0
  27. data/app/models/concerns/fedipub/handles_delete_requests.rb +36 -0
  28. data/app/models/concerns/fedipub/has_uuid.rb +61 -0
  29. data/app/models/concerns/fedipub/likeable.rb +29 -0
  30. data/app/models/fedipub/activity.rb +62 -0
  31. data/app/models/fedipub/actor.rb +306 -0
  32. data/app/models/fedipub/application_record.rb +5 -0
  33. data/app/models/fedipub/following.rb +72 -0
  34. data/app/models/fedipub/host.rb +48 -0
  35. data/app/policies/fedipub/client/activity_policy.rb +9 -0
  36. data/app/policies/fedipub/client/actor_policy.rb +15 -0
  37. data/app/policies/fedipub/client/following_policy.rb +35 -0
  38. data/app/policies/fedipub/fedipub_policy.rb +63 -0
  39. data/app/policies/fedipub/server/activity_policy.rb +9 -0
  40. data/app/policies/fedipub/server/actor_policy.rb +23 -0
  41. data/app/policies/fedipub/server/following_policy.rb +6 -0
  42. data/app/policies/fedipub/server/publishable_policy.rb +15 -0
  43. data/app/views/fedipub/client/activities/_activity.html.erb +5 -0
  44. data/app/views/fedipub/client/activities/_activity.json.jbuilder +1 -0
  45. data/app/views/fedipub/client/activities/_index.json.jbuilder +1 -0
  46. data/app/views/fedipub/client/activities/feed.html.erb +11 -0
  47. data/app/views/fedipub/client/activities/feed.json.jbuilder +1 -0
  48. data/app/views/fedipub/client/activities/index.html.erb +5 -0
  49. data/app/views/fedipub/client/activities/index.json.jbuilder +1 -0
  50. data/app/views/fedipub/client/actors/_actor.json.jbuilder +17 -0
  51. data/app/views/fedipub/client/actors/_lookup_form.html.erb +5 -0
  52. data/app/views/fedipub/client/actors/gone.html.erb +1 -0
  53. data/app/views/fedipub/client/actors/index.html.erb +44 -0
  54. data/app/views/fedipub/client/actors/index.json.jbuilder +1 -0
  55. data/app/views/fedipub/client/actors/show.html.erb +112 -0
  56. data/app/views/fedipub/client/actors/show.json.jbuilder +1 -0
  57. data/app/views/fedipub/client/common/_client_links.html.erb +26 -0
  58. data/app/views/fedipub/client/followings/_follow.html.erb +4 -0
  59. data/app/views/fedipub/client/followings/_follow_actions.html.erb +36 -0
  60. data/app/views/fedipub/client/followings/_follower.html.erb +7 -0
  61. data/app/views/fedipub/client/followings/_following.json.jbuilder +1 -0
  62. data/app/views/fedipub/client/followings/_form.html.erb +22 -0
  63. data/app/views/fedipub/client/followings/index.html.erb +29 -0
  64. data/app/views/fedipub/client/followings/index.json.jbuilder +1 -0
  65. data/app/views/fedipub/client/followings/show.html.erb +21 -0
  66. data/app/views/fedipub/client/followings/show.json.jbuilder +1 -0
  67. data/app/views/fedipub/server/activities/_activity.activitypub.jbuilder +25 -0
  68. data/app/views/fedipub/server/activities/show.activitypub.jbuilder +1 -0
  69. data/app/views/fedipub/server/actors/_actor.activitypub.jbuilder +41 -0
  70. data/app/views/fedipub/server/actors/_tombstone.activitypub.jbuilder +6 -0
  71. data/app/views/fedipub/server/actors/show.activitypub.jbuilder +5 -0
  72. data/app/views/fedipub/server/followings/_following.activitypub.jbuilder +7 -0
  73. data/app/views/fedipub/server/followings/show.activitypub.jbuilder +1 -0
  74. data/app/views/fedipub/server/nodeinfo/index.nodeinfo.jbuilder +10 -0
  75. data/app/views/fedipub/server/nodeinfo/show.nodeinfo.jbuilder +19 -0
  76. data/app/views/fedipub/server/published/_publishable.activitypub.jbuilder +11 -0
  77. data/app/views/fedipub/server/published/_tombstone.activitypub.jbuilder +6 -0
  78. data/app/views/fedipub/server/published/show.activitypub.jbuilder +5 -0
  79. data/app/views/fedipub/server/shared/ordered_collection.activitypub.jbuilder +6 -0
  80. data/app/views/fedipub/server/shared/ordered_collection_page.activitypub.jbuilder +12 -0
  81. data/app/views/fedipub/server/web_finger/find.jrd.jbuilder +32 -0
  82. data/app/views/fedipub/server/web_finger/host_meta.xrd.erb +5 -0
  83. data/app/views/users/show.html.erb +4 -0
  84. data/config/initializers/mime_types.rb +21 -0
  85. data/config/initializers/security_vocab.rb +53 -0
  86. data/config/routes.rb +53 -0
  87. data/db/migrate/20200712133150_create_federails_actors.rb +22 -0
  88. data/db/migrate/20200712143127_create_federails_followings.rb +14 -0
  89. data/db/migrate/20200712174938_create_federails_activities.rb +11 -0
  90. data/db/migrate/20241002094500_add_uuids.rb +13 -0
  91. data/db/migrate/20241002094501_add_keypair_to_actors.rb +8 -0
  92. data/db/migrate/20250122160618_add_extensions_to_federails_actors.rb +5 -0
  93. data/db/migrate/20250301082500_add_local_to_actors.rb +11 -0
  94. data/db/migrate/20250329123939_add_actor_type_to_actors.rb +5 -0
  95. data/db/migrate/20250329123940_add_tombstoned_at_to_actors.rb +5 -0
  96. data/db/migrate/20250426061729_create_federails_hosts.rb +22 -0
  97. data/db/migrate/20251121160720_add_to_and_cc_to_federails_activities.rb +6 -0
  98. data/db/migrate/20260421170640_add_result_and_instrument_to_federails_activities.rb +6 -0
  99. data/db/migrate/20260723113716_rename_federails_to_fedipub.rb +15 -0
  100. data/lib/fedipub/configuration.rb +119 -0
  101. data/lib/fedipub/data_transformer/note.rb +36 -0
  102. data/lib/fedipub/engine.rb +11 -0
  103. data/lib/fedipub/maintenance/actors_updater.rb +66 -0
  104. data/lib/fedipub/maintenance/hosts_updater.rb +19 -0
  105. data/lib/fedipub/utils/actor.rb +85 -0
  106. data/lib/fedipub/utils/context.rb +12 -0
  107. data/lib/fedipub/utils/host.rb +54 -0
  108. data/lib/fedipub/utils/json_request.rb +89 -0
  109. data/lib/fedipub/utils/object.rb +131 -0
  110. data/lib/fedipub/utils/response_codes.rb +11 -0
  111. data/lib/fedipub/version.rb +1 -3
  112. data/lib/fedipub.rb +127 -4
  113. data/lib/fediverse/collection.rb +31 -0
  114. data/lib/fediverse/inbox.rb +110 -0
  115. data/lib/fediverse/node_info.rb +38 -0
  116. data/lib/fediverse/notifier.rb +63 -0
  117. data/lib/fediverse/request.rb +29 -0
  118. data/lib/fediverse/signature/draft_cavage12.rb +109 -0
  119. data/lib/fediverse/signature/rfc9421.rb +69 -0
  120. data/lib/fediverse/signature.rb +26 -0
  121. data/lib/fediverse/webfinger.rb +140 -0
  122. data/lib/fediverse.rb +5 -0
  123. data/lib/generators/fedipub/copy_client_policies/USAGE +8 -0
  124. data/lib/generators/fedipub/copy_client_policies/copy_client_policies_generator.rb +9 -0
  125. data/lib/generators/fedipub/copy_client_views/USAGE +8 -0
  126. data/lib/generators/fedipub/copy_client_views/copy_client_views_generator.rb +9 -0
  127. data/lib/generators/fedipub/copy_factories/USAGE +8 -0
  128. data/lib/generators/fedipub/copy_factories/copy_factories_generator.rb +43 -0
  129. data/lib/generators/fedipub/install/USAGE +9 -0
  130. data/lib/generators/fedipub/install/install_generator.rb +10 -0
  131. data/lib/generators/fedipub/install/templates/fedipub.rb +1 -0
  132. data/lib/generators/fedipub/install/templates/fedipub.yml +27 -0
  133. data/lib/tasks/factory_bot.rake +15 -0
  134. data/lib/tasks/fedipub_tasks.rake +13 -0
  135. metadata +276 -10
@@ -0,0 +1,29 @@
1
+ require 'json/ld'
2
+
3
+ module Fediverse
4
+ class Request
5
+ class << self
6
+ # Dereferences a value
7
+ #
8
+ # @param value [String, Hash]
9
+ #
10
+ # @return [Hash, nil]
11
+ def dereference(value)
12
+ return value if value.is_a? Hash
13
+ return get(value) if value.is_a? String
14
+
15
+ raise "Unhandled object type #{value.class}"
16
+ end
17
+
18
+ private
19
+
20
+ def get(id)
21
+ json = Fedipub::Utils::JsonRequest.get_json(id)
22
+
23
+ JSON::LD::API.compact json, json['@context']
24
+ rescue JSON::ParserError
25
+ nil
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,109 @@
1
+ require 'fediverse/signature'
2
+
3
+ module Fediverse
4
+ module Signature
5
+ class DraftCavage12
6
+ class << self
7
+ def sign(sender:, request:)
8
+ request = set_headers(request)
9
+
10
+ parts = {
11
+ keyId: sender.key_id,
12
+ headers: signature_headers(request).join(' '),
13
+ signature: signature(sender: sender, request: request),
14
+ }
15
+ request.headers['Signature'] = parts.map { |k, v| "#{k}=\"#{v}\"" }.join(',')
16
+ request
17
+ end
18
+
19
+ def verify!(request:)
20
+ # Do we have a signature to verify?
21
+ return false unless request.headers.key?('Signature')
22
+
23
+ # Have we got what we need?
24
+ components = signature_components(request)
25
+ raise Fediverse::Signature::BadSignature, 'Malformed signature' unless components['signature'] && components['headers']
26
+
27
+ # Find the sender
28
+ sender = find_sender_by_key_id(components['keyId'])
29
+ raise Fediverse::Signature::BadSignature, "Couldn't find sender" unless sender
30
+
31
+ # Build the expected payload
32
+ comparison_string = signature_payload(request: request, headers: components['headers'])
33
+
34
+ # Verify the payload against the signature
35
+ result = do_verification(components['signature'], sender, comparison_string)
36
+ raise Fediverse::Signature::BadSignature unless result
37
+
38
+ result
39
+ end
40
+
41
+ private
42
+
43
+ def do_verification(signature, sender, comparison_string)
44
+ signature = Base64.decode64(signature)
45
+ key = OpenSSL::PKey::RSA.new(sender.public_key)
46
+ key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison_string)
47
+ end
48
+
49
+ def find_sender_by_key_id(key_id)
50
+ return unless key_id
51
+
52
+ Fedipub::Actor.find_or_create_by_federation_url(
53
+ key_id.split('#', 2).first
54
+ )
55
+ end
56
+
57
+ def set_headers(request) # rubocop:disable Naming/AccessorMethodName
58
+ request.headers['Digest'] = digest(request.body) if request.body
59
+ request.headers['Host'] = URI.parse(request.path).host
60
+ request.headers['Date'] = Time.now.utc.httpdate
61
+ request
62
+ end
63
+
64
+ def signature_components(request)
65
+ request.headers['Signature'].split(',').to_h do |pair|
66
+ /\A(?<key>\w+)="(?<value>.*)"\z/ =~ pair
67
+ [key, value]
68
+ end
69
+ end
70
+
71
+ def digest(message)
72
+ "SHA-256=#{Base64.strict_encode64(
73
+ OpenSSL::Digest.new('SHA256').digest(message)
74
+ )}"
75
+ end
76
+
77
+ def signature_payload(request:, headers: signature_headers(request))
78
+ headers = headers.split if headers.is_a?(String)
79
+ headers.map do |header|
80
+ case header
81
+ when '(request-target)'
82
+ "(request-target): #{(request.try(:http_method) || request.try(:method)).downcase} #{URI.parse(request.path).path}"
83
+ else
84
+ "#{header}: #{request.headers[header.capitalize]}"
85
+ end
86
+ end.join("\n")
87
+ end
88
+
89
+ def signature_headers(request)
90
+ if request.body
91
+ %w[(request-target) host date digest]
92
+ else
93
+ %w[(request-target) host date]
94
+ end
95
+ end
96
+
97
+ def signature(sender:, request:)
98
+ private_key = OpenSSL::PKey::RSA.new sender.private_key, Rails.application.credentials.secret_key_base
99
+ Base64.strict_encode64(
100
+ private_key.sign(
101
+ OpenSSL::Digest.new('SHA256'),
102
+ signature_payload(request: request)
103
+ )
104
+ )
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,69 @@
1
+ require 'linzer'
2
+ require 'linzer/faraday'
3
+ require 'linzer/rack'
4
+
5
+ Linzer::Message.register_adapter ActionDispatch::Request, Linzer::Message::Adapter::Rack::Request
6
+
7
+ module Fediverse
8
+ module Signature
9
+ class Rfc9421
10
+ class << self
11
+ def sign(sender:, request:)
12
+ request.headers['Content-Digest'] = digest(request.body) if request.body
13
+ Linzer.sign!(
14
+ request,
15
+ key: linzer_private_key(sender),
16
+ components: components(request),
17
+ params: {
18
+ created: Time.now.to_i,
19
+ alg: 'rsa-v1_5-sha256',
20
+ }
21
+ )
22
+ request
23
+ end
24
+
25
+ def verify!(request:)
26
+ # Do we have a signature to verify?
27
+ return false if !request.headers.key?('Signature-Input') || !request.headers.key?('Signature')
28
+
29
+ # Verify the signature
30
+ Linzer.verify!(request) do |key_id|
31
+ sender = Fedipub::Actor.find_or_create_by_federation_url(key_id.split('#', 2).first)
32
+ raise Fediverse::Signature::BadSignature if sender.nil?
33
+
34
+ linzer_public_key(sender)
35
+ end
36
+ rescue Linzer::Error
37
+ raise Fediverse::Signature::BadSignature
38
+ end
39
+
40
+ private
41
+
42
+ # Converts key to right structure for Linzer to use
43
+ def linzer_private_key(sender)
44
+ private_key = OpenSSL::PKey::RSA.new sender.private_key, Rails.application.credentials.secret_key_base
45
+ Linzer.new_rsa_v1_5_sha256_key(private_key.to_pem, sender.key_id)
46
+ end
47
+
48
+ def linzer_public_key(sender)
49
+ public_key = OpenSSL::PKey::RSA.new(sender.public_key)
50
+ Linzer.new_rsa_v1_5_sha256_key(public_key.to_pem, sender.key_id)
51
+ end
52
+
53
+ def components(request)
54
+ if request.body
55
+ %w[@method @target-uri content-digest]
56
+ else
57
+ %w[@method @target-uri]
58
+ end
59
+ end
60
+
61
+ def digest(message)
62
+ "sha-256=:#{Base64.strict_encode64(
63
+ OpenSSL::Digest.new('SHA256').digest(message)
64
+ )}:"
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,26 @@
1
+ module Fediverse
2
+ module Signature
3
+ class BadSignature < StandardError; end
4
+
5
+ class << self
6
+ def sign(sender:, request:, legacy_signature: false)
7
+ if legacy_signature
8
+ Fediverse::Signature::DraftCavage12.sign(sender: sender, request: request)
9
+ else
10
+ Fediverse::Signature::Rfc9421.sign(sender: sender, request: request)
11
+ end
12
+ end
13
+
14
+ def verify!(request:, require_signature: false) # rubocop:disable Naming/PredicateMethod
15
+ success = Fediverse::Signature::Rfc9421.verify!(request: request) ||
16
+ Fediverse::Signature::DraftCavage12.verify!(request: request)
17
+ raise(BadSignature) if require_signature && !success
18
+
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ require 'fediverse/signature/draft_cavage12'
26
+ require 'fediverse/signature/rfc9421'
@@ -0,0 +1,140 @@
1
+ require 'fedipub/utils/host'
2
+ require 'fedipub/utils/json_request'
3
+
4
+ module Fediverse
5
+ # Methods related to Webfinger: find accounts, fetch actors,...
6
+ class Webfinger
7
+ class << self
8
+ ACCOUNT_REGEX = /(?<username>[a-z0-9\-_.]+)(?:@(?<domain>.*))?/
9
+
10
+ # Extracts username and domain from an account string.
11
+ # Accepts forms "user@domain", "@user@domain" and "acct:user@domain"
12
+ #
13
+ # @param account [String] Account string
14
+ #
15
+ # @return [MatchData, nil] Matches with +:username+ and +:domain+ or +nil+
16
+ def split_account(account)
17
+ /\A(acct:|@)?#{ACCOUNT_REGEX}\z/io.match account
18
+ end
19
+
20
+ # Determines if a given account string should be a local account (same host as configured one)
21
+ #
22
+ # @param hash [Hash, MatchData] Object with +:username+ and +:domain+ keys
23
+ #
24
+ # @return [Boolean]
25
+ def local_user?(hash)
26
+ hash[:username] && (hash[:domain].nil? || (hash[:domain] == Fedipub::Utils::Host.localhost))
27
+ end
28
+
29
+ # Fetches a distant actor
30
+ #
31
+ # @param username [String]
32
+ # @param domain [String]
33
+ #
34
+ # @return [Fedipub::Actor, nil] Fedipub actor or nothing when not found
35
+ def fetch_actor(username, domain)
36
+ fetch_actor_url webfinger(username, domain)
37
+ end
38
+
39
+ # Fetches an actor given its URL
40
+ #
41
+ # @param url [String] Actor's federation URL
42
+ #
43
+ # @return [Fedipub::Actor, nil] Fedipub actor or nothing when not found
44
+ def fetch_actor_url(url)
45
+ webfinger_to_actor get_json url
46
+ end
47
+
48
+ # Gets the real actor's federation URL from its username and domain
49
+ #
50
+ # @param username [String]
51
+ # @param domain [String]
52
+ #
53
+ # @return [String, nil] Federation URL if found
54
+ def webfinger(username, domain)
55
+ json = webfinger_response(username, domain)
56
+ link = json['links'].find { |l| Mime::Type.lookup(l['type']).to_sym == :activitypub }
57
+
58
+ link['href'] if link
59
+ end
60
+
61
+ # Returns remote follow link template, or complete link if actor_url is provided
62
+ #
63
+ # @param username [String]
64
+ # @param domain [String]
65
+ # @param actor_url [String] Optional Federation URL to provide when known
66
+ #
67
+ # @return [String] The URL to use as follow URL
68
+ def remote_follow_url(username, domain, actor_url: nil)
69
+ json = webfinger_response(username, domain)
70
+ link = json['links'].find { |l| l['rel'] == 'http://ostatus.org/schema/1.0/subscribe' }
71
+ return nil if link&.dig('template').nil?
72
+
73
+ if actor_url
74
+ link['template'].gsub('{uri}', CGI.escape(actor_url))
75
+ else
76
+ link['template']
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ # Makes a webfinger request for a given username/domain
83
+ # @return [Hash] Webfinger response's content
84
+ def webfinger_response(username, domain)
85
+ scheme = Fedipub.configuration.force_ssl ? 'https' : 'http'
86
+ get_json "#{scheme}://#{domain}/.well-known/webfinger", resource: "acct:#{username}@#{domain}"
87
+ end
88
+
89
+ # Extracts the server and port from a string, omitting common ports
90
+ # @return [String] Server and port
91
+ def server_and_port(string)
92
+ uri = URI.parse string
93
+ if uri.port && [80, 443].exclude?(uri.port)
94
+ "#{uri.host}:#{uri.port}"
95
+ else
96
+ uri.host
97
+ end
98
+ end
99
+
100
+ # Builds a +Fedipub::Actor+ from a Webfinger response
101
+ # @param data [Hash] Webfinger response
102
+ # @return [Fedipub::Actor]
103
+ def webfinger_to_actor(data) # rubocop:disable Metrics/MethodLength
104
+ data = data.clone
105
+ id = data.delete('id')
106
+ Fedipub::Actor.new federated_url: id,
107
+ username: data.delete('preferredUsername'),
108
+ actor_type: data.delete('type'),
109
+ name: data.delete('name'),
110
+ server: server_and_port(id),
111
+ inbox_url: data.delete('inbox'),
112
+ outbox_url: data.delete('outbox'),
113
+ followers_url: data.delete('followers'),
114
+ followings_url: data.delete('following'),
115
+ profile_url: data.delete('url'),
116
+ public_key: data.delete('publicKey')&.dig('publicKeyPem'),
117
+ extensions: data.except('@context')
118
+ end
119
+
120
+ # Makes a simple GET request and returns a +Hash+ from the parsed body
121
+ # @return [Hash]
122
+ # @raise [ActiveRecord::RecordNotFound] when the response is invalid
123
+ def get_json(url, params = {})
124
+ Fedipub::Utils::JsonRequest.get_json(url, params: params, headers: { accept: 'application/json' })
125
+ rescue Fedipub::Utils::JsonRequest::UnhandledResponseStatus => e
126
+ Rails.logger.debug { e.message }
127
+
128
+ raise ActiveRecord::RecordNotFound
129
+ rescue Faraday::ConnectionFailed
130
+ Rails.logger.debug { "Failed to reach server for GET #{url}" }
131
+
132
+ raise ActiveRecord::RecordNotFound
133
+ rescue JSON::ParserError
134
+ Rails.logger.debug { "Invalid JSON response for GET #{url}" }
135
+
136
+ raise ActiveRecord::RecordNotFound
137
+ end
138
+ end
139
+ end
140
+ end
data/lib/fediverse.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'fediverse/collection'
2
+
3
+ # This module includes classes and helpers to interact with the Fediverse.
4
+ module Fediverse
5
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Copies the Pundit client policies to be overridden in main application.
3
+
4
+ Example:
5
+ bin/rails generate copy_client_policies
6
+
7
+ This will create:
8
+ app/policies/fedipub/client/*
@@ -0,0 +1,9 @@
1
+ module Fedipub
2
+ class CopyClientPoliciesGenerator < Rails::Generators::Base
3
+ source_root File.expand_path('../../../../app/policies/fedipub', __dir__)
4
+
5
+ def copy_policies
6
+ directory 'client', Rails.root.join('app', 'policies', 'fedipub', 'client')
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Copies the client views to be overridden in main application
3
+
4
+ Example:
5
+ bin/rails generate copy_client_views
6
+
7
+ This will create:
8
+ app/views/fedipub/client/**/*
@@ -0,0 +1,9 @@
1
+ module Fedipub
2
+ class CopyClientViewsGenerator < Rails::Generators::Base
3
+ source_root File.expand_path('../../../../app/views', __dir__)
4
+
5
+ def copy_views
6
+ directory 'fedipub/client', Rails.root.join('app', 'views', 'fedipub', 'client')
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Copies FactoryBot factories in the "spec" directory
3
+
4
+ Example:
5
+ bin/rails generate copy_factories
6
+
7
+ This will create:
8
+ spec/factories/*
@@ -0,0 +1,43 @@
1
+ module Fedipub
2
+ class CopyFactoriesGenerator < Rails::Generators::Base
3
+ SOURCE_DIRECTORY = File.expand_path('../../../../spec/factories/fedipub', __dir__)
4
+ FACTORY_DEFINITION_REGEX = /(FactoryBot.define do\n\s+factory) :(\w+),/
5
+
6
+ source_root SOURCE_DIRECTORY
7
+
8
+ def initialize
9
+ super
10
+ @files = []
11
+ @factories = []
12
+ end
13
+
14
+ def copy_factories
15
+ dest = Rails.root.join('spec', 'factories')
16
+
17
+ Dir.entries(SOURCE_DIRECTORY).each do |node|
18
+ source_path = File.join(SOURCE_DIRECTORY, node)
19
+ next unless File.file?(source_path) && node.match?(/\.rb\Z/)
20
+
21
+ file_path = File.join(dest, "fedipub_#{node}")
22
+ copy_file node, file_path
23
+ @files << file_path
24
+ @factories << File.read(file_path).match(FACTORY_DEFINITION_REGEX)&.[](2)
25
+ end
26
+
27
+ substitute_values!
28
+ end
29
+
30
+ private
31
+
32
+ def substitute_values!
33
+ @factories.compact!
34
+ @files.each do |file|
35
+ gsub_file file, FACTORY_DEFINITION_REGEX, '\1 :fedipub_\2,'
36
+
37
+ @factories.each do |factory|
38
+ gsub_file file, ":#{factory}", ":fedipub_#{factory}"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Copies configuration file and initializer
3
+
4
+ Example:
5
+ bin/rails generate fedipub:install
6
+
7
+ This will create:
8
+ config/fedipub.yml
9
+ config/initializers/fedipub.rb
@@ -0,0 +1,10 @@
1
+ module Fedipub
2
+ class InstallGenerator < Rails::Generators::Base
3
+ source_root File.expand_path('templates', __dir__)
4
+
5
+ def copy_files
6
+ copy_file 'fedipub.yml', Rails.root.join('config', 'fedipub.yml')
7
+ copy_file 'fedipub.rb', Rails.root.join('config', 'initializers', 'fedipub.rb')
8
+ end
9
+ end
10
+ end
@@ -0,0 +1 @@
1
+ Fedipub.config_from 'fedipub'
@@ -0,0 +1,27 @@
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
+ open_registrations: false
10
+ app_layout: 'layouts/application'
11
+ server_routes_path: federation
12
+ client_routes_path: app
13
+ job_queue: default
14
+ #remote_entities_cache_duration: 86400 # 1 day in seconds
15
+ #base_client_controller: ::ActionController::Base
16
+
17
+ development:
18
+ <<: *defaults
19
+
20
+ test:
21
+ <<: *defaults
22
+ site_port: null
23
+
24
+ production:
25
+ <<: *defaults
26
+ force_ssl: true
27
+ 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
@@ -0,0 +1,13 @@
1
+ namespace :fedipub do
2
+ desc 'Re-fetches every remote actors to update database'
3
+ task sync_actors: :environment do
4
+ Fedipub::Maintenance::ActorUpdater.run do |actor, status|
5
+ puts "#{actor.federated_url}: #{status}"
6
+ end
7
+ end
8
+
9
+ desc 'Re-fetches every host and completes missing ones'
10
+ task sync_hosts: :environment do
11
+ Fedipub::Maintenance::HostsUpdater.run
12
+ end
13
+ end