fedipub 0.0.1 → 0.9.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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +70 -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 +45 -0
  17. data/app/controllers/fedipub/server_controller.rb +37 -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 +166 -0
  24. data/app/models/concerns/fedipub/announceable.rb +18 -0
  25. data/app/models/concerns/fedipub/data_entity.rb +279 -0
  26. data/app/models/concerns/fedipub/handles_delete_requests.rb +36 -0
  27. data/app/models/concerns/fedipub/has_uuid.rb +61 -0
  28. data/app/models/concerns/fedipub/likeable.rb +29 -0
  29. data/app/models/fedipub/activity.rb +62 -0
  30. data/app/models/fedipub/actor.rb +303 -0
  31. data/app/models/fedipub/application_record.rb +5 -0
  32. data/app/models/fedipub/following.rb +72 -0
  33. data/app/models/fedipub/host.rb +48 -0
  34. data/app/policies/fedipub/client/activity_policy.rb +9 -0
  35. data/app/policies/fedipub/client/actor_policy.rb +15 -0
  36. data/app/policies/fedipub/client/following_policy.rb +35 -0
  37. data/app/policies/fedipub/fedipub_policy.rb +63 -0
  38. data/app/policies/fedipub/server/activity_policy.rb +9 -0
  39. data/app/policies/fedipub/server/actor_policy.rb +23 -0
  40. data/app/policies/fedipub/server/following_policy.rb +6 -0
  41. data/app/policies/fedipub/server/publishable_policy.rb +15 -0
  42. data/app/views/fedipub/client/activities/_activity.html.erb +5 -0
  43. data/app/views/fedipub/client/activities/_activity.json.jbuilder +1 -0
  44. data/app/views/fedipub/client/activities/_index.json.jbuilder +1 -0
  45. data/app/views/fedipub/client/activities/feed.html.erb +11 -0
  46. data/app/views/fedipub/client/activities/feed.json.jbuilder +1 -0
  47. data/app/views/fedipub/client/activities/index.html.erb +5 -0
  48. data/app/views/fedipub/client/activities/index.json.jbuilder +1 -0
  49. data/app/views/fedipub/client/actors/_actor.json.jbuilder +17 -0
  50. data/app/views/fedipub/client/actors/_lookup_form.html.erb +5 -0
  51. data/app/views/fedipub/client/actors/gone.html.erb +1 -0
  52. data/app/views/fedipub/client/actors/index.html.erb +44 -0
  53. data/app/views/fedipub/client/actors/index.json.jbuilder +1 -0
  54. data/app/views/fedipub/client/actors/show.html.erb +112 -0
  55. data/app/views/fedipub/client/actors/show.json.jbuilder +1 -0
  56. data/app/views/fedipub/client/common/_client_links.html.erb +26 -0
  57. data/app/views/fedipub/client/followings/_follow.html.erb +4 -0
  58. data/app/views/fedipub/client/followings/_follow_actions.html.erb +36 -0
  59. data/app/views/fedipub/client/followings/_follower.html.erb +7 -0
  60. data/app/views/fedipub/client/followings/_following.json.jbuilder +1 -0
  61. data/app/views/fedipub/client/followings/_form.html.erb +22 -0
  62. data/app/views/fedipub/client/followings/index.html.erb +29 -0
  63. data/app/views/fedipub/client/followings/index.json.jbuilder +1 -0
  64. data/app/views/fedipub/client/followings/show.html.erb +21 -0
  65. data/app/views/fedipub/client/followings/show.json.jbuilder +1 -0
  66. data/app/views/fedipub/server/activities/_activity.activitypub.jbuilder +25 -0
  67. data/app/views/fedipub/server/activities/show.activitypub.jbuilder +1 -0
  68. data/app/views/fedipub/server/actors/_actor.activitypub.jbuilder +27 -0
  69. data/app/views/fedipub/server/actors/_tombstone.activitypub.jbuilder +6 -0
  70. data/app/views/fedipub/server/actors/show.activitypub.jbuilder +5 -0
  71. data/app/views/fedipub/server/followings/_following.activitypub.jbuilder +7 -0
  72. data/app/views/fedipub/server/followings/show.activitypub.jbuilder +1 -0
  73. data/app/views/fedipub/server/nodeinfo/index.nodeinfo.jbuilder +6 -0
  74. data/app/views/fedipub/server/nodeinfo/show.nodeinfo.jbuilder +19 -0
  75. data/app/views/fedipub/server/published/_publishable.activitypub.jbuilder +11 -0
  76. data/app/views/fedipub/server/published/_tombstone.activitypub.jbuilder +6 -0
  77. data/app/views/fedipub/server/published/show.activitypub.jbuilder +5 -0
  78. data/app/views/fedipub/server/shared/ordered_collection.activitypub.jbuilder +6 -0
  79. data/app/views/fedipub/server/shared/ordered_collection_page.activitypub.jbuilder +12 -0
  80. data/app/views/fedipub/server/web_finger/find.jrd.jbuilder +24 -0
  81. data/app/views/fedipub/server/web_finger/host_meta.xrd.erb +5 -0
  82. data/app/views/users/show.html.erb +4 -0
  83. data/config/initializers/mime_types.rb +21 -0
  84. data/config/initializers/security_vocab.rb +53 -0
  85. data/config/routes.rb +53 -0
  86. data/db/migrate/20200712133150_create_federails_actors.rb +22 -0
  87. data/db/migrate/20200712143127_create_federails_followings.rb +14 -0
  88. data/db/migrate/20200712174938_create_federails_activities.rb +11 -0
  89. data/db/migrate/20241002094500_add_uuids.rb +13 -0
  90. data/db/migrate/20241002094501_add_keypair_to_actors.rb +8 -0
  91. data/db/migrate/20250122160618_add_extensions_to_federails_actors.rb +5 -0
  92. data/db/migrate/20250301082500_add_local_to_actors.rb +11 -0
  93. data/db/migrate/20250329123939_add_actor_type_to_actors.rb +5 -0
  94. data/db/migrate/20250329123940_add_tombstoned_at_to_actors.rb +5 -0
  95. data/db/migrate/20250426061729_create_federails_hosts.rb +22 -0
  96. data/db/migrate/20251121160720_add_to_and_cc_to_federails_activities.rb +6 -0
  97. data/db/migrate/20260421170640_add_result_and_instrument_to_federails_activities.rb +6 -0
  98. data/db/migrate/20260723113716_rename_federails_to_fedipub.rb +15 -0
  99. data/lib/fedipub/configuration.rb +119 -0
  100. data/lib/fedipub/data_transformer/note.rb +36 -0
  101. data/lib/fedipub/engine.rb +11 -0
  102. data/lib/fedipub/maintenance/actors_updater.rb +66 -0
  103. data/lib/fedipub/maintenance/hosts_updater.rb +19 -0
  104. data/lib/fedipub/utils/actor.rb +85 -0
  105. data/lib/fedipub/utils/context.rb +12 -0
  106. data/lib/fedipub/utils/host.rb +54 -0
  107. data/lib/fedipub/utils/json_request.rb +41 -0
  108. data/lib/fedipub/utils/object.rb +131 -0
  109. data/lib/fedipub/utils/response_codes.rb +11 -0
  110. data/lib/fedipub/version.rb +1 -3
  111. data/lib/fedipub.rb +127 -4
  112. data/lib/fediverse/collection.rb +31 -0
  113. data/lib/fediverse/inbox.rb +110 -0
  114. data/lib/fediverse/node_info.rb +38 -0
  115. data/lib/fediverse/notifier.rb +90 -0
  116. data/lib/fediverse/request.rb +29 -0
  117. data/lib/fediverse/signature.rb +49 -0
  118. data/lib/fediverse/webfinger.rb +140 -0
  119. data/lib/fediverse.rb +5 -0
  120. data/lib/generators/fedipub/copy_client_policies/USAGE +8 -0
  121. data/lib/generators/fedipub/copy_client_policies/copy_client_policies_generator.rb +9 -0
  122. data/lib/generators/fedipub/copy_client_views/USAGE +8 -0
  123. data/lib/generators/fedipub/copy_client_views/copy_client_views_generator.rb +9 -0
  124. data/lib/generators/fedipub/copy_factories/USAGE +8 -0
  125. data/lib/generators/fedipub/copy_factories/copy_factories_generator.rb +43 -0
  126. data/lib/generators/fedipub/install/USAGE +9 -0
  127. data/lib/generators/fedipub/install/install_generator.rb +10 -0
  128. data/lib/generators/fedipub/install/templates/fedipub.rb +1 -0
  129. data/lib/generators/fedipub/install/templates/fedipub.yml +27 -0
  130. data/lib/tasks/factory_bot.rake +15 -0
  131. data/lib/tasks/fedipub_tasks.rake +13 -0
  132. metadata +259 -10
data/config/routes.rb ADDED
@@ -0,0 +1,53 @@
1
+ Fedipub::Engine.routes.draw do
2
+ if Fedipub.configuration.enable_discovery
3
+ scope path: '/' do
4
+ get '/.well-known/webfinger', to: 'server/web_finger#find', as: :webfinger
5
+ get '/.well-known/host-meta', to: 'server/web_finger#host_meta', as: :host_meta
6
+ get '/.well-known/nodeinfo', to: 'server/nodeinfo#index', as: :node_info
7
+ get '/nodeinfo/2.0', to: 'server/nodeinfo#show', as: :show_node_info
8
+ end
9
+ end
10
+
11
+ if Fedipub.configuration.client_routes_path
12
+ scope Fedipub.configuration.client_routes_path, module: :client, as: :client do
13
+ resources :activities, only: [:index] do
14
+ collection do
15
+ get :feed, to: 'activities#feed'
16
+ end
17
+ end
18
+ resources :actors, only: [:index, :show] do
19
+ collection do
20
+ get :lookup, to: 'actors#lookup'
21
+ end
22
+ resources :activities, only: [:index]
23
+ end
24
+ get :feed, to: 'activities#feed'
25
+ resources :followings, only: [:new, :create, :destroy] do
26
+ collection do
27
+ post :follow, to: 'followings#follow'
28
+ end
29
+
30
+ member do
31
+ put :accept, to: 'followings#accept'
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ scope Fedipub.configuration.server_routes_path, module: :server, as: :server, defaults: { format: :activitypub } do
38
+ resources :actors, only: [:show] do
39
+ member do
40
+ get :followers
41
+ get :following
42
+ end
43
+ get :outbox, to: 'activities#outbox'
44
+ post :inbox, to: 'activities#create'
45
+ resources :activities, only: [:show]
46
+ resources :followings, only: [:show]
47
+ end
48
+
49
+ scope :published do
50
+ get ':publishable_type/:id', to: 'published#show', as: :published
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,22 @@
1
+ class CreateFederailsActors < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :federails_actors do |t|
4
+ t.string :name
5
+ t.string :federated_url
6
+ t.string :username
7
+ t.string :server
8
+ t.string :inbox_url
9
+ t.string :outbox_url
10
+ t.string :followers_url
11
+ t.string :followings_url
12
+ t.string :profile_url
13
+
14
+ t.integer :entity_id, null: true
15
+ t.string :entity_type, null: true
16
+
17
+ t.timestamps
18
+ t.index :federated_url, unique: true
19
+ t.index [:entity_type, :entity_id], name: 'index_federails_actors_on_entity', unique: true
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ class CreateFederailsFollowings < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :federails_followings do |t|
4
+ t.references :actor, null: false, foreign_key: { to_table: :federails_actors }
5
+ t.references :target_actor, null: false, foreign_key: { to_table: :federails_actors }
6
+ t.integer :status, default: 0
7
+ t.string :federated_url
8
+
9
+ t.timestamps
10
+
11
+ t.index [:actor_id, :target_actor_id], unique: true
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ class CreateFederailsActivities < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :federails_activities do |t|
4
+ t.references :entity, polymorphic: true, null: false
5
+ t.string :action, null: false, default: nil
6
+ t.references :actor, null: false, foreign_key: { to_table: :federails_actors }
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ class AddUuids < ActiveRecord::Migration[7.0]
2
+ def change
3
+ [
4
+ :federails_actors,
5
+ :federails_activities,
6
+ :federails_followings,
7
+ ].each do |table|
8
+ change_table table do |t|
9
+ t.string :uuid, default: nil, index: { unique: true }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ class AddKeypairToActors < ActiveRecord::Migration[7.0]
2
+ def change
3
+ change_table :federails_actors do |t|
4
+ t.text :public_key
5
+ t.text :private_key
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class AddExtensionsToFederailsActors < ActiveRecord::Migration[7.1]
2
+ def change
3
+ add_column :federails_actors, :extensions, :json, default: nil, null: true
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ class AddLocalToActors < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :federails_actors, :local, :boolean, null: false, default: false
4
+
5
+ reversible do |dir|
6
+ dir.up do
7
+ exec_update 'UPDATE federails_actors SET local=true WHERE entity_type IS NOT NULL'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ class AddActorTypeToActors < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_column :federails_actors, :actor_type, :string, null: true
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddTombstonedAtToActors < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :federails_actors, :tombstoned_at, :datetime, default: nil
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ class CreateFederailsHosts < ActiveRecord::Migration[7.2]
2
+ def change
3
+ create_table :federails_hosts do |t|
4
+ t.string :domain, null: false, default: nil
5
+ t.string :nodeinfo_url
6
+ t.string :software_name
7
+ t.string :software_version
8
+
9
+ # Uncomment the lines below if you use PostgreSQL
10
+ # t.jsonb :protocols, default: []
11
+ # t.jsonb :services, default: {}
12
+ #
13
+ # Other databases
14
+ t.text :protocols, default: '[]'
15
+ t.text :services, default: '{}'
16
+
17
+ t.timestamps
18
+
19
+ t.index :domain, unique: true
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ class AddToAndCcToFederailsActivities < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_column :federails_activities, :to, :string
4
+ add_column :federails_activities, :cc, :string
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class AddResultAndInstrumentToFederailsActivities < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_column :federails_activities, :result, :string
4
+ add_column :federails_activities, :instrument, :string
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ class RenameFederailsToFedipub < ActiveRecord::Migration[7.2]
2
+ def change
3
+ # Activities
4
+ rename_index 'federails_activities', 'index_federails_activities_on_entity', 'index_fedipub_activities_on_entity'
5
+ rename_table 'federails_activities', 'fedipub_activities'
6
+ # Actors
7
+ rename_index 'federails_actors', 'index_federails_actors_on_entity', 'index_fedipub_actors_on_entity'
8
+ rename_table 'federails_actors', 'fedipub_actors'
9
+ # Followings
10
+ rename_index 'federails_followings', 'index_federails_followings_on_actor_id_and_target_actor_id', 'index_fedipub_followings_on_actor_id_and_target_actor_id'
11
+ rename_table 'federails_followings', 'fedipub_followings'
12
+ # Hosts
13
+ rename_table 'federails_hosts', 'fedipub_hosts'
14
+ end
15
+ end
@@ -0,0 +1,119 @@
1
+ module Fedipub
2
+ # rubocop:disable Style/ClassVars
3
+
4
+ # Stores the Fedipub configuration in a _singleton_.
5
+ module Configuration
6
+ # Application name, used in well-known and nodeinfo endpoints
7
+ mattr_accessor :app_name
8
+ @@app_name = nil
9
+
10
+ # Application version, used in well-known and nodeinfo endpoints
11
+ mattr_accessor :app_version
12
+ @@app_version = nil
13
+
14
+ # Force https urls in various rendered content (currently in webfinger views)
15
+ mattr_accessor :force_ssl
16
+ @@force_ssl = nil
17
+
18
+ # Site hostname
19
+ mattr_reader :site_host
20
+ @@site_host = nil
21
+
22
+ # Site port
23
+ mattr_reader :site_port
24
+ @@site_port = nil
25
+
26
+ # Whether to enable ".well-known" and "nodeinfo" endpoints
27
+ mattr_accessor :enable_discovery
28
+ @@enable_discovery = true
29
+
30
+ # Does the site allow open registrations? (only used for nodeinfo reporting)
31
+ # Can either be a static boolean, or a Proc which will be called to get the state.
32
+ mattr_writer :open_registrations
33
+ @@open_registrations = false
34
+ def self.open_registrations
35
+ @@open_registrations.is_a?(Proc) ? @@open_registrations.call : @@open_registrations
36
+ end
37
+
38
+ # Custom metadata for nodeinfo
39
+ # Can either be a static hash, or a Proc which will be called to get the state.
40
+ mattr_writer :nodeinfo_metadata
41
+ @@nodeinfo_metadata = {}
42
+ def self.nodeinfo_metadata
43
+ @@nodeinfo_metadata.is_a?(Proc) ? @@nodeinfo_metadata.call : @@nodeinfo_metadata
44
+ end
45
+
46
+ # Application layout
47
+ mattr_accessor :app_layout
48
+ @@app_layout = nil
49
+
50
+ # Route path for the federation URLs (to "Fedipub::Server::*" controllers)
51
+ mattr_accessor :server_routes_path
52
+ @@server_routes_path = :federation
53
+
54
+ # Route path for the webapp URLs (to "Fedipub::Client::*" controllers)
55
+ mattr_accessor :client_routes_path
56
+ @@client_routes_path = :app
57
+
58
+ # Default controller to use as base for client controllers
59
+ mattr_accessor :base_client_controller
60
+ @@base_client_controller = 'ActionController::Base'
61
+
62
+ # @!method self.remote_follow_url_method
63
+ #
64
+ # Route method for remote-following requests
65
+
66
+ # @!method self.remote_follow_url_method=(value)
67
+ #
68
+ # Sets the route method for remote-following requests
69
+ #
70
+ # The route should lead to a page displaying the remote actor and a button to follow it.
71
+ # Remote actor is specified in the `uri` query parameter.
72
+ #
73
+ # Its value defaults to a route of the Fedipub client, so your application will break
74
+ # if you don't use the client routes and don't override this value.
75
+ #
76
+ # @param value [String] Route method name as used in links
77
+ # @example
78
+ # remote_follow_url_method 'main_app.my_custom_route_helper'
79
+ mattr_accessor :remote_follow_url_method
80
+ @@remote_follow_url_method = 'fedipub.new_client_following_url'
81
+
82
+ def self.site_host=(value)
83
+ @@site_host = value
84
+ Fedipub::Engine.routes.default_url_options[:host] = value
85
+ end
86
+
87
+ def self.site_port=(value)
88
+ @@site_port = value
89
+ Fedipub::Engine.routes.default_url_options[:port] = value
90
+ end
91
+
92
+ # Default amount of seconds to consider that a remote entity could be updated
93
+ #
94
+ # This setting is used for hosts information only, for now.
95
+ mattr_accessor :remote_entities_cache_duration
96
+ @@remote_entities_cache_duration = 1.day
97
+
98
+ # Job queue name
99
+ mattr_accessor :job_queue
100
+ @@job_queue = :default
101
+
102
+ # List of actor types (classes using Fedipub::ActorEntity)
103
+ mattr_reader :actor_types
104
+ @@actor_types = {}
105
+
106
+ def self.register_actor_class(klass, config = {})
107
+ @@actor_types[klass.name] = config.merge(class: klass)
108
+ end
109
+
110
+ # List of data types (classes using Fedipub::DataEntity)
111
+ mattr_reader :data_types
112
+ @@data_types = {}
113
+
114
+ def self.register_data_type(klass, config = {})
115
+ @@data_types[klass.name] = config.merge(class: klass)
116
+ end
117
+ end
118
+ # rubocop:enable Style/ClassVars
119
+ end
@@ -0,0 +1,36 @@
1
+ require 'fedipub/utils/context'
2
+
3
+ module Fedipub
4
+ module DataTransformer
5
+ module Note
6
+ # Renders a Note. The entity is used to determine actor and generic fields data
7
+ #
8
+ # @param entity [#federail_actor, #federated_url, #created_at, #updated_at] A model instance
9
+ # @param content [String] Note content
10
+ # @param name [String, nil] Optional name/title
11
+ # @param custom [Hash] Optional additional keys (e.g.: attachment, icon, ...). Defaults will override these.
12
+ #
13
+ # @return [Hash]
14
+ #
15
+ # @example
16
+ # Fedipub::DataTransformer::Note.to_federation(comment, content: comment.content, custom: { 'inReplyTo' => comment.parent.federated_url })
17
+ #
18
+ # See:
19
+ # - https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object
20
+ # - https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note
21
+ def self.to_federation(entity, content:, name: nil, custom: {})
22
+ # Merge default and custom contexts
23
+ context = Utils::Context.generate(additional: custom.delete('@context'))
24
+ # Merge in standard Note fields
25
+ custom.merge '@context' => context,
26
+ 'id' => entity.federated_url,
27
+ 'type' => 'Note',
28
+ 'name' => name,
29
+ 'content' => content,
30
+ 'attributedTo' => entity.fedipub_actor.federated_url,
31
+ 'published' => entity.created_at,
32
+ 'updated' => entity.updated_at
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ module Fedipub
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Fedipub
4
+
5
+ config.generators do |g|
6
+ g.test_framework :rspec
7
+ g.fixture_replacement :factory_bot
8
+ g.factory_bot dir: 'spec/factories'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,66 @@
1
+ module Fedipub
2
+ module Maintenance
3
+ class ActorsUpdater
4
+ class << self
5
+ # Fetches all distant actors again and update their local copy
6
+ #
7
+ # A block can be passed with two arguments: the actor being updated and the update status
8
+ #
9
+ # @param actors [Integer, Fedipub::Actor, Array<Fedipub::Actor>, nil] Actor ID, Actor or list of actors to update.
10
+ # If nothing is passed, all distant actors are processed
11
+ # @example
12
+ # Update all distant actors
13
+ # Fedipub::Maintenance::ActorUpdater.run
14
+ # With an actor id:
15
+ # Fedipub::Maintenance::ActorUpdater.run 1
16
+ # With a federated URL:
17
+ # Fedipub::Maintenance::ActorUpdater.run 'https://example.com/actor'
18
+ # With a federated URL:
19
+ # Fedipub::Maintenance::ActorUpdater.run ['https://example.com/actors/1', 'https://example.com/actors/1']
20
+ # With actors:
21
+ # Fedipub::Maintenance::ActorUpdater.run Fedipub::Actor.last(10)
22
+ # Update all distant actors and puts status for each actor
23
+ # Fedipub::Maintenance::ActorUpdater.run {|actor, status| puts "#{actor.federated_url}: #{status}"}
24
+ def run(actors = nil, &block)
25
+ actors_list(actors).each do |actor|
26
+ status = update(actor)
27
+
28
+ yield(actor, status) if block
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # Make a list of actors to update from the passed attribute
35
+ def actors_list(param) # rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
36
+ if param.nil?
37
+ Fedipub::Actor.distant
38
+ elsif param.is_a? String
39
+ [Fedipub::Actor.distant.find_by!(federated_url: param)]
40
+ elsif param.is_a? Integer
41
+ [Fedipub::Actor.distant.find(param)]
42
+ elsif param.is_a?(Fedipub::Actor)
43
+ [param]
44
+ elsif param.respond_to?(:pluck) && param.first.is_a?(Fedipub::Actor)
45
+ param
46
+ elsif param.is_a?(Array) && param.first.is_a?(String)
47
+ Fedipub::Actor.distant.where(federated_url: param)
48
+ else
49
+ raise "Cannot extract actors from #{param.class}"
50
+ end
51
+ end
52
+
53
+ # @param actor [Fedipub::Actor]
54
+ def update(actor)
55
+ return :ignored_local if actor.local?
56
+
57
+ actor.sync! ? :updated : :failed
58
+ rescue ActiveRecord::RecordNotFound
59
+ :not_found
60
+ rescue StandardError
61
+ :failed
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,19 @@
1
+ module Fedipub
2
+ module Maintenance
3
+ class HostsUpdater
4
+ class << self
5
+ # Update information for all known hosts, and complete if some are missing
6
+ def run(cache_interval: nil)
7
+ cache_interval ||= Fedipub::Configuration.remote_entities_cache_duration
8
+
9
+ domains = Fedipub::Actor.distant.distinct(:server).pluck(:server) + Fedipub::Host.pluck(:domain)
10
+ domains.uniq!
11
+
12
+ domains.each do |domain|
13
+ Fedipub::Host.create_or_update domain, min_update_interval: cache_interval
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,85 @@
1
+ module Fedipub
2
+ module Utils
3
+ class Actor
4
+ # List of the attributes computed for local actors
5
+ COMPUTED_ATTRIBUTES = [
6
+ :federated_url,
7
+ :username,
8
+ :name,
9
+ :server,
10
+ :inbox_url,
11
+ :outbox_url,
12
+ :followers_url,
13
+ :followings_url,
14
+ :profile_url,
15
+ ].freeze
16
+
17
+ class << self
18
+ # @param actor [Fedipub::Actor]
19
+ # @return [Fedipub::Actor]
20
+ def tombstone!(actor)
21
+ if actor.local?
22
+ tombstone_local_actor actor
23
+ else
24
+ tombstone_distant_actor actor
25
+ end
26
+
27
+ actor
28
+ end
29
+
30
+ def untombstone!(actor)
31
+ if actor.local?
32
+ untombstone_local_actor actor
33
+ else
34
+ untombstone_distant_actor actor
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def tombstone_local_actor(actor)
41
+ Fedipub::Actor.transaction do
42
+ hash = {
43
+ tombstoned_at: Time.current,
44
+ entity: actor.entity || nil,
45
+ }
46
+ # Hardcode attributes depending on the actor's entity
47
+ COMPUTED_ATTRIBUTES.each { |attribute| hash[attribute] = actor.send(attribute) }
48
+
49
+ actor.update! hash
50
+
51
+ Activity.create! actor: actor, action: 'Delete', entity: actor
52
+ end
53
+ end
54
+
55
+ def untombstone_local_actor(actor)
56
+ return unless actor.tombstoned?
57
+ raise 'Cannot restore a local actor without an entity' if actor.entity.blank?
58
+
59
+ Fedipub::Actor.transaction do
60
+ # Reset hardcoded attributes depending on the actor's entity
61
+ hash = { tombstoned_at: nil }
62
+ COMPUTED_ATTRIBUTES.each { |attribute| hash[attribute] = nil }
63
+
64
+ actor.update! hash
65
+
66
+ delete_activity = Activity.find_by actor: actor, action: 'Delete', entity: actor
67
+ return unless delete_activity
68
+
69
+ delete_activity.undo!
70
+ end
71
+ end
72
+
73
+ def tombstone_distant_actor(actor)
74
+ actor.update! tombstoned_at: Time.current
75
+ end
76
+
77
+ def untombstone_distant_actor(actor)
78
+ actor.tombstoned_at = nil
79
+ actor.sync!
80
+ actor.save
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,12 @@
1
+ module Fedipub
2
+ module Utils
3
+ module Context
4
+ class << self
5
+ def generate(additional: nil)
6
+ activity_streams = 'https://www.w3.org/ns/activitystreams'
7
+ additional.nil? ? activity_streams : [activity_streams, additional].flatten.compact
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,54 @@
1
+ module Fedipub
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 Fedipub.configuration.site_host
11
+ host_and_port (uri.host || 'localhost'), Fedipub.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
@@ -0,0 +1,41 @@
1
+ require 'faraday'
2
+ require 'faraday/follow_redirects'
3
+
4
+ module Fedipub
5
+ module Utils
6
+ # Simple wrapper to make requests expecting JSON
7
+ module JsonRequest
8
+ class UnhandledResponseStatus < StandardError; end
9
+
10
+ BASE_HEADERS = {
11
+ 'Content-Type' => 'application/json',
12
+ 'Accept' => 'application/json',
13
+ }.freeze
14
+
15
+ # Makes a GET request and returns a +Hash+ from the parsed body
16
+ #
17
+ # @param url [String] Target URL
18
+ # @param params [Hash] Querystring parameters
19
+ # @param headers [Hash] Additional headers
20
+ # @param follow_redirects [Boolean] Whether to follow redirections
21
+ # @param expected_status [Integer] Expected response status. Will raise a +UnhandledResponseStatus+ when status is different
22
+ #
23
+ # @return The parsed JSON object
24
+ #
25
+ # @raise [UnhandledResponseStatus] when response status is not the expected_status
26
+ def self.get_json(url, params: {}, headers: {}, follow_redirects: false, expected_status: 200)
27
+ headers = BASE_HEADERS.merge headers
28
+
29
+ connection = Faraday.new url: url, params: params, headers: headers do |faraday|
30
+ faraday.response :follow_redirects if follow_redirects
31
+ faraday.adapter Faraday.default_adapter
32
+ end
33
+
34
+ response = connection.get
35
+ raise UnhandledResponseStatus, "Unhandled status code #{response.status} for GET #{url}" if expected_status && response.status != expected_status
36
+
37
+ JSON.parse(response.body)
38
+ end
39
+ end
40
+ end
41
+ end