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.
- checksums.yaml +4 -4
- data/README.md +70 -2
- data/Rakefile +5 -9
- data/app/assets/config/fedipub_manifest.js +1 -0
- data/app/assets/stylesheets/fedipub/application.css +15 -0
- data/app/controllers/concerns/fedipub/server/render_collections.rb +19 -0
- data/app/controllers/fedipub/client/activities_controller.rb +27 -0
- data/app/controllers/fedipub/client/actors_controller.rb +54 -0
- data/app/controllers/fedipub/client/followings_controller.rb +103 -0
- data/app/controllers/fedipub/client_controller.rb +9 -0
- data/app/controllers/fedipub/server/activities_controller.rb +78 -0
- data/app/controllers/fedipub/server/actors_controller.rb +50 -0
- data/app/controllers/fedipub/server/followings_controller.rb +19 -0
- data/app/controllers/fedipub/server/nodeinfo_controller.rb +28 -0
- data/app/controllers/fedipub/server/published_controller.rb +30 -0
- data/app/controllers/fedipub/server/web_finger_controller.rb +45 -0
- data/app/controllers/fedipub/server_controller.rb +37 -0
- data/app/helpers/fedipub/server_helper.rb +18 -0
- data/app/jobs/fedipub/application_job.rb +10 -0
- data/app/jobs/fedipub/fetch_nodeinfo_job.rb +10 -0
- data/app/jobs/fedipub/notify_inbox_job.rb +10 -0
- data/app/mailers/fedipub/application_mailer.rb +6 -0
- data/app/models/concerns/fedipub/actor_entity.rb +166 -0
- data/app/models/concerns/fedipub/announceable.rb +18 -0
- data/app/models/concerns/fedipub/data_entity.rb +279 -0
- data/app/models/concerns/fedipub/handles_delete_requests.rb +36 -0
- data/app/models/concerns/fedipub/has_uuid.rb +61 -0
- data/app/models/concerns/fedipub/likeable.rb +29 -0
- data/app/models/fedipub/activity.rb +62 -0
- data/app/models/fedipub/actor.rb +303 -0
- data/app/models/fedipub/application_record.rb +5 -0
- data/app/models/fedipub/following.rb +72 -0
- data/app/models/fedipub/host.rb +48 -0
- data/app/policies/fedipub/client/activity_policy.rb +9 -0
- data/app/policies/fedipub/client/actor_policy.rb +15 -0
- data/app/policies/fedipub/client/following_policy.rb +35 -0
- data/app/policies/fedipub/fedipub_policy.rb +63 -0
- data/app/policies/fedipub/server/activity_policy.rb +9 -0
- data/app/policies/fedipub/server/actor_policy.rb +23 -0
- data/app/policies/fedipub/server/following_policy.rb +6 -0
- data/app/policies/fedipub/server/publishable_policy.rb +15 -0
- data/app/views/fedipub/client/activities/_activity.html.erb +5 -0
- data/app/views/fedipub/client/activities/_activity.json.jbuilder +1 -0
- data/app/views/fedipub/client/activities/_index.json.jbuilder +1 -0
- data/app/views/fedipub/client/activities/feed.html.erb +11 -0
- data/app/views/fedipub/client/activities/feed.json.jbuilder +1 -0
- data/app/views/fedipub/client/activities/index.html.erb +5 -0
- data/app/views/fedipub/client/activities/index.json.jbuilder +1 -0
- data/app/views/fedipub/client/actors/_actor.json.jbuilder +17 -0
- data/app/views/fedipub/client/actors/_lookup_form.html.erb +5 -0
- data/app/views/fedipub/client/actors/gone.html.erb +1 -0
- data/app/views/fedipub/client/actors/index.html.erb +44 -0
- data/app/views/fedipub/client/actors/index.json.jbuilder +1 -0
- data/app/views/fedipub/client/actors/show.html.erb +112 -0
- data/app/views/fedipub/client/actors/show.json.jbuilder +1 -0
- data/app/views/fedipub/client/common/_client_links.html.erb +26 -0
- data/app/views/fedipub/client/followings/_follow.html.erb +4 -0
- data/app/views/fedipub/client/followings/_follow_actions.html.erb +36 -0
- data/app/views/fedipub/client/followings/_follower.html.erb +7 -0
- data/app/views/fedipub/client/followings/_following.json.jbuilder +1 -0
- data/app/views/fedipub/client/followings/_form.html.erb +22 -0
- data/app/views/fedipub/client/followings/index.html.erb +29 -0
- data/app/views/fedipub/client/followings/index.json.jbuilder +1 -0
- data/app/views/fedipub/client/followings/show.html.erb +21 -0
- data/app/views/fedipub/client/followings/show.json.jbuilder +1 -0
- data/app/views/fedipub/server/activities/_activity.activitypub.jbuilder +25 -0
- data/app/views/fedipub/server/activities/show.activitypub.jbuilder +1 -0
- data/app/views/fedipub/server/actors/_actor.activitypub.jbuilder +27 -0
- data/app/views/fedipub/server/actors/_tombstone.activitypub.jbuilder +6 -0
- data/app/views/fedipub/server/actors/show.activitypub.jbuilder +5 -0
- data/app/views/fedipub/server/followings/_following.activitypub.jbuilder +7 -0
- data/app/views/fedipub/server/followings/show.activitypub.jbuilder +1 -0
- data/app/views/fedipub/server/nodeinfo/index.nodeinfo.jbuilder +6 -0
- data/app/views/fedipub/server/nodeinfo/show.nodeinfo.jbuilder +19 -0
- data/app/views/fedipub/server/published/_publishable.activitypub.jbuilder +11 -0
- data/app/views/fedipub/server/published/_tombstone.activitypub.jbuilder +6 -0
- data/app/views/fedipub/server/published/show.activitypub.jbuilder +5 -0
- data/app/views/fedipub/server/shared/ordered_collection.activitypub.jbuilder +6 -0
- data/app/views/fedipub/server/shared/ordered_collection_page.activitypub.jbuilder +12 -0
- data/app/views/fedipub/server/web_finger/find.jrd.jbuilder +24 -0
- data/app/views/fedipub/server/web_finger/host_meta.xrd.erb +5 -0
- data/app/views/users/show.html.erb +4 -0
- data/config/initializers/mime_types.rb +21 -0
- data/config/initializers/security_vocab.rb +53 -0
- data/config/routes.rb +53 -0
- data/db/migrate/20200712133150_create_federails_actors.rb +22 -0
- data/db/migrate/20200712143127_create_federails_followings.rb +14 -0
- data/db/migrate/20200712174938_create_federails_activities.rb +11 -0
- data/db/migrate/20241002094500_add_uuids.rb +13 -0
- data/db/migrate/20241002094501_add_keypair_to_actors.rb +8 -0
- 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/db/migrate/20250426061729_create_federails_hosts.rb +22 -0
- data/db/migrate/20251121160720_add_to_and_cc_to_federails_activities.rb +6 -0
- data/db/migrate/20260421170640_add_result_and_instrument_to_federails_activities.rb +6 -0
- data/db/migrate/20260723113716_rename_federails_to_fedipub.rb +15 -0
- data/lib/fedipub/configuration.rb +119 -0
- data/lib/fedipub/data_transformer/note.rb +36 -0
- data/lib/fedipub/engine.rb +11 -0
- data/lib/fedipub/maintenance/actors_updater.rb +66 -0
- data/lib/fedipub/maintenance/hosts_updater.rb +19 -0
- data/lib/fedipub/utils/actor.rb +85 -0
- data/lib/fedipub/utils/context.rb +12 -0
- data/lib/fedipub/utils/host.rb +54 -0
- data/lib/fedipub/utils/json_request.rb +41 -0
- data/lib/fedipub/utils/object.rb +131 -0
- data/lib/fedipub/utils/response_codes.rb +11 -0
- data/lib/fedipub/version.rb +1 -3
- data/lib/fedipub.rb +127 -4
- data/lib/fediverse/collection.rb +31 -0
- data/lib/fediverse/inbox.rb +110 -0
- data/lib/fediverse/node_info.rb +38 -0
- data/lib/fediverse/notifier.rb +90 -0
- data/lib/fediverse/request.rb +29 -0
- data/lib/fediverse/signature.rb +49 -0
- data/lib/fediverse/webfinger.rb +140 -0
- data/lib/fediverse.rb +5 -0
- data/lib/generators/fedipub/copy_client_policies/USAGE +8 -0
- data/lib/generators/fedipub/copy_client_policies/copy_client_policies_generator.rb +9 -0
- data/lib/generators/fedipub/copy_client_views/USAGE +8 -0
- data/lib/generators/fedipub/copy_client_views/copy_client_views_generator.rb +9 -0
- data/lib/generators/fedipub/copy_factories/USAGE +8 -0
- data/lib/generators/fedipub/copy_factories/copy_factories_generator.rb +43 -0
- data/lib/generators/fedipub/install/USAGE +9 -0
- data/lib/generators/fedipub/install/install_generator.rb +10 -0
- data/lib/generators/fedipub/install/templates/fedipub.rb +1 -0
- data/lib/generators/fedipub/install/templates/fedipub.yml +27 -0
- data/lib/tasks/factory_bot.rake +15 -0
- data/lib/tasks/fedipub_tasks.rake +13 -0
- metadata +259 -10
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
require 'fedipub/utils/host'
|
|
2
|
+
require 'fedipub/utils/actor'
|
|
3
|
+
require 'fediverse/webfinger'
|
|
4
|
+
|
|
5
|
+
module Fedipub
|
|
6
|
+
# Model storing _distant_ actors and links to local ones.
|
|
7
|
+
#
|
|
8
|
+
# To make a model act as an actor, use the `Fedipub::ActorEntity` concern
|
|
9
|
+
#
|
|
10
|
+
# See also:
|
|
11
|
+
# - https://www.w3.org/TR/activitypub/#actor-objects
|
|
12
|
+
class Actor < ApplicationRecord # rubocop:disable Metrics/ClassLength
|
|
13
|
+
class TombstonedError < StandardError; end
|
|
14
|
+
|
|
15
|
+
include Fedipub::HasUuid
|
|
16
|
+
include Fedipub::HandlesDeleteRequests
|
|
17
|
+
include Fedipub::Likeable
|
|
18
|
+
include Fedipub::Announceable
|
|
19
|
+
|
|
20
|
+
validates :federated_url, presence: { unless: :entity }, uniqueness: { unless: :local? }
|
|
21
|
+
validates :username, presence: { unless: :local? }
|
|
22
|
+
validates :server, presence: { unless: :local? }
|
|
23
|
+
validates :inbox_url, presence: { unless: :local? }
|
|
24
|
+
validates :outbox_url, presence: { unless: :local? }
|
|
25
|
+
validates :followers_url, presence: { unless: :local? }
|
|
26
|
+
validates :followings_url, presence: { unless: :local? }
|
|
27
|
+
validates :profile_url, presence: { unless: :local? }
|
|
28
|
+
validates :actor_type, presence: { unless: :local? }
|
|
29
|
+
validates :entity_id, uniqueness: { scope: :entity_type }, if: :entity_type
|
|
30
|
+
validates :entity, presence: true, if: -> { local? && !tombstoned? }
|
|
31
|
+
|
|
32
|
+
belongs_to :entity, polymorphic: true, optional: true
|
|
33
|
+
# FIXME: Handle this with something like undelete
|
|
34
|
+
has_many :activities, dependent: :destroy
|
|
35
|
+
has_many :activities_as_entity, class_name: 'Fedipub::Activity', as: :entity, dependent: :destroy
|
|
36
|
+
has_many :following_followers, class_name: 'Fedipub::Following', foreign_key: :target_actor_id, dependent: :destroy, inverse_of: :target_actor
|
|
37
|
+
has_many :following_follows, class_name: 'Fedipub::Following', dependent: :destroy, inverse_of: :actor
|
|
38
|
+
# Actors following actor
|
|
39
|
+
has_many :followers, source: :actor, through: :following_followers
|
|
40
|
+
# Actors followed by actor
|
|
41
|
+
has_many :follows, source: :target_actor, through: :following_follows
|
|
42
|
+
belongs_to :host, class_name: 'Fedipub::Host', foreign_key: :server, primary_key: :domain, inverse_of: :actors, optional: true
|
|
43
|
+
|
|
44
|
+
# Explicitly explain serialization for MariaDB
|
|
45
|
+
attribute :extensions, :json
|
|
46
|
+
|
|
47
|
+
scope :local, -> { where(local: true) }
|
|
48
|
+
scope :distant, -> { where(local: false) }
|
|
49
|
+
scope :tombstoned, -> { where.not(tombstoned_at: nil) }
|
|
50
|
+
scope :not_tombstoned, -> { where(tombstoned_at: nil) }
|
|
51
|
+
|
|
52
|
+
after_create -> { FetchNodeinfoJob.perform_later(server) }, unless: :local?
|
|
53
|
+
|
|
54
|
+
on_fedipub_delete_requested -> { tombstone! }
|
|
55
|
+
on_fedipub_undelete_requested -> { untombstone! }
|
|
56
|
+
|
|
57
|
+
def distant?
|
|
58
|
+
!local?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def federated_url
|
|
62
|
+
use_entity_attributes? ? Fedipub::Engine.routes.url_helpers.server_actor_url(self) : attributes['federated_url'].presence
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def username
|
|
66
|
+
return attributes['username'] unless use_entity_attributes?
|
|
67
|
+
|
|
68
|
+
entity.send(entity_configuration[:username_field]).to_s
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def name
|
|
72
|
+
value = (entity.send(entity_configuration[:name_field]).to_s if use_entity_attributes?)
|
|
73
|
+
|
|
74
|
+
value || attributes['name'] || username
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def server
|
|
78
|
+
use_entity_attributes? ? Utils::Host.localhost : attributes['server']
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def actor_type
|
|
82
|
+
use_entity_attributes? ? entity_configuration[:actor_type] : attributes['actor_type']
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def inbox_url
|
|
86
|
+
use_entity_attributes? ? Fedipub::Engine.routes.url_helpers.server_actor_inbox_url(self) : attributes['inbox_url']
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def outbox_url
|
|
90
|
+
use_entity_attributes? ? Fedipub::Engine.routes.url_helpers.server_actor_outbox_url(self) : attributes['outbox_url']
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def followers_url
|
|
94
|
+
use_entity_attributes? ? Fedipub::Engine.routes.url_helpers.followers_server_actor_url(self) : attributes['followers_url']
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def followings_url
|
|
98
|
+
use_entity_attributes? ? Fedipub::Engine.routes.url_helpers.following_server_actor_url(self) : attributes['followings_url']
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def profile_url
|
|
102
|
+
return attributes['profile_url'].presence unless use_entity_attributes?
|
|
103
|
+
|
|
104
|
+
method = entity_configuration[:profile_url_method]
|
|
105
|
+
return Fedipub::Engine.routes.url_helpers.server_actor_url self unless method
|
|
106
|
+
|
|
107
|
+
Rails.application.routes.url_helpers.send method, [entity]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def at_address(prefix: '@')
|
|
111
|
+
"#{prefix}#{username}@#{server}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def short_at_address
|
|
115
|
+
use_entity_attributes? ? "@#{username}" : at_address
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def acct_uri
|
|
119
|
+
"acct:#{username}@#{server}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Checks if a given actor follows the current actor
|
|
123
|
+
#
|
|
124
|
+
# @return [Fedipub::Following, false]
|
|
125
|
+
def follows?(actor)
|
|
126
|
+
list = following_follows.where target_actor: actor
|
|
127
|
+
return list.first if list.one?
|
|
128
|
+
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Checks if current actor is followed by the given actor
|
|
133
|
+
#
|
|
134
|
+
# @return [Fedipub::Following, false]
|
|
135
|
+
def followed_by?(actor)
|
|
136
|
+
list = following_followers.where actor: actor
|
|
137
|
+
return list.first if list.one?
|
|
138
|
+
|
|
139
|
+
false
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def entity_configuration
|
|
143
|
+
raise("Entity not configured for #{entity_type}. Did you use \"acts_as_fedipub_actor\"?") unless Fedipub.actor_entity? entity_type
|
|
144
|
+
|
|
145
|
+
Fedipub.actor_entity entity_type
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Synchronizes actor with distant data
|
|
149
|
+
#
|
|
150
|
+
# @raise [ActiveRecord::RecordNotFound] when distant data was not found
|
|
151
|
+
def sync!
|
|
152
|
+
if local?
|
|
153
|
+
Rails.logger.info 'Ignored attempt to sync a local actor'
|
|
154
|
+
return false
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
response = Fediverse::Webfinger.fetch_actor_url(federated_url)
|
|
158
|
+
new_attributes = response.attributes.except 'id', 'uuid', 'created_at', 'updated_at', 'local', 'entity_id', 'entity_type'
|
|
159
|
+
|
|
160
|
+
update! new_attributes
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def tombstoned?
|
|
164
|
+
tombstoned_at.present?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def tombstone!
|
|
168
|
+
Fedipub::Utils::Actor.tombstone! self
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def untombstone!
|
|
172
|
+
Fedipub::Utils::Actor.untombstone! self
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
class << self
|
|
176
|
+
# Searches for an actor from account URI
|
|
177
|
+
#
|
|
178
|
+
# @param account [String] Account URI (username@host)
|
|
179
|
+
# @return [Fedipub::Actor, nil]
|
|
180
|
+
def find_by_account(account)
|
|
181
|
+
parts = Fediverse::Webfinger.split_account account
|
|
182
|
+
|
|
183
|
+
if Fediverse::Webfinger.local_user? parts
|
|
184
|
+
actor = find_local_by_username! parts[:username]
|
|
185
|
+
else
|
|
186
|
+
actor = find_by username: parts[:username], server: parts[:domain]
|
|
187
|
+
actor ||= Fediverse::Webfinger.fetch_actor(parts[:username], parts[:domain])
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
actor
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def find_by_federation_url(federated_url)
|
|
194
|
+
local_route = Utils::Host.local_route federated_url
|
|
195
|
+
return find_param(local_route[:id]) if local_route && local_route[:controller] == 'fedipub/server/actors' && local_route[:action] == 'show'
|
|
196
|
+
|
|
197
|
+
actor = find_by federated_url: federated_url
|
|
198
|
+
return actor if actor
|
|
199
|
+
|
|
200
|
+
Fediverse::Webfinger.fetch_actor_url(federated_url)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def find_by_federation_url!(federated_url)
|
|
204
|
+
find_by_federation_url(federated_url).tap do |actor|
|
|
205
|
+
raise Fedipub::Actor::TombstonedError if actor.tombstoned?
|
|
206
|
+
raise ActiveRecord::RecordNotFound if actor.nil?
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def find_or_create_by_account(account)
|
|
211
|
+
actor = find_by_account account
|
|
212
|
+
# Create/update distant actors
|
|
213
|
+
actor.save! unless actor.local?
|
|
214
|
+
|
|
215
|
+
actor
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def find_or_create_by_federation_url(url)
|
|
219
|
+
actor = find_by_federation_url url
|
|
220
|
+
# Create/update distant actors
|
|
221
|
+
actor.save! unless actor.local?
|
|
222
|
+
|
|
223
|
+
actor
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Find or create actor from a given actor hash or actor id (actor's URL)
|
|
227
|
+
def find_or_create_by_object(object)
|
|
228
|
+
case object
|
|
229
|
+
when String
|
|
230
|
+
find_or_create_by_federation_url object
|
|
231
|
+
when Hash
|
|
232
|
+
find_or_create_by_federation_url object['id']
|
|
233
|
+
else
|
|
234
|
+
raise "Unsupported object type for actor (#{object.class})"
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def find_local_by_username(username)
|
|
239
|
+
actor = nil
|
|
240
|
+
Fedipub::Configuration.actor_types.each_value do |entity|
|
|
241
|
+
break if actor.present?
|
|
242
|
+
|
|
243
|
+
actor = entity[:class].find_by(entity[:username_field] => username)&.fedipub_actor
|
|
244
|
+
end
|
|
245
|
+
return actor if actor
|
|
246
|
+
|
|
247
|
+
# Last hope: Search for tombstoned actors
|
|
248
|
+
Fedipub::Actor.local.tombstoned.find_by username: username
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def find_local_by_username!(username)
|
|
252
|
+
find_local_by_username(username).tap do |actor|
|
|
253
|
+
raise ActiveRecord::RecordNotFound if actor.nil?
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def public_key
|
|
259
|
+
ensure_key_pair_exists!
|
|
260
|
+
self[:public_key]
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def private_key
|
|
264
|
+
ensure_key_pair_exists!
|
|
265
|
+
self[:private_key]
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def key_id
|
|
269
|
+
"#{federated_url}#main-key"
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
private
|
|
273
|
+
|
|
274
|
+
def ensure_key_pair_exists!
|
|
275
|
+
return if self[:private_key].present? || !local?
|
|
276
|
+
|
|
277
|
+
update!(generate_key_pair)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def generate_key_pair
|
|
281
|
+
rsa_key = OpenSSL::PKey::RSA.new 2048
|
|
282
|
+
cipher = OpenSSL::Cipher.new('AES-128-CBC')
|
|
283
|
+
{
|
|
284
|
+
private_key: if Rails.application.credentials.secret_key_base
|
|
285
|
+
rsa_key.to_pem(cipher, Rails.application.credentials.secret_key_base)
|
|
286
|
+
else
|
|
287
|
+
rsa_key.to_pem
|
|
288
|
+
end,
|
|
289
|
+
public_key: rsa_key.public_key.to_pem,
|
|
290
|
+
}
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def use_entity_attributes?
|
|
294
|
+
local? && !tombstoned? && entity.present?
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def create_fedipub_activity(action, actor: self, to: nil, cc: nil)
|
|
298
|
+
return unless local
|
|
299
|
+
|
|
300
|
+
Activity.create! actor: actor, action: action, entity: self, to: to, cc: cc
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Fedipub
|
|
2
|
+
# Stores following data between actors
|
|
3
|
+
class Following < ApplicationRecord
|
|
4
|
+
include Fedipub::HasUuid
|
|
5
|
+
|
|
6
|
+
enum :status, pending: 0, accepted: 1
|
|
7
|
+
|
|
8
|
+
validates :target_actor_id, uniqueness: { scope: [:actor_id, :target_actor_id] }
|
|
9
|
+
|
|
10
|
+
belongs_to :actor
|
|
11
|
+
belongs_to :target_actor, class_name: 'Fedipub::Actor'
|
|
12
|
+
has_many :activities, as: :entity, dependent: :destroy
|
|
13
|
+
|
|
14
|
+
after_create :after_follow
|
|
15
|
+
after_create :create_activity, if: :locally_instigated?
|
|
16
|
+
after_update :after_follow_accepted
|
|
17
|
+
after_destroy :destroy_activity, if: :locally_instigated?
|
|
18
|
+
|
|
19
|
+
define_callbacks :on_fedipub_delete_requested
|
|
20
|
+
|
|
21
|
+
set_callback :on_fedipub_delete_requested, -> { destroy! unless locally_instigated? }
|
|
22
|
+
|
|
23
|
+
scope :with_actor, ->(actor) { where(actor_id: actor.id).or(where(target_actor_id: actor.id)) }
|
|
24
|
+
|
|
25
|
+
def federated_url
|
|
26
|
+
attributes['federated_url'].presence || Fedipub::Engine.routes.url_helpers.server_actor_following_url(actor_id: actor.to_param, id: to_param)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def accept!
|
|
30
|
+
update! status: :accepted
|
|
31
|
+
Activity.create! actor: target_actor, action: 'Accept', entity: self, to: [actor.federated_url]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def follow_activity
|
|
35
|
+
Activity.find_by actor: actor, action: 'Follow', entity: target_actor
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
def new_from_account(account, actor:)
|
|
40
|
+
target_actor = Actor.find_or_create_by_account account
|
|
41
|
+
new actor: actor, target_actor: target_actor
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def locally_instigated?
|
|
48
|
+
actor.local?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def after_follow
|
|
52
|
+
return unless target_actor&.entity
|
|
53
|
+
|
|
54
|
+
target_actor.entity.class.send(:dispatch_callback, :after_followed, target_actor.entity, self)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def after_follow_accepted
|
|
58
|
+
return unless status_previously_changed? && status == 'accepted'
|
|
59
|
+
return unless actor&.entity
|
|
60
|
+
|
|
61
|
+
actor.entity.class.send(:dispatch_callback, :after_follow_accepted, actor.entity, self)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def create_activity
|
|
65
|
+
Activity.create! actor: actor, action: 'Follow', entity: target_actor, to: [target_actor.federated_url]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def destroy_activity
|
|
69
|
+
follow_activity&.undo!
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'fediverse/node_info'
|
|
2
|
+
|
|
3
|
+
module Fedipub
|
|
4
|
+
class Host < ApplicationRecord
|
|
5
|
+
attribute :protocols, :json
|
|
6
|
+
attribute :services, :json
|
|
7
|
+
|
|
8
|
+
validates :domain, presence: true, allow_blank: false, uniqueness: true
|
|
9
|
+
|
|
10
|
+
# No "dependent" option here as this is not a hard reference, and we want to keep the actors if the host gets deleted
|
|
11
|
+
has_many :actors, class_name: 'Fedipub::Actor', primary_key: :domain, foreign_key: :server, inverse_of: :host # rubocop:disable Rails/HasManyOrHasOneDependent
|
|
12
|
+
|
|
13
|
+
scope :same_app, -> { where software_name: Configuration.app_name }
|
|
14
|
+
scope :same_app_and_version, -> { same_app.where app_version: Configuration.app_version }
|
|
15
|
+
|
|
16
|
+
def same_app?
|
|
17
|
+
software_name == Configuration.app_name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def same_app_and_version?
|
|
21
|
+
software_name == Configuration.app_name && app_version == Configuration.app_version
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Update from remote data
|
|
25
|
+
def sync!
|
|
26
|
+
update! Fediverse::NodeInfo.fetch(domain)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
# Creates or update a Host
|
|
31
|
+
#
|
|
32
|
+
# @param domain [String] Domain to check
|
|
33
|
+
# @param min_update_interval [Integer, ActiveSupport::Duration] Minimum amount of seconds since the last update to fetch fresh data
|
|
34
|
+
def create_or_update(domain, min_update_interval: 0)
|
|
35
|
+
entry = find_or_initialize_by domain: domain
|
|
36
|
+
return if min_update_interval && entry.persisted? && (entry.updated_at + min_update_interval) > Time.current
|
|
37
|
+
|
|
38
|
+
entry.sync!
|
|
39
|
+
|
|
40
|
+
entry
|
|
41
|
+
rescue Fediverse::NodeInfo::NoActivityPubError
|
|
42
|
+
Rails.logger.info { "#{domain} does not provide ActivityPub service" }
|
|
43
|
+
rescue Fedipub::Utils::JsonRequest::UnhandledResponseStatus, Faraday::SSLError => e
|
|
44
|
+
Rails.logger.info { "Error connecting to #{domain}: '#{e.message}'" }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Fedipub
|
|
2
|
+
module Client
|
|
3
|
+
class FollowingPolicy < Fedipub::FedipubPolicy
|
|
4
|
+
def show?
|
|
5
|
+
in_following?
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def destroy?
|
|
9
|
+
in_following?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def accept?
|
|
13
|
+
in_following? && @record.target_actor_id == @user.fedipub_actor.id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def follow?
|
|
17
|
+
create?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Scope < Scope
|
|
21
|
+
def resolve
|
|
22
|
+
scope.with_actor(@user.fedipub_actor)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def in_following?
|
|
29
|
+
return false unless user_with_actor?
|
|
30
|
+
|
|
31
|
+
@record.actor_id == @user.fedipub_actor&.id || @record.target_actor_id == @user.fedipub_actor&.id
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Fedipub
|
|
2
|
+
class FedipubPolicy
|
|
3
|
+
attr_reader :user, :record
|
|
4
|
+
|
|
5
|
+
def initialize(user, record)
|
|
6
|
+
@user = user
|
|
7
|
+
@record = record
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def index?
|
|
11
|
+
true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def show?
|
|
15
|
+
true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create?
|
|
19
|
+
@user.present?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def new?
|
|
23
|
+
create?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def update?
|
|
27
|
+
owner?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def edit?
|
|
31
|
+
update?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def destroy?
|
|
35
|
+
owner?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class Scope
|
|
39
|
+
attr_reader :user, :scope
|
|
40
|
+
|
|
41
|
+
def initialize(user, scope)
|
|
42
|
+
@user = user
|
|
43
|
+
@scope = scope
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def resolve
|
|
47
|
+
scope.all
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def owner?
|
|
54
|
+
return false unless @user
|
|
55
|
+
|
|
56
|
+
@record.actor_id == @user.fedipub_actor.id
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def user_with_actor?
|
|
60
|
+
@user && Fedipub.actor_entity?(@user) && !!@user.fedipub_actor
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Fedipub
|
|
2
|
+
module Server
|
|
3
|
+
class ActorPolicy < Fedipub::FedipubPolicy
|
|
4
|
+
def show?
|
|
5
|
+
@record.local?
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def following?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def followers?
|
|
13
|
+
true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Scope < Scope
|
|
17
|
+
def resolve
|
|
18
|
+
scope.local
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Fedipub
|
|
2
|
+
module Server
|
|
3
|
+
class PublishablePolicy < Fedipub::FedipubPolicy
|
|
4
|
+
def show?
|
|
5
|
+
@record.send(@record.fedipub_data_configuration[:should_federate_method])
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class Scope < Scope
|
|
9
|
+
def resolve
|
|
10
|
+
raise NotImplementedError
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
json.extract! activity, :id, :entity_id, :entity_type, :action, :actor_id, :created_at
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
json.array! @activities, partial: 'fedipub/client/activities/activity', as: :activity
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<h1>Your feed</h1>
|
|
2
|
+
<p>
|
|
3
|
+
This is the list of activities from actors you follow.
|
|
4
|
+
</p>
|
|
5
|
+
|
|
6
|
+
<% if @activities.size.zero? %>
|
|
7
|
+
<p>There is nothing to display. Start following things!</p>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% @activities.each do |activity| %>
|
|
10
|
+
<%= render 'activity', activity: activity %>
|
|
11
|
+
<% end %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
json.partial! 'fedipub/client/activities/index'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
json.partial! 'fedipub/client/activities/index'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
json.extract! actor,
|
|
2
|
+
:id,
|
|
3
|
+
:name,
|
|
4
|
+
:federated_url,
|
|
5
|
+
:username,
|
|
6
|
+
:inbox_url,
|
|
7
|
+
:outbox_url,
|
|
8
|
+
:followers_url,
|
|
9
|
+
:followings_url,
|
|
10
|
+
:profile_url,
|
|
11
|
+
:at_address,
|
|
12
|
+
:local,
|
|
13
|
+
:entity_id,
|
|
14
|
+
:entity_type,
|
|
15
|
+
:tombstoned_at,
|
|
16
|
+
:created_at,
|
|
17
|
+
:updated_at
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= t('.gone') %>
|