federails 0.5.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.
- checksums.yaml +4 -4
- data/README.md +26 -1
- data/app/controllers/federails/client/actors_controller.rb +18 -4
- data/app/controllers/federails/server/actors_controller.rb +4 -1
- data/app/controllers/federails/server/published_controller.rb +1 -1
- data/app/controllers/federails/server/web_finger_controller.rb +9 -6
- data/app/controllers/federails/server_controller.rb +7 -0
- data/app/models/concerns/federails/actor_entity.rb +40 -8
- data/app/models/concerns/federails/data_entity.rb +49 -22
- data/app/models/concerns/federails/handles_delete_requests.rb +31 -0
- data/app/models/federails/activity.rb +15 -4
- data/app/models/federails/actor.rb +87 -31
- data/app/models/federails/following.rb +28 -9
- data/app/views/federails/client/actors/_actor.json.jbuilder +4 -1
- data/app/views/federails/client/actors/gone.html.erb +1 -0
- data/app/views/federails/client/actors/index.html.erb +7 -1
- data/app/views/federails/server/activities/_activity.activitypub.jbuilder +8 -3
- data/app/views/federails/server/actors/_actor.activitypub.jbuilder +2 -2
- data/app/views/federails/server/actors/_tombstone.activitypub.jbuilder +9 -0
- data/app/views/federails/server/actors/show.activitypub.jbuilder +5 -1
- data/app/views/federails/server/published/_tombstone.activitypub.jbuilder +9 -0
- data/app/views/federails/server/published/show.activitypub.jbuilder +5 -1
- data/db/migrate/20250122160618_add_extensions_to_federails_actors.rb +5 -0
- data/db/migrate/20250301082500_add_local_to_actors.rb +11 -0
- data/db/migrate/20250329123939_add_actor_type_to_actors.rb +5 -0
- data/db/migrate/20250329123940_add_tombstoned_at_to_actors.rb +5 -0
- data/lib/federails/configuration.rb +5 -1
- data/lib/federails/data_transformer/note.rb +1 -1
- data/lib/federails/maintenance/actors_updater.rb +67 -0
- data/lib/federails/utils/actor.rb +53 -0
- data/lib/federails/utils/object.rb +25 -0
- data/lib/federails/version.rb +1 -1
- data/lib/fediverse/inbox.rb +25 -4
- data/lib/fediverse/webfinger.rb +19 -23
- data/lib/tasks/federails_tasks.rake +8 -4
- metadata +13 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0538a0c2d7f7e3845a5d8ae304ac2b319b3cf1e76b10520645773f8f763c3431'
|
4
|
+
data.tar.gz: e97c559ecc5aba1b1c05c9be5f39b982a22e92cb8f73ae8c8a08741a3efb3d01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 560377c5b698b87274d1df302595c33467f010402096227583161087aaf124b0a9e2820f44234b2932c158e84b0ab8ccb79ea8501557840509a416c7b8205295
|
7
|
+
data.tar.gz: 6380fc79556446afbd4a81e3e5e2e6b49bdb5877576dd1038208c1c4471cd3986bb35bbfaae1a9f37e618ddf5ab14dcf6974f512f6a609c70c2de0b7c251803f
|
data/README.md
CHANGED
@@ -23,12 +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
|
+
## Supported Ruby on Rails versions
|
27
|
+
|
28
|
+
This gem is tested against non end-of-life versions of Ruby and Rails:
|
29
|
+
|
30
|
+
- Ruby versions 3.1 to 3.4
|
31
|
+
- Rails 7.1 to 8.0.x.
|
32
|
+
|
33
|
+
Feel free to open an issue if we missed something
|
34
|
+
|
35
|
+
It _may_ work on other versions, but we won't provide support.
|
36
|
+
|
26
37
|
## Documentation
|
27
38
|
|
28
39
|
- [Usage](docs/usage.md)
|
29
40
|
- [Common questions](docs/faq.md)
|
30
|
-
- [Contributing](
|
41
|
+
- [Contributing](CONTRIBUTING.md)
|
31
42
|
|
32
43
|
## License
|
33
44
|
|
34
45
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
|
49
|
+
See [CONTRIBUTING](CONTRIBUTING.md) to have an overview of the process and the tools we use.
|
50
|
+
|
51
|
+
### Contributors
|
52
|
+
|
53
|
+
- [echarp](https://gitlab.com/echarp)
|
54
|
+
- [James Smith](https://gitlab.com/floppy.uk)
|
55
|
+
- [Manuel Tancoigne](https://gitlab.com/mtancoigne)
|
56
|
+
|
57
|
+
### Indirect contributions
|
58
|
+
|
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
|
17
|
+
def show
|
18
|
+
render_show
|
19
|
+
end
|
18
20
|
|
19
|
-
# GET /app/
|
20
|
-
# GET /app/
|
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
|
-
|
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
|
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
|
@@ -3,7 +3,7 @@ module Federails
|
|
3
3
|
# Controller to render ActivityPub representation of entities configured with Federails::DataEntity
|
4
4
|
class PublishedController < Federails::ServerController
|
5
5
|
def show
|
6
|
-
@publishable = type_scope.
|
6
|
+
@publishable = type_scope.find_untombstoned_by!(url_param => params[:id])
|
7
7
|
authorize @publishable, policy_class: Federails::Server::PublishablePolicy
|
8
8
|
end
|
9
9
|
|
@@ -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)
|
12
|
+
@user = Federails::Actor.find_by_federation_url!(resource).entity # rubocop:disable Rails/DynamicFindBy
|
13
13
|
when /^acct:.+/
|
14
|
-
Federails::
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
@@ -59,14 +59,26 @@ module Federails
|
|
59
59
|
end
|
60
60
|
# rubocop:enable Metrics/ParameterLists
|
61
61
|
|
62
|
-
# Define a method that will be called after the entity receives a follow request
|
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.
|
63
64
|
#
|
64
65
|
# @param method_name [Symbol] The name of the method to call, or a block that will be called directly
|
65
66
|
#
|
66
67
|
# @example
|
67
68
|
# after_followed :accept_follow
|
68
69
|
def after_followed(method_name)
|
69
|
-
|
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
|
+
#
|
78
|
+
# @example
|
79
|
+
# after_follow_accepted :follow_accepted
|
80
|
+
def after_follow_accepted(method_name)
|
81
|
+
@after_follow_accepted = method_name
|
70
82
|
end
|
71
83
|
|
72
84
|
# Define a method that will be called after an activity has been received
|
@@ -80,20 +92,29 @@ module Federails
|
|
80
92
|
def after_activity_received(activity_type, object_type, method_name)
|
81
93
|
Fediverse::Inbox.register_handler(activity_type, object_type, self, method_name)
|
82
94
|
end
|
83
|
-
end
|
84
95
|
|
85
|
-
|
86
|
-
include ActiveSupport::Callbacks
|
96
|
+
private
|
87
97
|
|
88
|
-
|
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
|
105
|
+
end
|
106
|
+
end
|
89
107
|
|
90
|
-
|
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
|
91
111
|
|
92
112
|
after_create :create_federails_actor, if: lambda {
|
93
113
|
raise("Entity not configured for #{self.class.name}. Did you use \"acts_as_federails_actor\"?") unless Federails.actor_entity? self
|
94
114
|
|
95
115
|
Federails.actor_entity(self)[:auto_create_actors]
|
96
116
|
}
|
117
|
+
before_destroy :tombstone_federails_actor!
|
97
118
|
end
|
98
119
|
|
99
120
|
# Add custom data to actor responses.
|
@@ -123,8 +144,19 @@ module Federails
|
|
123
144
|
|
124
145
|
private
|
125
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
|
+
|
126
154
|
def create_federails_actor
|
127
|
-
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!
|
128
160
|
end
|
129
161
|
end
|
130
162
|
end
|
@@ -25,10 +25,17 @@ module Federails
|
|
25
25
|
# class Post < ApplicationRecord
|
26
26
|
# include Federails::DataEntity
|
27
27
|
# acts_as_federails_data options
|
28
|
+
#
|
29
|
+
# # This will be called when a Delete activity comes for the entry. As we don't know how you want to handle it,
|
30
|
+
# # you'll have to implement the behavior yourself.
|
31
|
+
# on_federails_delete_requested :do_something
|
28
32
|
# end
|
29
33
|
# ```
|
30
34
|
module DataEntity
|
35
|
+
class TombstonedError < StandardError; end
|
36
|
+
|
31
37
|
extend ActiveSupport::Concern
|
38
|
+
include Federails::HandlesDeleteRequests
|
32
39
|
|
33
40
|
# Class methods automatically included in the concern.
|
34
41
|
module ClassMethods
|
@@ -49,10 +56,14 @@ module Federails
|
|
49
56
|
# @param should_federate_method [Symbol] method to determine if an object should be federated. If the method returns false,
|
50
57
|
# no create/update activities will happen, and object will not be accessible at federated_url. Defaults to a method
|
51
58
|
# that always returns true.
|
59
|
+
# @param soft_deleted_method [Symbol, nil] Method to soft-delete the object when receiving a Delete request. This
|
60
|
+
# is not required by the spec but greatly encouraged as the app will return a 410 response with a Tombstone object
|
61
|
+
# instead of an 404 error.
|
62
|
+
# @param soft_delete_date_method [Symbol, nil] Method to get the date of the soft-deletion
|
52
63
|
#
|
53
64
|
# @example
|
54
65
|
# acts_as_federails_data handles: 'Note', with: :note_handler, route_path_segment: :articles, actor_entity_method: :user
|
55
|
-
# rubocop:disable Metrics/ParameterLists
|
66
|
+
# rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
|
56
67
|
def acts_as_federails_data(
|
57
68
|
handles:,
|
58
69
|
with: :handle_incoming_fediverse_data,
|
@@ -60,23 +71,28 @@ module Federails
|
|
60
71
|
actor_entity_method: nil,
|
61
72
|
url_param: :id,
|
62
73
|
filter_method: nil,
|
63
|
-
should_federate_method: :default_should_federate
|
74
|
+
should_federate_method: :default_should_federate?,
|
75
|
+
soft_deleted_method: nil,
|
76
|
+
soft_delete_date_method: nil
|
64
77
|
)
|
65
78
|
route_path_segment ||= name.pluralize.underscore
|
66
79
|
|
67
80
|
Federails::Configuration.register_data_type self,
|
68
|
-
route_path_segment:
|
69
|
-
actor_entity_method:
|
70
|
-
url_param:
|
71
|
-
handles:
|
72
|
-
with:
|
73
|
-
filter_method:
|
74
|
-
should_federate_method:
|
75
|
-
|
81
|
+
route_path_segment: route_path_segment,
|
82
|
+
actor_entity_method: actor_entity_method,
|
83
|
+
url_param: url_param,
|
84
|
+
handles: handles,
|
85
|
+
with: with,
|
86
|
+
filter_method: filter_method,
|
87
|
+
should_federate_method: should_federate_method,
|
88
|
+
soft_deleted_method: soft_deleted_method,
|
89
|
+
soft_delete_date_method: soft_delete_date_method
|
90
|
+
|
91
|
+
# NOTE: Delete activities cannot be handled like this as we can't be sure to have the object's type
|
76
92
|
Fediverse::Inbox.register_handler 'Create', handles, self, with
|
77
93
|
Fediverse::Inbox.register_handler 'Update', handles, self, with
|
78
94
|
end
|
79
|
-
# rubocop:enable Metrics/ParameterLists
|
95
|
+
# rubocop:enable Metrics/ParameterLists, Metrics/MethodLength
|
80
96
|
|
81
97
|
# Instantiates a new instance from an ActivityPub object
|
82
98
|
#
|
@@ -107,6 +123,15 @@ module Federails
|
|
107
123
|
|
108
124
|
entity
|
109
125
|
end
|
126
|
+
|
127
|
+
def find_untombstoned_by!(**params)
|
128
|
+
configuration = Federails.data_entity_configuration(self)
|
129
|
+
entity = find_by!(**params)
|
130
|
+
|
131
|
+
raise Federails::DataEntity::TombstonedError if configuration[:soft_deleted_method] && entity.send(configuration[:soft_deleted_method])
|
132
|
+
|
133
|
+
entity
|
134
|
+
end
|
110
135
|
end
|
111
136
|
|
112
137
|
included do
|
@@ -116,8 +141,9 @@ module Federails
|
|
116
141
|
scope :distant_federails_entities, -> { where.not(federated_url: nil) }
|
117
142
|
|
118
143
|
before_validation :set_federails_actor
|
119
|
-
after_create
|
120
|
-
after_update :
|
144
|
+
after_create -> { create_federails_activity 'Create' }
|
145
|
+
after_update -> { create_federails_activity 'Update' }, :federails_tombstoned?
|
146
|
+
after_destroy -> { create_federails_activity 'Delete' }
|
121
147
|
end
|
122
148
|
|
123
149
|
# Computed value for the federated URL
|
@@ -139,6 +165,14 @@ module Federails
|
|
139
165
|
attributes['federated_url'].blank?
|
140
166
|
end
|
141
167
|
|
168
|
+
def federails_tombstoned?
|
169
|
+
federails_data_configuration[:soft_deleted_method] ? send(federails_data_configuration[:soft_deleted_method]) : false
|
170
|
+
end
|
171
|
+
|
172
|
+
def federails_tombstoned_at
|
173
|
+
federails_data_configuration[:soft_delete_date_method] ? send(federails_data_configuration[:soft_delete_date_method]) : nil
|
174
|
+
end
|
175
|
+
|
142
176
|
def federails_data_configuration
|
143
177
|
Federails.data_entity_configuration(self)
|
144
178
|
end
|
@@ -153,18 +187,11 @@ module Federails
|
|
153
187
|
raise 'Cannot determine actor from configuration' unless federails_actor
|
154
188
|
end
|
155
189
|
|
156
|
-
def create_federails_activity
|
157
|
-
ensure_federails_configuration!
|
158
|
-
return unless local_federails_entity? && send(federails_data_configuration[:should_federate_method])
|
159
|
-
|
160
|
-
Activity.create! actor: federails_actor, action: 'Create', entity: self
|
161
|
-
end
|
162
|
-
|
163
|
-
def update_federails_activity
|
190
|
+
def create_federails_activity(action)
|
164
191
|
ensure_federails_configuration!
|
165
192
|
return unless local_federails_entity? && send(federails_data_configuration[:should_federate_method])
|
166
193
|
|
167
|
-
Activity.create! actor: federails_actor, action:
|
194
|
+
Activity.create! actor: federails_actor, action: action, entity: self
|
168
195
|
end
|
169
196
|
|
170
197
|
def ensure_federails_configuration!
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Federails
|
2
|
+
# Model concern providing hooks for on_federails_delete_requested callback
|
3
|
+
#
|
4
|
+
# ```rb
|
5
|
+
# # Example migration
|
6
|
+
# add_column :my_table, :uuid, :text, default: nil, index: { unique: true }
|
7
|
+
# ```
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
#
|
11
|
+
# ```rb
|
12
|
+
# class MyModel < ApplicationRecord
|
13
|
+
# include Federails::HandlesDeleteRequests
|
14
|
+
#
|
15
|
+
# on_federails_delete_requested -> { delete! }
|
16
|
+
# end
|
17
|
+
module HandlesDeleteRequests
|
18
|
+
extend ActiveSupport::Concern
|
19
|
+
|
20
|
+
# Class methods automatically included in the concern.
|
21
|
+
module ClassMethods
|
22
|
+
def on_federails_delete_requested(*args)
|
23
|
+
set_callback :on_federails_delete_requested, *args
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
included do
|
28
|
+
define_callbacks :on_federails_delete_requested
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -30,16 +30,27 @@ module Federails
|
|
30
30
|
def recipients
|
31
31
|
return [] unless actor.local?
|
32
32
|
|
33
|
-
case
|
34
|
-
when '
|
35
|
-
[
|
33
|
+
case action
|
34
|
+
when 'Follow'
|
35
|
+
[entity]
|
36
|
+
when 'Undo'
|
37
|
+
[entity.entity]
|
38
|
+
when 'Accept'
|
39
|
+
[entity.actor]
|
36
40
|
else
|
37
|
-
|
41
|
+
default_recipient_list
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
41
45
|
private
|
42
46
|
|
47
|
+
def default_recipient_list
|
48
|
+
list = actor.followers
|
49
|
+
# If local actor is the subject, notify that actor's followers as well
|
50
|
+
list += entity.followers if entity.is_a?(Federails::Actor) && entity.local?
|
51
|
+
list.uniq
|
52
|
+
end
|
53
|
+
|
43
54
|
def post_to_inboxes
|
44
55
|
NotifyInboxJob.perform_later(self)
|
45
56
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'federails/utils/host'
|
2
|
+
require 'federails/utils/actor'
|
2
3
|
require 'fediverse/webfinger'
|
3
4
|
|
4
5
|
module Federails
|
@@ -9,17 +10,22 @@ module Federails
|
|
9
10
|
# See also:
|
10
11
|
# - https://www.w3.org/TR/activitypub/#actor-objects
|
11
12
|
class Actor < ApplicationRecord # rubocop:disable Metrics/ClassLength
|
12
|
-
|
13
|
+
class TombstonedError < StandardError; end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
validates :
|
18
|
-
validates :
|
19
|
-
validates :
|
20
|
-
validates :
|
21
|
-
validates :
|
22
|
-
validates :
|
15
|
+
include Federails::HasUuid
|
16
|
+
include Federails::HandlesDeleteRequests
|
17
|
+
|
18
|
+
validates :federated_url, presence: { unless: :entity }, uniqueness: { unless: :local? }
|
19
|
+
validates :username, presence: { unless: :local? }
|
20
|
+
validates :server, presence: { unless: :local? }
|
21
|
+
validates :inbox_url, presence: { unless: :local? }
|
22
|
+
validates :outbox_url, presence: { unless: :local? }
|
23
|
+
validates :followers_url, presence: { unless: :local? }
|
24
|
+
validates :followings_url, presence: { unless: :local? }
|
25
|
+
validates :profile_url, presence: { unless: :local? }
|
26
|
+
validates :actor_type, presence: { unless: :local? }
|
27
|
+
validates :entity_id, uniqueness: { scope: :entity_type }, if: :entity_type
|
28
|
+
validates :entity, presence: true, if: -> { local? && !tombstoned? }
|
23
29
|
|
24
30
|
belongs_to :entity, polymorphic: true, optional: true
|
25
31
|
# FIXME: Handle this with something like undelete
|
@@ -30,51 +36,59 @@ module Federails
|
|
30
36
|
has_many :followers, source: :actor, through: :following_followers
|
31
37
|
has_many :follows, source: :target_actor, through: :following_follows
|
32
38
|
|
33
|
-
scope :local, -> { where
|
34
|
-
scope :distant, -> { where
|
39
|
+
scope :local, -> { where(local: true) }
|
40
|
+
scope :distant, -> { where(local: false) }
|
41
|
+
scope :tombstoned, -> { where.not(tombstoned_at: nil) }
|
42
|
+
scope :not_tombstoned, -> { where(tombstoned_at: nil) }
|
35
43
|
|
36
|
-
|
37
|
-
|
44
|
+
on_federails_delete_requested -> { tombstone! }
|
45
|
+
|
46
|
+
def distant?
|
47
|
+
!local?
|
38
48
|
end
|
39
49
|
|
40
50
|
def federated_url
|
41
|
-
|
51
|
+
use_entity_attributes? ? Federails::Engine.routes.url_helpers.server_actor_url(self) : attributes['federated_url'].presence
|
42
52
|
end
|
43
53
|
|
44
54
|
def username
|
45
|
-
return attributes['username'] unless
|
55
|
+
return attributes['username'] unless use_entity_attributes?
|
46
56
|
|
47
57
|
entity.send(entity_configuration[:username_field]).to_s
|
48
58
|
end
|
49
59
|
|
50
60
|
def name
|
51
|
-
value = (entity.send(entity_configuration[:name_field]).to_s if
|
61
|
+
value = (entity.send(entity_configuration[:name_field]).to_s if use_entity_attributes?)
|
52
62
|
|
53
63
|
value || attributes['name'] || username
|
54
64
|
end
|
55
65
|
|
56
66
|
def server
|
57
|
-
|
67
|
+
use_entity_attributes? ? Utils::Host.localhost : attributes['server']
|
68
|
+
end
|
69
|
+
|
70
|
+
def actor_type
|
71
|
+
use_entity_attributes? ? entity_configuration[:actor_type] : attributes['actor_type']
|
58
72
|
end
|
59
73
|
|
60
74
|
def inbox_url
|
61
|
-
|
75
|
+
use_entity_attributes? ? Federails::Engine.routes.url_helpers.server_actor_inbox_url(self) : attributes['inbox_url']
|
62
76
|
end
|
63
77
|
|
64
78
|
def outbox_url
|
65
|
-
|
79
|
+
use_entity_attributes? ? Federails::Engine.routes.url_helpers.server_actor_outbox_url(self) : attributes['outbox_url']
|
66
80
|
end
|
67
81
|
|
68
82
|
def followers_url
|
69
|
-
|
83
|
+
use_entity_attributes? ? Federails::Engine.routes.url_helpers.followers_server_actor_url(self) : attributes['followers_url']
|
70
84
|
end
|
71
85
|
|
72
86
|
def followings_url
|
73
|
-
|
87
|
+
use_entity_attributes? ? Federails::Engine.routes.url_helpers.following_server_actor_url(self) : attributes['followings_url']
|
74
88
|
end
|
75
89
|
|
76
90
|
def profile_url
|
77
|
-
return attributes['profile_url'].presence unless
|
91
|
+
return attributes['profile_url'].presence unless use_entity_attributes?
|
78
92
|
|
79
93
|
method = entity_configuration[:profile_url_method]
|
80
94
|
return Federails::Engine.routes.url_helpers.server_actor_url self unless method
|
@@ -83,11 +97,15 @@ module Federails
|
|
83
97
|
end
|
84
98
|
|
85
99
|
def at_address
|
86
|
-
"
|
100
|
+
"@#{username}@#{server}"
|
87
101
|
end
|
88
102
|
|
89
103
|
def short_at_address
|
90
|
-
|
104
|
+
use_entity_attributes? ? "@#{username}" : at_address
|
105
|
+
end
|
106
|
+
|
107
|
+
def acct_uri
|
108
|
+
"acct:#{username}@#{server}"
|
91
109
|
end
|
92
110
|
|
93
111
|
def follows?(actor)
|
@@ -103,16 +121,24 @@ module Federails
|
|
103
121
|
Federails.actor_entity entity_type
|
104
122
|
end
|
105
123
|
|
124
|
+
def tombstoned?
|
125
|
+
tombstoned_at.present?
|
126
|
+
end
|
127
|
+
|
128
|
+
def tombstone!
|
129
|
+
Federails::Utils::Actor.tombstone! self
|
130
|
+
end
|
131
|
+
|
106
132
|
class << self
|
107
|
-
|
133
|
+
# Searches for an actor from account URI
|
134
|
+
#
|
135
|
+
# @param account [String] Account URI (username@host)
|
136
|
+
# @return [Federails::Actor, nil]
|
137
|
+
def find_by_account(account)
|
108
138
|
parts = Fediverse::Webfinger.split_account account
|
109
139
|
|
110
140
|
if Fediverse::Webfinger.local_user? parts
|
111
|
-
actor =
|
112
|
-
Federails::Configuration.actor_types.each_value do |entity|
|
113
|
-
actor ||= entity[:class].find_by(entity[:username_field] => parts[:username])&.federails_actor
|
114
|
-
end
|
115
|
-
raise ActiveRecord::RecordNotFound if actor.nil?
|
141
|
+
actor = find_local_by_username! parts[:username]
|
116
142
|
else
|
117
143
|
actor = find_by username: parts[:username], server: parts[:domain]
|
118
144
|
actor ||= Fediverse::Webfinger.fetch_actor(parts[:username], parts[:domain])
|
@@ -131,6 +157,13 @@ module Federails
|
|
131
157
|
Fediverse::Webfinger.fetch_actor_url(federated_url)
|
132
158
|
end
|
133
159
|
|
160
|
+
def find_by_federation_url!(federated_url)
|
161
|
+
find_by_federation_url(federated_url).tap do |actor|
|
162
|
+
raise Federails::Actor::TombstonedError if actor.tombstoned?
|
163
|
+
raise ActiveRecord::RecordNotFound if actor.nil?
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
134
167
|
def find_or_create_by_account(account)
|
135
168
|
actor = find_by_account account
|
136
169
|
# Create/update distant actors
|
@@ -158,6 +191,25 @@ module Federails
|
|
158
191
|
raise "Unsupported object type for actor (#{object.class})"
|
159
192
|
end
|
160
193
|
end
|
194
|
+
|
195
|
+
def find_local_by_username(username)
|
196
|
+
actor = nil
|
197
|
+
Federails::Configuration.actor_types.each_value do |entity|
|
198
|
+
break if actor.present?
|
199
|
+
|
200
|
+
actor = entity[:class].find_by(entity[:username_field] => username)&.federails_actor
|
201
|
+
end
|
202
|
+
return actor if actor
|
203
|
+
|
204
|
+
# Last hope: Search for tombstoned actors
|
205
|
+
Federails::Actor.local.tombstoned.find_by username: username
|
206
|
+
end
|
207
|
+
|
208
|
+
def find_local_by_username!(username)
|
209
|
+
find_local_by_username(username).tap do |actor|
|
210
|
+
raise ActiveRecord::RecordNotFound if actor.nil?
|
211
|
+
end
|
212
|
+
end
|
161
213
|
end
|
162
214
|
|
163
215
|
def public_key
|
@@ -194,5 +246,9 @@ module Federails
|
|
194
246
|
public_key: rsa_key.public_key.to_pem,
|
195
247
|
}
|
196
248
|
end
|
249
|
+
|
250
|
+
def use_entity_attributes?
|
251
|
+
local? && !tombstoned?
|
252
|
+
end
|
197
253
|
end
|
198
254
|
end
|