federails 0.4.0 → 0.6.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -0
  3. data/README.md +19 -189
  4. data/app/controllers/federails/client/actors_controller.rb +18 -4
  5. data/app/controllers/federails/server/actors_controller.rb +4 -1
  6. data/app/controllers/federails/server/published_controller.rb +30 -0
  7. data/app/controllers/federails/server/web_finger_controller.rb +9 -6
  8. data/app/controllers/federails/server_controller.rb +7 -0
  9. data/app/models/concerns/federails/actor_entity.rb +120 -56
  10. data/app/models/concerns/federails/data_entity.rb +205 -0
  11. data/app/models/concerns/federails/handles_delete_requests.rb +31 -0
  12. data/app/models/concerns/federails/has_uuid.rb +27 -1
  13. data/app/models/federails/activity.rb +27 -4
  14. data/app/models/federails/actor.rb +93 -30
  15. data/app/models/federails/following.rb +29 -9
  16. data/app/policies/federails/server/publishable_policy.rb +15 -0
  17. data/app/views/federails/client/actors/_actor.json.jbuilder +4 -1
  18. data/app/views/federails/client/actors/gone.html.erb +1 -0
  19. data/app/views/federails/client/actors/index.html.erb +7 -1
  20. data/app/views/federails/server/activities/_activity.activitypub.jbuilder +8 -3
  21. data/app/views/federails/server/actors/_actor.activitypub.jbuilder +2 -2
  22. data/app/views/federails/server/actors/_tombstone.activitypub.jbuilder +9 -0
  23. data/app/views/federails/server/actors/show.activitypub.jbuilder +5 -1
  24. data/app/views/federails/server/published/_publishable.activitypub.jbuilder +11 -0
  25. data/app/views/federails/server/published/_tombstone.activitypub.jbuilder +9 -0
  26. data/app/views/federails/server/published/show.activitypub.jbuilder +5 -0
  27. data/config/routes.rb +4 -0
  28. data/db/migrate/20250122160618_add_extensions_to_federails_actors.rb +5 -0
  29. data/db/migrate/20250301082500_add_local_to_actors.rb +11 -0
  30. data/db/migrate/20250329123939_add_actor_type_to_actors.rb +5 -0
  31. data/db/migrate/20250329123940_add_tombstoned_at_to_actors.rb +5 -0
  32. data/lib/federails/configuration.rb +24 -1
  33. data/lib/federails/data_transformer/note.rb +31 -0
  34. data/lib/federails/maintenance/actors_updater.rb +67 -0
  35. data/lib/federails/utils/actor.rb +53 -0
  36. data/lib/federails/utils/object.rb +131 -0
  37. data/lib/federails/version.rb +1 -1
  38. data/lib/federails.rb +54 -0
  39. data/lib/fediverse/inbox.rb +40 -8
  40. data/lib/fediverse/notifier.rb +3 -0
  41. data/lib/fediverse/request.rb +13 -0
  42. data/lib/fediverse/webfinger.rb +72 -26
  43. data/lib/fediverse.rb +3 -0
  44. data/lib/tasks/federails_tasks.rake +8 -4
  45. metadata +22 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3463b1a877ca8224ade011016236c1fdbcbe41e31712b609347f41592d30531
4
- data.tar.gz: 16ec7975b26bc5fbec1e243a4653c27d8b0239c7dbda42544150c9e5680d65f5
3
+ metadata.gz: '0538a0c2d7f7e3845a5d8ae304ac2b319b3cf1e76b10520645773f8f763c3431'
4
+ data.tar.gz: e97c559ecc5aba1b1c05c9be5f39b982a22e92cb8f73ae8c8a08741a3efb3d01
5
5
  SHA512:
6
- metadata.gz: b54850967223bb1be7aadd54547f757a9e5fbe6ccc8d84249a4a2b340a494b275905c25e862c493b2d2b177a7cc5e12abf2d6e44c4f84b4bec76796a7b9cba5f
7
- data.tar.gz: cc00b05153d4434eacc6409a4422575d5e851e07c332001a72fc818e5f64d1bf6f2cd35f6eaa5f4025b3150fe9009a370665a044d44f84a60399690186a8f5e8
6
+ metadata.gz: 560377c5b698b87274d1df302595c33467f010402096227583161087aaf124b0a9e2820f44234b2932c158e84b0ab8ccb79ea8501557840509a416c7b8205295
7
+ data.tar.gz: 6380fc79556446afbd4a81e3e5e2e6b49bdb5877576dd1038208c1c4471cd3986bb35bbfaae1a9f37e618ddf5ab14dcf6974f512f6a609c70c2de0b7c251803f
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Experiments Labs / Experimentations
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -23,207 +23,37 @@ The general direction is to be able to:
23
23
  - implement some or all the parts of the RFC labelled with **SHOULD** and **SHOULD NOT**
24
24
  - maybe implement the parts of the RFC labelled with **MAY**
25
25
 
26
- ## Installation
26
+ ## Supported Ruby on Rails versions
27
27
 
28
- Add this line to your application's Gemfile:
28
+ This gem is tested against non end-of-life versions of Ruby and Rails:
29
29
 
30
- ```ruby
31
- gem "federails"
32
- ```
30
+ - Ruby versions 3.1 to 3.4
31
+ - Rails 7.1 to 8.0.x.
33
32
 
34
- And then execute:
33
+ Feel free to open an issue if we missed something
35
34
 
36
- ```bash
37
- $ bundle
38
- ```
35
+ It _may_ work on other versions, but we won't provide support.
39
36
 
40
- ### Configuration
37
+ ## Documentation
41
38
 
42
- Generate configuration files:
39
+ - [Usage](docs/usage.md)
40
+ - [Common questions](docs/faq.md)
41
+ - [Contributing](CONTRIBUTING.md)
43
42
 
44
- ```sh
45
- bundle exec rails generate federails:install
46
- ```
47
-
48
- It creates an initializer and a configuration file:
49
- - `config/initializers/federails.rb`
50
- - `config/federails.yml`
51
-
52
- By default, Federails is configured using `config_from` method, that loads the appropriate YAML file, but you may want
53
- to configure it differently:
54
-
55
- ```rb
56
- # config/initializers/federails.rb
57
- Federails.configure do |config|
58
- config.host = 'localhost'
59
- # ...
60
- end
61
- ```
62
-
63
- For now, refer to [the source code](lib/federails/configuration.rb) for the full list of options.
64
-
65
- ### Routes
66
-
67
- Mount the engine on `/`: routes to `/.well-known/*` and `/nodeinfo/*` must be at the root of the site.
68
- Federails routes are then available under the configured path (`routes_path`):
69
-
70
- ```rb
71
- # config/routes.rb
72
- mount Federails::Engine => '/'
73
- ```
74
-
75
- With `routes_path = 'federation'`, routes will be:
76
-
77
- ```txt
78
- /.well-known/webfinger(.:format)
79
- /.well-known/host-meta(.:format)
80
- /.well-known/nodeinfo(.:format)
81
- /nodeinfo/2.0(.:format)
82
- /federation/actors/:id/followers(.:format)
83
- /federation/actors/:id/following(.:format)
84
- /federation/actors/:actor_id/outbox(.:format)
85
- /federation/actors/:actor_id/inbox(.:format)
86
- /federation/actors/:actor_id/activities/:id(.:format)
87
- /federation/actors/:actor_id/followings/:id(.:format)
88
- /federation/actors/:actor_id/notes/:id(.:format)
89
- /federation/actors/:id(.:format)
90
- ...
91
- ```
92
-
93
- Some routes can be disabled in configuration if you don't want to expose particular features:
94
-
95
- ```rb
96
- Federails.configure do |config|
97
- # Disable routing for .well-known and nodeinfo
98
- config.enable_discovery = false
99
-
100
- # Disable web client UI routes
101
- config.client_routes_path = nil
102
- end
103
- ```
104
-
105
- #### Remote following
106
-
107
- By default, remote follow requests (where you press a follow button on another server and get redirected home to complete the follow)
108
- will use the built-in client paths. If you're not using the client, or want to provide your own user interface, you can set the path like this, assuming that `new_follow_url` is a valid route in your app. A `uri` query parameter template will be automatically appended, you don't need to specify that.
109
-
110
- ```rb
111
- Federails.configure do |config|
112
- config.remote_follow_url_method = :new_follow_url
113
- end
114
- ```
115
-
116
- ### Migrations
117
-
118
- Copy the migrations:
119
-
120
- ```sh
121
- bundle exec rails federails:install:migrations
122
- ```
123
-
124
- ### User model
125
-
126
- In the ActivityPub world, we refer to _actors_ to represent the thing that publishes or subscribe to _other actors_.
127
-
128
- Federails provides a concern to include in your "user" model or whatever will publish data:
129
-
130
- ```rb
131
- # app/models/user.rb
132
-
133
- class User < ApplicationRecord
134
- # Include the concern here:
135
- include Federails::ActorEntity
136
-
137
- # Configure field names
138
- acts_as_federails_actor username_field: :username, name_field: :name, profile_url_method: :user_url
139
- end
140
- ```
141
-
142
- This concern automatically create a `Federails::Actor` after a user creation, as well as the `actor` reference. When adding it to
143
- an existing model with existing data, you will need to generate the corresponding actors yourself in a migration.
144
-
145
- Usage example:
146
-
147
- ```rb
148
- actor = User.find(1).federails_actor
149
-
150
- actor.inbox
151
- actor.outbox
152
- actor.followers
153
- actor.following
154
- #...
155
- ```
156
-
157
- ### Using the Federails client
158
-
159
- Federails comes with a client, enabled by default, that provides basic views to display and interact with Federails data,
160
- accessible on `/app` by default (changeable with the configuration option `client_routes_path`)
161
-
162
- If it's a good starting point, it might be disabled once you made your own integration by setting `client_routes_path`
163
- to a `nil` value.
164
-
165
- If you want to override the client's views, copy them in your application:
166
-
167
- ```sh
168
- rails generate federails:copy_client_views
169
- ```
170
-
171
- ## Common questions
172
-
173
- - **I override the base controller and the links breaks in my layout**
174
-
175
- Use `main_app.<url_helper>` for links to your application; `federails.<federails_url_helper>` for links to the Federails client.
176
- - **I specified a custom layout and the links breaks in it**
177
-
178
- Use `main_app.<url_helper>` for links to your application; `federails.<federails_url_helper>` for links to the Federails client.
179
- - **I specified a custom layout and my helpers are not available**
43
+ ## License
180
44
 
181
- You will have better results if you specify a `base_controller` from your application as Federails base controller is isolated from the main app and does not have access to its helpers.
45
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
182
46
 
183
47
  ## Contributing
184
48
 
185
- Contributions are welcome, may it be issues, ideas, code or whatever you want to share. Please note:
49
+ See [CONTRIBUTING](CONTRIBUTING.md) to have an overview of the process and the tools we use.
186
50
 
187
- - This project is _fast forward_ only: we don't do merge commits
188
- - We adhere to [semantic versioning](). Please update the changelog in your commits
189
- - We try to adhere to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) principles
190
- - We _may_ rename your commits before merging them
191
- - We _may_ split your commits before merging them
51
+ ### Contributors
192
52
 
193
- To contribute:
53
+ - [echarp](https://gitlab.com/echarp)
54
+ - [James Smith](https://gitlab.com/floppy.uk)
55
+ - [Manuel Tancoigne](https://gitlab.com/mtancoigne)
194
56
 
195
- 1. Fork this repository
196
- 2. Create small commits
197
- 3. Ideally create small pull requests. Don't hesitate to open them early so we all can follow how it's going
198
- 4. Get congratulated
57
+ ### Indirect contributions
199
58
 
200
- ### Tooling
201
-
202
- #### RSpec
203
-
204
- RSpec is the test suite. Start it with
205
-
206
- ```sh
207
- bundle exec rspec
208
- ```
209
-
210
- #### Rubocop
211
-
212
- Rubocop is a linter. Start it with
213
-
214
- ```sh
215
- bundle exec rubocop
216
- ```
217
-
218
- #### FactoryBot
219
-
220
- FactoryBot is a factory generator used in tests and development.
221
- A rake task checks the replayability of the factories and traits:
222
-
223
- ```sh
224
- bundle exec app:factory_bot:lint
225
- ```
226
-
227
- ## License
228
-
229
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
59
+ - Gitlab runners are graciously provided by [Coopaname](https://coopaname.coop), a French cooperative.
@@ -14,14 +14,16 @@ module Federails
14
14
 
15
15
  # GET /app/actors/1
16
16
  # GET /app/actors/1.json
17
- def show; end
17
+ def show
18
+ render_show
19
+ end
18
20
 
19
- # GET /app/explorer/lookup
20
- # GET /app/explorer/lookup.json
21
+ # GET /app/actors/lookup
22
+ # GET /app/actors/lookup.json
21
23
  def lookup
22
24
  @actor = Federails::Actor.find_by_account account_param
23
25
  authorize @actor, policy_class: Federails::Client::ActorPolicy
24
- render :show
26
+ render_show
25
27
  end
26
28
 
27
29
  private
@@ -35,6 +37,18 @@ module Federails
35
37
  def account_param
36
38
  params.require('account')
37
39
  end
40
+
41
+ def render_show
42
+ respond_to do |format|
43
+ if @actor.tombstoned?
44
+ format.html { render :gone, status: :gone }
45
+ format.json { render json: { error: I18n.t('controller.actors.gone') }, status: :gone }
46
+ else
47
+ format.html { render :show }
48
+ format.json { render :show }
49
+ end
50
+ end
51
+ end
38
52
  end
39
53
  end
40
54
  end
@@ -5,7 +5,10 @@ module Federails
5
5
 
6
6
  # GET /federation/actors/1
7
7
  # GET /federation/actors/1.json
8
- def show; end
8
+ def show
9
+ status = @actor.tombstoned? ? :gone : :ok
10
+ render :show, status: status
11
+ end
9
12
 
10
13
  # GET /federation/actors/:id/followers
11
14
  # GET /federation/actors/:id/followers.json
@@ -0,0 +1,30 @@
1
+ module Federails
2
+ module Server
3
+ # Controller to render ActivityPub representation of entities configured with Federails::DataEntity
4
+ class PublishedController < Federails::ServerController
5
+ def show
6
+ @publishable = type_scope.find_untombstoned_by!(url_param => params[:id])
7
+ authorize @publishable, policy_class: Federails::Server::PublishablePolicy
8
+ end
9
+
10
+ private
11
+
12
+ def publishable_config
13
+ return @publishable_config if instance_variable_defined? :@publishable_config
14
+
15
+ _, @publishable_config = Federails.configuration.data_types.find { |_, v| v[:route_path_segment].to_s == params[:publishable_type] }
16
+ raise ActiveRecord::RecordNotFound, "Invalid #{params[:publishable_type]} type" unless @publishable_config
17
+
18
+ @publishable_config
19
+ end
20
+
21
+ def url_param
22
+ publishable_config[:url_param]
23
+ end
24
+
25
+ def type_scope
26
+ publishable_config[:class].all
27
+ end
28
+ end
29
+ end
30
+ end
@@ -9,11 +9,12 @@ module Federails
9
9
  resource = params.require(:resource)
10
10
  case resource
11
11
  when %r{^https?://.+}
12
- @user = Federails::Actor.find_by_federation_url(resource)&.entity
12
+ @user = Federails::Actor.find_by_federation_url!(resource).entity # rubocop:disable Rails/DynamicFindBy
13
13
  when /^acct:.+/
14
- Federails::Configuration.actor_types.each_value do |entity|
15
- @user ||= entity[:class].find_by(entity[:username_field] => username)
16
- end
14
+ actor = Federails::Actor.find_local_by_username(username)
15
+ raise Federails::Actor::TombstonedError if actor&.tombstoned?
16
+
17
+ @user = actor&.entity
17
18
  end
18
19
  raise ActiveRecord::RecordNotFound if @user.nil?
19
20
 
@@ -31,11 +32,13 @@ module Federails
31
32
  private
32
33
 
33
34
  def username
34
- account = Fediverse::Webfinger.split_resource_account params.require(:resource)
35
+ return @username if instance_variable_defined? :@username
36
+
37
+ account = Fediverse::Webfinger.split_account params.require(:resource)
35
38
  # Fail early if user don't _seems_ local
36
39
  raise ActiveRecord::RecordNotFound unless account && Fediverse::Webfinger.local_user?(account)
37
40
 
38
- account[:username]
41
+ @username = account[:username]
39
42
  end
40
43
  end
41
44
  end
@@ -8,6 +8,9 @@ module Federails
8
8
  helper Federails::ServerHelper
9
9
 
10
10
  rescue_from ActiveRecord::RecordNotFound, with: :error_not_found
11
+ rescue_from Federails::Actor::TombstonedError,
12
+ Federails::DataEntity::TombstonedError,
13
+ with: :error_gone
11
14
 
12
15
  private
13
16
 
@@ -26,5 +29,9 @@ module Federails
26
29
  def error_not_found(exception = nil)
27
30
  error_fallback(exception, 'Resource not found', :not_found)
28
31
  end
32
+
33
+ def error_gone(exception = nil)
34
+ error_fallback(exception, 'Resource is gone', :gone)
35
+ end
29
36
  end
30
37
  end
@@ -1,51 +1,45 @@
1
1
  module Federails
2
+ # Concern to include in models that acts as actors.
3
+ #
4
+ # Actors can be anything; they authors content _via_ their _outbox_ and receive content in their _inbox_.
5
+ # Actors can follow and be followed by each other
6
+ #
7
+ # By default, when an entry is created on models using this concern, a _local_ `Federails::Actor` will be created.
8
+ #
9
+ # See also:
10
+ # - https://www.w3.org/TR/activitypub/#actor-objects
11
+ #
12
+ # ## Usage
13
+ #
14
+ # Include the concern in an existing model:
15
+ #
16
+ # ```rb
17
+ # class User < ApplicationRecord
18
+ # include Federails::ActorEntity
19
+ # acts_as_federails_actor options
20
+ # end
21
+ # ```
2
22
  module ActorEntity
3
23
  extend ActiveSupport::Concern
4
24
 
5
- included do # rubocop:todo Metrics/BlockLength
6
- include ActiveSupport::Callbacks
7
- define_callbacks :followed
8
-
9
- # Define a method that will be called after the entity receives a follow request
10
- # @param method [Symbol] The name of the method to call, or a block that will be called directly
11
- # @example
12
- # after_followed :accept_follow
13
- def self.after_followed(method)
14
- set_callback :followed, :after, method
15
- end
16
-
17
- # Define a method that will be called after an activity has been received
18
- # @param activity_type [String] The activity action to handle, e.g. 'Create'. If you specify '*', the handler will be called for any activity type.
19
- # @param object_type [String] The object type to handle, e.g. 'Note'. If you specify '*', the handler will be called for any object type.
20
- # @param method [Symbol] The name of the class method to call. The method will receive the complete activity payload as a parameter.
21
- # @example
22
- # after_activity_received 'Create', 'Note', :create_note
23
- def self.after_activity_received(activity_type, object_type, method)
24
- Fediverse::Inbox.register_handler(activity_type, object_type, self, method)
25
- end
26
-
27
- has_one :federails_actor, class_name: 'Federails::Actor', as: :entity, dependent: :destroy
28
-
29
- after_create :create_federails_actor, if: lambda {
30
- raise("Entity not configured for #{self.class.name}. Did you use \"acts_as_federails_actor\"?") unless Federails.actor_entity? self
31
-
32
- Federails.actor_entity(self)[:auto_create_actors]
33
- }
34
-
25
+ # Class methods automatically included in the concern.
26
+ module ClassMethods
35
27
  # Configures the mapping between entity and actor
28
+ #
36
29
  # @param username_field [Symbol] The method or attribute name that returns the preferred username for ActivityPub
37
30
  # @param name_field [Symbol] The method or attribute name that returns the preferred name for ActivityPub
38
31
  # @param profile_url_method [Symbol] The route method name that will generate the profile URL for ActivityPub
39
32
  # @param actor_type [String] The ActivityStreams Actor type for this entity; defaults to 'Person'
40
33
  # @param user_count_method [Symbol] A class method to call to count active users. Leave unspecified to leave this
41
- # entity out of user counts. Method signature should accept a single parameter which will specify a date range
42
- # If parameter is nil, the total user count should be returned. If the parameter is specified, the number of users
43
- # active during the time period should be returned.
34
+ # entity out of user counts. Method signature should accept a single parameter which will specify a date range
35
+ # If parameter is nil, the total user count should be returned. If the parameter is specified, the number of users
36
+ # active during the time period should be returned.
44
37
  # @param auto_create_actors [Boolean] Whether to automatically create an actor when the entity is created
38
+ #
45
39
  # @example
46
40
  # acts_as_federails_actor username_field: :username, name_field: :display_name, profile_url_method: :url_for, actor_type: 'Person'
47
41
  # rubocop:disable Metrics/ParameterLists
48
- def self.acts_as_federails_actor(
42
+ def acts_as_federails_actor(
49
43
  name_field:,
50
44
  username_field:,
51
45
  profile_url_method: nil,
@@ -65,34 +59,104 @@ module Federails
65
59
  end
66
60
  # rubocop:enable Metrics/ParameterLists
67
61
 
68
- # Add custom data to actor responses.
69
- # Override in your own model to add extra data, which will be merged into the actor response
70
- # generated by Federails. You can include extra `@context` for activitypub extensions and it will
71
- # be merged with the main response context.
62
+ # Define a method that will be called after the entity receives a follow request.
63
+ # The follow request will be passed as an argument to the method.
64
+ #
65
+ # @param method_name [Symbol] The name of the method to call, or a block that will be called directly
66
+ #
67
+ # @example
68
+ # after_followed :accept_follow
69
+ def after_followed(method_name)
70
+ @after_followed = method_name
71
+ end
72
+
73
+ # Define a method that will be called after a follow request made by the entity is accepted
74
+ # The accepted follow request will be passed as an argument to the method.
75
+ #
76
+ # @param method_name [Symbol] The name of the method to call, or a block that will be called directly
77
+ #
72
78
  # @example
73
- # def to_activitypub_object
74
- # {
75
- # "@context": {
76
- # toot: "http://joinmastodon.org/ns#",
77
- # attributionDomains: {
78
- # "@id": "toot:attributionDomains",
79
- # "@type": "@id"
80
- # }
81
- # },
82
- # attributionDomains: [
83
- # "example.com"
84
- # ]
85
- # }
86
- # end
87
- def to_activitypub_object
88
- {}
79
+ # after_follow_accepted :follow_accepted
80
+ def after_follow_accepted(method_name)
81
+ @after_follow_accepted = method_name
82
+ end
83
+
84
+ # Define a method that will be called after an activity has been received
85
+ #
86
+ # @param activity_type [String] The activity action to handle, e.g. 'Create'. If you specify '*', the handler will be called for any activity type.
87
+ # @param object_type [String] The object type to handle, e.g. 'Note'. If you specify '*', the handler will be called for any object type.
88
+ # @param method_name [Symbol] The name of the class method to call. The method will receive the complete activity payload as a parameter.
89
+ #
90
+ # @example
91
+ # after_activity_received 'Create', 'Note', :create_note
92
+ def after_activity_received(activity_type, object_type, method_name)
93
+ Fediverse::Inbox.register_handler(activity_type, object_type, self, method_name)
89
94
  end
90
95
 
91
96
  private
92
97
 
93
- def create_federails_actor
94
- Federails::Actor.create! entity: self
98
+ def dispatch_callback(name, instance, *args)
99
+ case name
100
+ when :after_followed
101
+ instance.send(@after_followed, *args) if @after_followed
102
+ when :after_follow_accepted
103
+ instance.send(@after_follow_accepted, *args) if @after_follow_accepted
104
+ end
95
105
  end
96
106
  end
107
+
108
+ included do
109
+ # No "dependent: :xyz" as the "before_destroy" hook should have nullified the actor
110
+ has_one :federails_actor, class_name: 'Federails::Actor', as: :entity # rubocop:disable Rails/HasManyOrHasOneDependent
111
+
112
+ after_create :create_federails_actor, if: lambda {
113
+ raise("Entity not configured for #{self.class.name}. Did you use \"acts_as_federails_actor\"?") unless Federails.actor_entity? self
114
+
115
+ Federails.actor_entity(self)[:auto_create_actors]
116
+ }
117
+ before_destroy :tombstone_federails_actor!
118
+ end
119
+
120
+ # Add custom data to actor responses.
121
+ #
122
+ # Override in your own model to add extra data, which will be merged into the actor response
123
+ # generated by Federails. You can include extra `@context` for activitypub extensions and it will
124
+ # be merged with the main response context.
125
+ #
126
+ # @example
127
+ # def to_activitypub_object
128
+ # {
129
+ # "@context": {
130
+ # toot: "http://joinmastodon.org/ns#",
131
+ # attributionDomains: {
132
+ # "@id": "toot:attributionDomains",
133
+ # "@type": "@id"
134
+ # }
135
+ # },
136
+ # attributionDomains: [
137
+ # "example.com"
138
+ # ]
139
+ # }
140
+ # end
141
+ def to_activitypub_object
142
+ {}
143
+ end
144
+
145
+ private
146
+
147
+ # Result is used to determine if an actor related to this entity should be created as local actor or not
148
+ #
149
+ # Override it in your models if you need distant actors to be related to another entity.
150
+ def create_federails_actor_as_local?
151
+ true
152
+ end
153
+
154
+ def create_federails_actor
155
+ Federails::Actor.create_with(local: create_federails_actor_as_local?).find_or_create_by!(entity: self)
156
+ end
157
+
158
+ def tombstone_federails_actor!
159
+ federails_actor.tombstone!
160
+ end
97
161
  end
98
162
  end