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
@@ -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,5 @@
1
+ module Fedipub
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ 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,9 @@
1
+ module Fedipub
2
+ module Client
3
+ class ActivityPolicy < Fedipub::FedipubPolicy
4
+ def feed?
5
+ user_with_actor?
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Fedipub
2
+ module Client
3
+ class ActorPolicy < Fedipub::FedipubPolicy
4
+ def lookup?
5
+ true
6
+ end
7
+
8
+ class Scope < Scope
9
+ def resolve
10
+ scope
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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,9 @@
1
+ module Fedipub
2
+ module Server
3
+ class ActivityPolicy < Fedipub::FedipubPolicy
4
+ def outbox?
5
+ true
6
+ end
7
+ end
8
+ end
9
+ 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,6 @@
1
+ module Fedipub
2
+ module Server
3
+ class FollowingPolicy < Fedipub::FedipubPolicy
4
+ end
5
+ end
6
+ 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,5 @@
1
+ <div>
2
+ <b><%= link_to activity.actor.name, fedipub.client_actor_url(activity.actor) %></b>
3
+ <code><%= activity.action %></code>
4
+ <!-- < %= link_to activity.entity_type, activity.entity %>-->
5
+ </div>
@@ -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,5 @@
1
+ <h1>Listing activities</h1>
2
+
3
+ <% @activities.each do |activity| %>
4
+ <%= render 'activity', activity: activity %>
5
+ <% end %>
@@ -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,5 @@
1
+ <%= form_tag fedipub.lookup_client_actors_url, method: :get do %>
2
+ <%= label_tag :account %>
3
+ <%= text_field_tag :account, nil, placeholder: 'user@domain.tld or username', required: true %>
4
+ <%= submit_tag 'Search' %>
5
+ <% end %>
@@ -0,0 +1 @@
1
+ <%= t('.gone') %>